Forms
Forms collect user input. Aurora gives you the building blocks (label, field, message) and the field components (TextInput, Combobox, DatePicker, etc.) — this page covers how to put them together so every form across the product feels the same.
This demo is real. Click into a field, leave it blank, tab out — the error appears on blur. Errors clear as you fix them. Submit only fires once everything passes the schema.
Anatomy
A form field is four pieces that always travel together. Aurora ships them as small wrappers around vee-validate — the validation library does the wiring, the wrappers do the styling.
| Piece | What it does |
|---|---|
FormField | Binds a field to vee-validate. Exposes componentField slot props (value, onChange, error state). |
FormItem | Layout wrapper. layout="stacked" (default — label above) or layout="inline" (label beside, for checkboxes/toggles). |
FormLabel | The label text. Reads required from the field schema and renders the red asterisk automatically. |
FormMessage | Renders inline error / helper text. Reads the validation error from FormField automatically. |
html
<FormField v-slot="{ componentField }" name="email">
<FormItem>
<FormLabel>Email</FormLabel>
<TextInput v-bind="componentField" placeholder="you@example.com" />
<FormMessage />
</FormItem>
</FormField>That structure — FormField → FormItem → (FormLabel, the input, FormMessage) — is the same for every field type. Swap TextInput for Combobox, DatePicker, Checkbox, etc. as needed.
Layout patterns
Single column
Short, focused flows — onboarding, login, quick-edit panels. Keeps the user's eye on one decision at a time. Default to single column whenever the form has fewer than ~4 fields.
html
<form class="flex flex-col gap-4 max-w-md">
<FormField v-slot="{ componentField }" name="username">
<FormItem>
<FormLabel>Username</FormLabel>
<TextInput v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<!-- … -->
</form>Two-column grid
Forms with 4+ fields where pairs of short fields can sit side by side (first/last name, city/postal, year/quarter). Wider fields (address, notes) span the full row.
html
<form class="grid grid-cols-2 gap-4 max-w-2xl">
<FormField v-slot="{ componentField }" name="firstName">
<FormItem><FormLabel>First name</FormLabel><TextInput v-bind="componentField" /></FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="lastName">
<FormItem><FormLabel>Last name</FormLabel><TextInput v-bind="componentField" /></FormItem>
</FormField>
<!-- Address spans both columns -->
<FormField v-slot="{ componentField }" name="address">
<FormItem class="col-span-2"><FormLabel>Address</FormLabel><TextInput v-bind="componentField" /></FormItem>
</FormField>
</form>Inline (checkbox / toggle)
For boolean fields, the label sits to the right of the control. Use layout="inline" on FormItem.
html
<FormField v-slot="{ componentField }" name="terms" type="checkbox" :value="true" :unchecked-value="false">
<FormItem layout="inline">
<Checkbox v-bind="componentField" />
<FormLabel>I agree to the terms and conditions</FormLabel>
<FormMessage />
</FormItem>
</FormField>Composition rules
- Label above the field, never inside. Placeholder text disappears on focus and is invisible to screen readers — it is not a label substitute.
- Mark required, not optional. A red asterisk on required fields. If most fields are required, flip it: mark the optional ones with a "(optional)" hint to reduce visual noise.
- Validate inline, on blur. Show the error as soon as the field loses focus — don't make the user submit and discover ten errors at once.
- One primary button per form. Cancel / Back use
secondaryortertiary. Two primaries side-by-side make the dominant action ambiguous. - Cap dense layouts at 3 columns. Even in wide containers. Short fields (year, percentage, code) can share a row; longer fields (name, address, notes) span ≥ 2 columns.
- Don't use placeholders as labels. They vanish on focus.
- Don't validate only on submit. Surprises are not a feature.
- Don't stack two primary buttons. Pick one.
- Don't exceed a 3-column grid. Even on a 4K monitor.
Field states
Every input ships with the same set of validation states — default, error, warning, success, missing, disabled, readonly. You usually don't set them by hand: vee-validate's componentField slot prop forwards the error state from the schema, and the input renders the red border + icon + message automatically.
When you do need to set a state manually (e.g. async server validation, "this field needs review"):
html
<TextInput v-bind="componentField" warning />
<FormMessage warning message="Looks unusual — please double-check." />FormMessage accepts error, warning, success, missing flags and a message prop for non-vee-validate cases.
vee-validate + Zod
Aurora's form components are designed to compose with vee-validate and Zod. The schema is the single source of truth — required-ness, types, and error messages all flow from it.
ts
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
import type { DateValue } from '@scaler-tech/aurora/date-picker'
const schema = z.object({
firstName: z.string().nonempty(),
lastName: z.string().nonempty(),
email: z.string().email(),
country: z.string().nonempty(),
birthDate: z.custom<DateValue>().transform(v => v.toString()),
terms: z.coerce.boolean().refine(v => v, {
message: 'You must accept the terms and conditions',
}),
})
const { handleSubmit, controlledValues } = useForm({
validationSchema: toTypedSchema(schema),
})
const onSubmit = handleSubmit((values) => {
api.post('/account', values)
})A few aurora-specific notes:
DatePickerv-model is aDateValueobject, not a string. Call.toString()on submit (or transform on the schema, like above) to get an ISOYYYY-MM-DD.Comboboxfor object values. When you want the full option object back (not just the value), pass the object as:valueonComboboxItemand callcomponentField.onChange(option)from@select. See the Combobox docs for the recipe.
Submit / cancel buttons
Place the action row at the bottom-right of the form on desktop, full-width-stacked on mobile. Primary first (right), secondary after (left). The primary action carries the verb-noun label that names what the form does ("Save changes", "Create asset", "Submit report") — never just "OK" or "Submit".
html
<div class="flex justify-end gap-2 mt-6">
<Button variant="tertiary" @click="cancel">Cancel</Button>
<Button type="submit">Save changes</Button>
</div>For long forms, sticky the action row to the bottom of the panel so the buttons stay reachable without scrolling.
Related
TextInput— single-line text fieldsCombobox— searchable select (single or multi)DatePicker— calendar-driven date entryFileUpload— drag-and-drop file selection