Components
Form
A native form element with consolidated error handling.
Form component
Sign in
Use your Ubuntu account
'use client'; import * as React from 'react'; import { Form } from 'gnome-ui/form'; import { Field } from 'gnome-ui/field'; import { Button } from 'gnome-ui/button'; import { User, Mail, Lock, Loader2, Check, AlertCircle } from 'lucide-react'; export function FormDefault() { const [errors, setErrors] = React.useState<Record<string, string>>({}); const [loading, setLoading] = React.useState(false); const [success, setSuccess] = React.useState(false); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); setLoading(true); setSuccess(false); const data = new FormData(e.currentTarget); const email = data.get('email') as string; await new Promise((r) => setTimeout(r, 1200)); if (!email.includes('@ubuntu')) { setErrors({ email: 'Only @ubuntu.com accounts are allowed.' }); } else { setErrors({}); setSuccess(true); } setLoading(false); } return ( <div className="w-80 overflow-hidden rounded-xl border border-border bg-card shadow-sm"> <div className="border-b border-border px-5 py-4"> <p className="text-base font-semibold text-foreground">Sign in</p> <p className="mt-0.5 text-xs text-muted-foreground">Use your Ubuntu account</p> </div> <Form errors={errors} onFormSubmit={handleSubmit} className="flex flex-col gap-4 px-5 py-5"> <Field.Root name="email"> <Field.Label className="text-xs font-semibold uppercase tracking-widest text-muted-foreground"> Email </Field.Label> <div className="relative mt-1.5 flex items-center"> <Icons.Mail className="pointer-events-none absolute left-3 size-4 text-muted-foreground" /> <Field.Control type="email" required placeholder="you@ubuntu.com" className="h-10 w-full rounded-xl border border-input bg-background pl-9 pr-3.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors duration-150 hover:border-ring/50 focus:outline focus:outline-2 focus:-outline-offset-1 focus:outline-ring data-[field-invalid]:border-destructive data-[field-invalid]:focus:outline-destructive" /> </div> <Field.Error className="mt-1.5 flex items-center gap-1.5 text-xs text-destructive"> <Icons.AlertCircle className="size-3.5 shrink-0" /> <Field.Error match="valueMissing">Email is required.</Field.Error> <Field.Error match="typeMismatch">Enter a valid email address.</Field.Error> </Field.Error> </Field.Root> <Field.Root name="password"> <Field.Label className="text-xs font-semibold uppercase tracking-widest text-muted-foreground"> Password </Field.Label> <div className="relative mt-1.5 flex items-center"> <Icons.Lock className="pointer-events-none absolute left-3 size-4 text-muted-foreground" /> <Field.Control type="password" required placeholder="••••••••" className="h-10 w-full rounded-xl border border-input bg-background pl-9 pr-3.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors duration-150 hover:border-ring/50 focus:outline focus:outline-2 focus:-outline-offset-1 focus:outline-ring data-[field-invalid]:border-destructive data-[field-invalid]:focus:outline-destructive" /> </div> <Field.Error className="mt-1.5 flex items-center gap-1.5 text-xs text-destructive"> <Icons.AlertCircle className="size-3.5 shrink-0" /> <Field.Error match="valueMissing">Password is required.</Field.Error> </Field.Error> </Field.Root> <Button type="submit" disabled={loading} focusableWhenDisabled className={`inline-flex items-center justify-center gap-2 rounded-xl text-sm font-medium leading-none transition-colors duration-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 mt-1 h-10 w-full ${success ? 'bg-[oklch(0.55_0.15_150)] text-white' : 'bg-primary text-primary-foreground hover:brightness-95'} disabled:opacity-100 disabled:brightness-90`} > {loading && <Icons.Loader2 className="size-4 shrink-0 animate-spin" />} {success && <Icons.Check className="size-4 shrink-0" />} {!loading && !success && <Icons.User className="size-4 shrink-0" />} {loading ? 'Signing in…' : success ? 'Signed in!' : 'Sign in'} </Button> </Form> </div> ); }
Anatomy
import { Form } from 'gnome-ui/form'; import { Field } from 'gnome-ui/field'; <Form errors={errors} onFormSubmit={handleSubmit}> <Field.Root name="fieldName"> <Field.Label /> <Field.Control /> <Field.Error /> </Field.Root> </Form>
Examples
On-blur validation
Uses validationMode="onBlur" to validate each field as the user tabs through the form, inspired by GNOME Settings profile editor.
Form component
Profile settings
Validates when you leave each field
'use client'; import * as React from 'react'; import { Form } from 'gnome-ui/form'; import { Field } from 'gnome-ui/field'; import { Button } from 'gnome-ui/button'; import { User, Mail, Globe, Loader2, Check, AlertCircle } from 'lucide-react'; export function FormOnBlur() { const [errors, setErrors] = React.useState<Record<string, string>>({}); const [loading, setLoading] = React.useState(false); const [saved, setSaved] = React.useState(false); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); setLoading(true); await new Promise((r) => setTimeout(r, 1000)); setLoading(false); setSaved(true); setTimeout(() => setSaved(false), 2000); } return ( <div className="w-96 overflow-hidden rounded-xl border border-border bg-card shadow-sm"> <div className="border-b border-border px-5 py-4"> <p className="text-base font-semibold text-foreground">Profile settings</p> <p className="mt-0.5 text-xs text-muted-foreground">Validates when you leave each field</p> </div> <Form errors={errors} validationMode="onBlur" onFormSubmit={handleSubmit} className="flex flex-col gap-4 px-5 py-5"> <div className="flex gap-3"> <Field.Root name="firstname" className="flex-1"> <Field.Label className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">First name</Field.Label> <div className="relative mt-1.5 flex items-center"> <Icons.User className="pointer-events-none absolute left-3 size-4 text-muted-foreground" /> <Field.Control required placeholder="Elena" className="h-10 w-full rounded-xl border border-input bg-background pl-9 pr-3.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors duration-150 hover:border-ring/50 focus:outline focus:outline-2 focus:-outline-offset-1 focus:outline-ring data-[field-invalid]:border-destructive data-[field-invalid]:focus:outline-destructive" /> </div> <Field.Error className="mt-1.5 flex items-center gap-1.5 text-xs text-destructive"> <Icons.AlertCircle className="size-3.5 shrink-0" /> <Field.Error match="valueMissing">Required.</Field.Error> </Field.Error> </Field.Root> <Field.Root name="lastname" className="flex-1"> <Field.Label className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">Last name</Field.Label> <div className="relative mt-1.5 flex items-center"> <Icons.User className="pointer-events-none absolute left-3 size-4 text-muted-foreground" /> <Field.Control required placeholder="Larsson" className="h-10 w-full rounded-xl border border-input bg-background pl-9 pr-3.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors duration-150 hover:border-ring/50 focus:outline focus:outline-2 focus:-outline-offset-1 focus:outline-ring data-[field-invalid]:border-destructive data-[field-invalid]:focus:outline-destructive" /> </div> <Field.Error className="mt-1.5 flex items-center gap-1.5 text-xs text-destructive"> <Icons.AlertCircle className="size-3.5 shrink-0" /> <Field.Error match="valueMissing">Required.</Field.Error> </Field.Error> </Field.Root> </div> <Field.Root name="email"> <Field.Label className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">Email address</Field.Label> <div className="relative mt-1.5 flex items-center"> <Icons.Mail className="pointer-events-none absolute left-3 size-4 text-muted-foreground" /> <Field.Control type="email" required placeholder="elena@example.com" className="h-10 w-full rounded-xl border border-input bg-background pl-9 pr-3.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors duration-150 hover:border-ring/50 focus:outline focus:outline-2 focus:-outline-offset-1 focus:outline-ring data-[field-invalid]:border-destructive data-[field-invalid]:focus:outline-destructive" /> </div> <Field.Error className="mt-1.5 flex items-center gap-1.5 text-xs text-destructive"> <Icons.AlertCircle className="size-3.5 shrink-0" /> <Field.Error match="valueMissing">Email is required.</Field.Error> <Field.Error match="typeMismatch">Enter a valid email.</Field.Error> </Field.Error> </Field.Root> <Field.Root name="website"> <Field.Label className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">Website</Field.Label> <div className="relative mt-1.5 flex items-center"> <Icons.Globe className="pointer-events-none absolute left-3 size-4 text-muted-foreground" /> <Field.Control type="url" placeholder="https://example.com" pattern="https?://.*" className="h-10 w-full rounded-xl border border-input bg-background pl-9 pr-3.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors duration-150 hover:border-ring/50 focus:outline focus:outline-2 focus:-outline-offset-1 focus:outline-ring data-[field-invalid]:border-destructive data-[field-invalid]:focus:outline-destructive" /> </div> <Field.Error className="mt-1.5 flex items-center gap-1.5 text-xs text-destructive"> <Icons.AlertCircle className="size-3.5 shrink-0" /> <Field.Error match="patternMismatch">Must start with http:// or https://</Field.Error> </Field.Error> </Field.Root> <Button type="submit" disabled={loading} focusableWhenDisabled className={`inline-flex items-center justify-center gap-2 rounded-xl text-sm font-medium leading-none transition-colors duration-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 mt-1 h-10 w-full ${saved ? 'bg-[oklch(0.55_0.15_150)] text-white' : 'bg-primary text-primary-foreground hover:brightness-95'} disabled:opacity-100 disabled:brightness-90`} > {loading && <Icons.Loader2 className="size-4 shrink-0 animate-spin" />} {saved && <Icons.Check className="size-4 shrink-0" />} {!loading && !saved && <Icons.Check className="size-4 shrink-0" />} {loading ? 'Saving…' : saved ? 'Saved!' : 'Save changes'} </Button> </Form> </div> ); }
A native form element with consolidated error handling.
Renders a <form> element.
Documentation: Base UI Form
API reference
A native form element with consolidated error handling.
Renders a <form> element.
Form Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| errors | Errors | - | Validation errors returned externally, typically after submission by a server or a form action.
This should be an object where keys correspond to the name attribute on <Field.Root>,
and values correspond to error(s) related to that field. |
| actionsRef | RefObject<Form.Actions | null> | - | A ref to imperative actions.* validate: Validates all fields when called. Optionally pass a field name to validate a single field. |
| onFormSubmit | ((formValues: Record<string, any>, eventDetails: Form.SubmitEventDetails) => void) | - | Event handler called when the form is submitted.
preventDefault() is called on the native submit event when used. |
| validationMode | FormValidationMode | 'onSubmit' | Determines when the form should be validated.
The validationMode prop on <Field.Root> takes precedence over this.* onSubmit (default): validates the field when the form is submitted, afterwards fields will re-validate on change. |
onBlur: validates a field when it loses focus.onChange: validates the field on every change to its value. | | className |string \| ((state: Form.State) => string \| undefined)| - | CSS class applied to the element, or a function that returns a class based on the component’s state. | | style |CSSProperties \| ((state: Form.State) => CSSProperties \| undefined)| - | - | | render |ReactElement \| ((props: HTMLProps, state: Form.State) => ReactElement)| - | Allows you to replace the component’s HTML element with a different tag, or compose it with another component.Accepts aReactElementor a function that returns the element to render. |