Skip to content

CoFormRenderer

This content is not available in your language yet.

CoFormRenderer toma el objeto que devuelve useForm() y renderiza todos los campos automáticamente. Para casos típicos no necesitas escribir un solo <co-input> a mano.

<script setup>
import { useForm, CoFormRenderer } from '@prolibu-suite/cobalt-form-vue';
const form = useForm(() => ({
schema: { modelSchema: userSchema, locale: 'es' },
}));
</script>
<template>
<CoFormRenderer :form="form" />
</template>
PropTipoDefaultDescripción
formUseFormReturnObjeto retornado por useForm(). Requerido.
layout'grid' | 'stack''grid'Layout de los campos
disabledbooleanfalseDeshabilita todos los campos
readOnlybooleanfalseModo lectura (todos los campos)
excludeFieldsstring[][]Nombres de campos a NO renderizar (siguen validando)
fieldComponentsRecord<string, Component>Override de renderers (ver Custom fields)
refResolverRefResolverCallback para campos ref (ver Ref fields)
EventoPayloadCuándo
submit{ values: Record<string, any> }Submit exitoso (válido)
invalid{ errors: Record<string, string[]> }Submit con errores
change{ name: string; value: any }Cada cambio de campo
<template>
<CoFormRenderer
:form="form"
@submit="onSubmit"
@invalid="onInvalid"
@change="onChange"
/>
</template>
<script setup>
const onSubmit = ({ values }) => {
console.log('válido, valores:', values);
};
const onInvalid = ({ errors }) => {
console.warn('inválido:', errors);
};
const onChange = ({ name, value }) => {
console.log(`${name} →`, value);
};
</script>

CSS Grid con 12 columnas. Cada campo ocupa por defecto 6 columnas (la mitad). Si el field schema tiene full: true, ocupa las 12.

const schema = {
firstName: { type: 'String' }, // 6 cols
lastName: { type: 'String' }, // 6 cols (al lado)
bio: { type: 'String', full: true }, // 12 cols (línea completa)
};

Una columna, un campo por fila. Útil para forms angostos (drawers, modales).

<CoFormRenderer :form="form" layout="stack" />

CoFormRenderer expone slots para personalizar zonas específicas.

<CoFormRenderer :form="form">
<template #header>
<h2>Crear usuario</h2>
<p>Completa los datos para registrarte.</p>
</template>
<template #footer>
<div class="actions">
<co-button label="Cancelar" variant="ghost" @click="onCancel" />
<co-button
label="Guardar"
variant="primary"
:disabled="!form.isValid.value"
@click="form.submit"
/>
</div>
</template>
</CoFormRenderer>

Si no defines #footer, se renderiza un botón “Submit” por defecto.

field:<name> — override de un campo específico

Sección titulada «field:<name> — override de un campo específico»

Para cambiar cómo se renderiza UN campo sin tocar los demás:

<CoFormRenderer :form="form">
<template #field:password="{ field, value, setValue, error }">
<co-input
:label="field.label"
type="password"
:value="value"
:status="error ? 'error' : undefined"
:helper-text="error || 'Mín. 8 caracteres'"
@co-input="(e) => setValue(e.detail.value)"
>
<button slot="action" @click="togglePasswordVisibility">👁</button>
</co-input>
</template>
</CoFormRenderer>

El slot recibe:

VariableTipoDescripción
fieldFieldDescriptorMetadata del campo (name, label, kind, etc.)
valueanyValor actual
setValue(v) => voidSetter
errorstring | undefinedPrimer error (si touched)
errorsstring[]Todos los errores
touchedbooleanSi el campo fue tocado

A veces quieres validar un campo (porque la API lo requiere) pero no mostrarlo:

<CoFormRenderer
:form="form"
:exclude-fields="['internalId', 'createdAt']"
/>

Los campos excluidos:

  • NO se renderizan en la UI
  • SÍ participan de validación
  • SÍ están en form.values.value (con su default)

Diferente a poner hidden: true en el schema: ese marca a nivel schema; excludeFields es por instancia de renderer.

<!-- form completo deshabilitado durante un loading -->
<CoFormRenderer :form="form" :disabled="isLoading" />
<!-- modo solo lectura para preview -->
<CoFormRenderer :form="form" read-only />

Tiene precedencia sobre disabled/readOnly individuales en el schema.

<script setup>
import { useForm, CoFormRenderer } from '@prolibu-suite/cobalt-form-vue';
import { ref } from 'vue';
import { useToast } from '@/composables/useToast';
const toast = useToast();
const isSaving = ref(false);
const schema = {
firstName: { type: 'String', required: true, minLength: 2, label: 'Nombre' },
lastName: { type: 'String', required: true, minLength: 2, label: 'Apellido' },
email: { type: 'String', required: true, format: 'email', label: 'Email' },
role: {
type: 'String',
enum: ['admin', 'user', 'viewer'],
default: 'user',
label: 'Rol',
},
bio: {
type: 'String',
uiCom: 'TextArea',
maxLength: 500,
full: true,
label: 'Biografía',
},
};
const form = useForm(() => ({
schema: { modelSchema: schema, locale: 'es' },
onSubmit: async (values) => {
isSaving.value = true;
try {
await api.createUser(values);
toast.success('Usuario creado');
} finally {
isSaving.value = false;
}
},
}));
</script>
<template>
<CoFormRenderer
:form="form"
layout="grid"
@submit="(p) => console.log(p.values)"
@invalid="(p) => toast.error('Revisa los errores marcados')"
>
<template #header>
<h2>Nuevo usuario</h2>
</template>
<template #footer>
<co-button label="Cancelar" variant="ghost" @click="$emit('cancel')" />
<co-button
label="Crear usuario"
variant="primary"
:disabled="!form.isValid.value || isSaving"
@click="form.submit"
/>
</template>
</CoFormRenderer>
</template>