Skip to content

Ejemplos

This content is not available in your language yet.

Cuatro ejemplos end-to-end con el form real renderizado. Cada demo es un <co-form> funcional — escribe, mira cómo cambian los valores y errores en el panel de la derecha, y dale submit para ver el payload final.


Form típico con validación, errores traducidos, password con patrón, y submit.

Demo en vivo — interactúa con el form

Valores
{}
Errores
{}
<script setup>
import { ref } from 'vue';
import { useForm, CoFormRenderer } from '@prolibu-suite/cobalt-form-vue';
import { useRouter } from 'vue-router';
import { useToast } from '@/composables/useToast';
const router = useRouter();
const toast = useToast();
const submitting = ref(false);
const schema = {
firstName: { type: 'String', required: true, minLength: 2, maxLength: 50, label: 'Nombre' },
lastName: { type: 'String', required: true, minLength: 2, maxLength: 50, label: 'Apellido' },
email: { type: 'String', required: true, format: 'email', label: 'Email' },
password: { type: 'String', required: true, minLength: 8, label: 'Contraseña' },
newsletter:{ type: 'Boolean', default: true, label: 'Newsletter' },
};
const form = useForm(() => ({
schema: { modelSchema: schema, locale: 'es' },
onSubmit: async (values) => {
submitting.value = true;
try {
await api.signup(values);
toast.success('Cuenta creada. Revisa tu email.');
router.push('/login');
} finally {
submitting.value = false;
}
},
}));
</script>
<template>
<CoFormRenderer :form="form" layout="grid">
<template #header>
<h1>Crear cuenta</h1>
</template>
<template #footer>
<co-button
label="Crear cuenta"
variant="primary"
:disabled="!form.isValid.value || submitting"
:loading="submitting"
@click="form.submit"
/>
</template>
</CoFormRenderer>
</template>

El form abre con datos existentes (simulando un edit). email es read-only, role viene de un enum, bio ocupa todo el ancho.

Demo — perfil prellenado

Valores
{}
Errores
{}
<script setup>
import { useForm, CoFormRenderer } from '@prolibu-suite/cobalt-form-vue';
import { useUser } from '@/composables/useUser';
const props = defineProps({ userId: String });
const { user, loading } = useUser(props.userId);
const form = useForm(() => ({
schema: { modelSchema: userSchema, locale: 'es' },
initialValues: user.value || {},
onSubmit: async (values) => {
await api.updateUser(props.userId, values);
},
}));
</script>
<template>
<div v-if="loading">Cargando…</div>
<CoFormRenderer v-else :form="form" layout="grid">
<template #footer>
<co-button
label="Cancelar"
variant="ghost"
:disabled="!form.isDirty.value"
@click="form.reset"
/>
<co-button
label="Guardar cambios"
variant="primary"
:disabled="!form.isValid.value || !form.isDirty.value"
@click="form.submit"
/>
</template>
</CoFormRenderer>
</template>

Lo interesante:

  • form.isDirty solo es true cuando el usuario cambia algo respecto al estado prefetched.
  • “Cancelar” llama reset() para volver al estado original.
  • “Guardar” se deshabilita si no hay cambios.

Mezcla de todos los tipos: string con patrón, number con rango, boolean, enum, date.

Demo — todos los tipos de campo

Valores
{}
Errores
{}

Probá:

  • Dejar el slug con mayúsculas → ves el error del pattern
  • Bajar los asientos a 0 → error de min
  • Cambiar el plan → se actualiza al instante en el panel de valores

Wizard de 3 pasos con validación por paso. Usa useForm directamente para tener control total del renderizado entre pasos.

Demo — paso 1 del wizard (datos personales)

Valores
{}
Errores
{}

Demo — paso 2 (datos de la empresa)

Valores
{}
Errores
{}
<script setup>
import { ref, computed } from 'vue';
import { useForm } from '@prolibu-suite/cobalt-form-vue';
const step = ref(1);
const schema = {
firstName: { type: 'String', required: true, label: 'Nombre' },
lastName: { type: 'String', required: true, label: 'Apellido' },
email: { type: 'String', required: true, format: 'email', label: 'Email' },
companyName: { type: 'String', required: true, label: 'Empresa' },
companySize: {
type: 'String',
enum: ['1-10', '11-50', '51-200', '200+'],
required: true,
label: 'Tamaño',
},
industry: { type: 'String', required: true, label: 'Industria' },
};
const fieldsByStep = {
1: ['firstName', 'lastName', 'email'],
2: ['companyName', 'companySize', 'industry'],
};
const form = useForm(() => ({
schema: { modelSchema: schema, locale: 'es' },
onSubmit: async (values) => api.completeOnboarding(values),
}));
const currentStepValid = computed(() =>
fieldsByStep[step.value].every((name) => !form.errors.value[name]?.length)
);
const next = () => {
fieldsByStep[step.value].forEach((name) => form.touch(name));
if (currentStepValid.value) step.value++;
};
</script>

Patterns clave:

  • Un solo useForm para todo el wizard. No hagas un form por paso.
  • Validación por paso vía currentStepValid que solo mira los campos del paso actual.
  • Touched solo on-demand — solo marcamos touched al intentar avanzar.

  • Loading durante submit: usa form.isSubmitting.value para mostrar spinner en el botón. El controller la setea automáticamente durante onSubmit.
  • Toasts post-submit: emitir desde el componente o desde onSubmit. No tires await sin try/catch o perderás errores silenciosamente.
  • Reset entre instancias: si tu drawer se reutiliza, observa el open y llama form.reset(newInitialValues) al abrir.
  • Dirty check al cerrar: si form.isDirty.value es true cuando el usuario cierra el drawer, pregunta antes de descartar cambios.