Skip to content
omaris

Inputs

Field

Field — the label, the help line and the error, around any control.

import { Field } from 'omaris'
Learn

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.

<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.

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.

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, required and disabled from the field.
  • A form whose errors come from one place — a validator, a server response: set error per 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 label and supportingText; 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 label and description.
  • 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 own label. 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's for points at an id nobody has.
  • Keep error specific 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 label as well; it will be announced twice.
  • Use description for the error. It is always shown; error is 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.

vertical
horizontal
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.