Field
Field — the label, the help line and the error, around any control.
Walkthrough
1. 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> 2. 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> 3. 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> 4. 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> 5. 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>