Inputs
Field
Field — the label, the help line and the error, around any control.
import { Field } from 'omaris' Examples
Basic
A label, the control, a line of help. The control stays bare — the field gives it its id, its description and its state.
We only use this for billing.
<script lang="ts"> import { Field, Input } from 'omaris'; let email = $state('');</script><Field class="w-80 max-w-full" label="Work email" description="We only use this for billing."> <Input type="email" placeholder="you@example.com" bind:value={email} /></Field> Validation
error marks the field invalid and opens the error row: it grows from nothing and fades in, so the form below slides rather than jumps. It is announced politely — after the person stops typing, not over them.
Where the menu lives.
Lowercase letters, digits and dashes only.
<script lang="ts"> import { Field, Input } from 'omaris'; let slug = $state('my store'); const error = $derived( /^[a-z0-9-]*$/.test(slug) ? undefined : 'Lowercase letters, digits and dashes only.' );</script><div class="flex w-80 max-w-full flex-col gap-4"> <Field label="Store address" description="Where the menu lives." {error} required> <Input bind:value={slug} prefix="rasil.app/" /> </Field> <Field label="Display name"> <Input placeholder="The name on the menu" /> </Field></div> Horizontal
horizontal puts the label and its help at the start and the control at the end — the settings row a switch or a checkbox wants.
A sound and a banner for each new order.
Every Sunday morning.
<script lang="ts"> import { Field, Switch, Checkbox, Divider } from 'omaris'; let alerts = $state(true); let digest = $state(false);</script><div class="flex w-96 max-w-full flex-col gap-4"> <Field orientation="horizontal" label="Order alerts" description="A sound and a banner for each new order." > <Switch bind:checked={alerts} /> </Field> <Divider /> <Field orientation="horizontal" label="Weekly digest" description="Every Sunday morning."> <Checkbox bind:checked={digest} /> </Field></div> Any control
One wrapper for every control. A select, a number, a slider and a radio group each pick up the label, the help and the state — a group is named by the label rather than pointed at by it.
Pick the branch it ships from.
Up to twelve at one table.
<script lang="ts"> import { Field, Select, NumberInput, Slider, RadioGroup, Radio } from 'omaris'; let branch = $state(''); let guests = $state<number | null>(2); let spice = $state(30); let seating = $state('inside');</script><div class="flex w-80 max-w-full flex-col gap-5"> <Field label="Branch" required error={branch ? undefined : 'Pick the branch it ships from.'}> <Select bind:value={branch} placeholder="Choose one" options={[ { value: 'erbil', label: 'Erbil' }, { value: 'duhok', label: 'Duhok' } ]} /> </Field> <Field label="Guests" description="Up to twelve at one table."> <NumberInput bind:value={guests} min={1} max={12} /> </Field> <Field label="Spice"> <Slider bind:value={spice} /> </Field> <Field label="Seating"> <RadioGroup bind:value={seating} orientation="horizontal"> <Radio value="inside" label="Inside" /> <Radio value="terrace" label="Terrace" /> </RadioGroup> </Field></div> Overridden
The control's own props win over the field's, and classes reaches every part. Name the control's id on the field (controlId), so the label still points at it.
Unknown code.
Monospace digits, italic help
<script lang="ts"> import { Field, Input } from 'omaris';</script><div class="flex w-72 max-w-full flex-col gap-4"> <Field label="Coupon" required error="Unknown code." controlId="coupon-code"> <Input required={false} value="SPRING" /> </Field> <Field label="Reading" description="Monospace digits, italic help" classes={{ label: 'uppercase tracking-wide', description: 'italic' }} > <Input value="42.0000" classes={{ control: 'font-mono tabular-nums' }} /> </Field></div> When to use it
Use it for
- One label, one line of help and one error around a control that has none of its own — a bare Input, a Select, a Slider, a Radio group. The control gets the right
id,aria-describedby,aria-invalid,requiredanddisabledfrom the field. - A form whose errors come from one place — a validator, a server response: set
errorper field and each one opens its own row, announced politely. - Settings rows:
orientation="horizontal"puts the label and description at the start and a Switch or a Checkbox at the end. - A control of your own: read
getFieldContext()and it gets the same wiring.
Not for
- A single text field with its own label and help → Input's
labelandsupportingText; it already draws both, and a Field around it adds nothing. - A checkbox or switch whose words sit beside it and toggle it → Checkbox's or Switch's own
labelanddescription. - An error about the whole form, not one field → Alert above the submit button.
- Several controls under one heading → a
fieldset, or a Radio group's ownlabel. A Field describes one control.
Do
- Leave the control bare. Its own props win over the field's, so a stray
invalid={false}on the control hides the field's error state. - Set the id on the field with
controlId, not on the control — otherwise the label'sforpoints at an id nobody has. - Keep
errorspecific and short — "Lowercase letters and dashes only", not "Invalid input". Clear it the moment the value is fixed. - Validate on blur or submit, not on every keystroke from the first one; an error that opens while the person is still typing is noise.
Don't
- Put two controls in one Field; both take the same id, and the label names only the first.
- Give the control a
labelas well; it will be announced twice. - Use
descriptionfor the error. It is always shown;erroris what marks the control invalid and what a screen reader announces.
Quick reference
orientation vertical(default)horizontal
API
Field
Field — the label, the help line and the error, around any control.
The control inside stays bare: <Field label="Email" error={err}><Input /></Field>. The field shares its ids and its state through context, so the control picks up the right id, aria-describedby, aria-invalid, required and disabled on its own — and anything the control is given directly still wins.
One DOM order, two layouts. Vertical stacks label, control, description, error. Horizontal puts the label and description at the start and the control at the end, the settings-row shape a switch or a checkbox wants.
The error opens rather than appears: its row grows from nothing and fades in, so the form below slides down instead of jumping. It closes the same way, still showing the old text while it goes.
import { Field } from 'omaris' Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
label- No description in the source yet.
control- Wraps whatever control you pass as children.
description- No description in the source yet.
error- The live region the error is announced from. Always present.
collapse- The row that opens: grid rows from 0fr to 1fr, and a fade.
message- The error line itself, with its glyph.
Props
label string The label. Its for points at the control, so pressing it focuses it.
description string Help under the control — what the value is for, or what shape it takes.
error string The error text. Setting it marks the field invalid; clearing it closes the error again. Announced politely, never read out over what the person is typing.
invalid Defaults to false
boolean Invalid with no message — for when the reason is shown somewhere else. Set automatically by error.
required Defaults to false
boolean Stars the label and marks the control required.
disabled Defaults to false
boolean Dims the label and description and disables the control.
orientation Defaults to 'vertical'
FieldOrientation vertical stacks the parts. horizontal puts the label at the start and the control at the end — a switch, a checkbox, a compact number.
verticalhorizontal
controlId string The control's id. Set it here rather than on the control, so the label's for and the control agree. Generated when omitted.
children Snippet One control. A group — radios, a slider — is labelled by the field too.
class string classes FieldClasses Per-part Tailwind overrides. class still covers the root.