Formularios
Form
Un form nativo que junta los valores por name y reparte los errores del servidor a cada campo.
import { Form } from "sebs7n-ui/form"Ejemplos
Los valores llegan juntados por `name`
Sin `FormData`, sin un `useState` por campo y sin librería de formularios. Cada `Field` aporta su `name` y el submit recibe el objeto armado.
import { Button } from "sebs7n-ui/button"
import { Field, FieldError, FieldLabel } from "sebs7n-ui/field"
import { Form } from "sebs7n-ui/form"
import { Input } from "sebs7n-ui/input"
import { useState } from "react"
function Valores() {
const [enviado, setEnviado] = useState<Record<string, unknown> | null>(null)
return (
<Form className="w-full max-w-sm" onFormSubmit={(valores) => setEnviado(valores)}>
<Field name="nombre">
<FieldLabel required>Nombre</FieldLabel>
<Input required />
{/* El mensaje se escribe siempre: el del navegador sale en el idioma
del navegador, no en el de la página. */}
<FieldError match="valueMissing">Falta el nombre</FieldError>
</Field>
<Field name="empresa">
<FieldLabel>Empresa</FieldLabel>
<Input />
</Field>
<Button type="submit">Enviar</Button>
{enviado && (
<pre className="rounded-control bg-fill-1 p-3 text-mono-callout text-label-secondary">
{JSON.stringify(enviado, null, 2)}
</pre>
)}
</Form>
)
}Validar con un schema
`sebs7n-ui/lib/schema` traduce cualquier schema que implemente Standard Schema —Zod, Valibot, ArkType— a los errores que espera `Form`. El paquete no depende de ninguna de las tres: habla la interfaz, no la librería.
import { Button } from "sebs7n-ui/button"
import { Field, FieldDescription, FieldError, FieldLabel } from "sebs7n-ui/field"
import { Form } from "sebs7n-ui/form"
import { Input } from "sebs7n-ui/input"
import { useState } from "react"
import { validate, type StandardSchemaV1 } from "sebs7n-ui/lib/schema"
function ConSchema() {
const [errors, setErrors] = useState({})
// En una app esto sería `z.object({ ... })`. Acá va a mano para que el sitio
// de documentación no tenga que instalar Zod solo para un ejemplo.
const esquema: StandardSchemaV1<unknown, { email: string; edad: number }> = {
"~standard": {
version: 1,
vendor: "demo",
validate: (valores) => {
const { email, edad } = valores as { email: string; edad: string }
const issues = []
if (!email?.includes("@")) issues.push({ message: "Revisá el email", path: ["email"] })
if (!Number(edad)) issues.push({ message: "Poné un número", path: ["edad"] })
else if (Number(edad) < 18) issues.push({ message: "Tenés que ser mayor de edad", path: ["edad"] })
return issues.length > 0 ? { issues } : { value: { email, edad: Number(edad) } }
},
},
}
return (
<Form
className="w-full max-w-sm"
errors={errors}
onFormSubmit={async (valores) => {
const resultado = await validate(esquema, valores)
// Si pasa, `resultado.value` ya viene parseado por el schema: `edad` es
// un número, no el string que devuelve el input.
setErrors(resultado.ok ? {} : resultado.errors)
}}
>
<Field name="email">
<FieldLabel>Email</FieldLabel>
<Input type="email" />
<FieldError />
</Field>
<Field name="edad">
<FieldLabel>Edad</FieldLabel>
<Input inputMode="numeric" />
<FieldDescription>Probá con 15 para ver el error.</FieldDescription>
<FieldError />
</Field>
<Button type="submit">Validar</Button>
</Form>
)
}Props
Generadas del TypeScript del paquete. Las propias del componente, más las heredadas del primitivo que tienen algo que explicar —marcadas «heredada de Base UI»—. El resto está en la línea «hereda de».
Form
Hereda las props de Form.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
errors | FormContext['errors'] | — | Heredada de Base UI. Objeto { nombreDelCampo: mensaje }. Es para los errores que solo conoce el servidor. |
onFormSubmit | ((formValues: FormValues, eventDetails: Form.SubmitEventDetails) => void) | — | Heredada de Base UI. Recibe los valores juntados por name. Ya hace preventDefault(). |
validationMode | "onBlur" | "onChange" | "onSubmit" | — | Heredada de Base UI. Cuándo se validan los campos que no lo definan por su cuenta. |
Teclado
- Enter
- Envía el formulario desde cualquier campo de texto, como cualquier
<form>. - Tab
- Recorre los campos y llega al submit.
Accesibilidad
- Es un
<form>de verdad: lo entiende el navegador y sigue funcionando sin JavaScript. - Al fallar la validación, el foco va al primer campo con error en vez de quedarse en el botón.
- Cada error aparece en su campo, no en un cartel arriba de todo: quien navega con lector de pantalla lo encuentra donde tiene que arreglarlo.
- Escribí siempre el mensaje del error, con `match` o con `validate`. El del navegador sale en el idioma del navegador, no en el de la página: un formulario en español puede terminar diciendo «Please fill out this field». Es el error más fácil de no ver, porque en la máquina de quien lo programó el navegador está en español.
Reglas de uso
- `errors` es para lo que el navegador no puede saber: que un email ya está usado, que el cupón venció, que el CUIT no existe en AFIP. Se limpia solo cuando el campo cambia.
onFormSubmitrecibe los valores ya juntados porname: no hace faltaFormDatani unuseStatepor campo.- Para validar todo contra un schema está
validate()desebs7n-ui/lib/schema, que devuelve el valor parseado o los errores con la forma que espera esta prop. - El submit se deshabilita mientras se envía (
<Button loading>), o el mismo formulario se manda dos veces.