Form controls
Field
The furniture every labeled control in the kit already carries, as a component you can put around one the kit does not ship. It draws the label, the required marker and the hint or error row, and it hands the control the three things that tie those together: the id the label points at, the id of whichever message is on screen, and whether that message is an error.
Around a control the kit does not ship
The kit ships no code editor, no range slider and no color field. A page that needs one adopts a
control that draws itself, never the browser's own: a native range or color input is drawn by the
operating system and no theme reaches it, as Choosing a control explains. Wrapping the
adopted control in Field means the label, the spacing and the hint match every Input on the same form, and the hint is wired to the control rather
than only sitting under it.
<script lang="ts">
import { Field } from '@lyeve-labs/ui-kit';
import CodeEditor from '$lib/CodeEditor.svelte';
let query = $state('select 1');
</script>
<Field label="Query" hint="Runs against the read replica.">
{#snippet children({ id, describedBy })}
<CodeEditor {id} ariaDescribedby={describedBy} language="sql" bind:value={query} />
{/snippet}
</Field>CodeEditor stands for whichever editor the app adopts, and its prop names are its
own. It puts the id on the element that takes focus, so a click on the label focuses it. That is
the label's for finding the id the snippet was handed, and it is the part a hand-rolled
field row usually leaves out.
Error and required
An error replaces the hint and sets invalid on the wiring. Pass it to the control's aria-invalid: the message is already reachable through aria-describedby, and this is what says the value was rejected rather than merely
described. The asterisk beside the label is decorative, so set required on the control
as well. The control's own attribute is what a screen reader reports.
<Field label="Query" error="The query reads a table this role cannot see." required>
{#snippet children({ id, describedBy, invalid })}
<CodeEditor
{id}
required
ariaDescribedby={describedBy}
ariaInvalid={invalid}
language="sql"
bind:value={query}
/>
{/snippet}
</Field>Hidden label
labelHidden keeps a real label element with a real for and
takes it off the screen, for a control whose visible name is a table column header. It is not the
same as dropping the label for an aria-label on the control: that names the control and
removes the name from the reading order, so a reader moving down a column meets a run of controls with
nothing ahead of each one saying what it is.
<td>
<Field label="Filter expression" labelHidden>
{#snippet children({ id, describedBy })}
<CodeEditor {id} ariaDescribedby={describedBy} language="jsonpath" bind:value={row.filter} />
{/snippet}
</Field>
</td>A kit row inside a Field
A label's for reaches one form control and never a group, so a caption written as a
label over a row of segments or chips does nothing when clicked and names nothing. Give the Field group instead. It draws the caption as text with an id of its own and hands that id
over as labeledBy. The caption looks the same as a label does.
SegmentedControl takes labeledBy and names its group from the caption, along with id and describedBy for
the Field's message. Pass labelHidden as well, so the row draws no caption of its
own. FilterChips takes no labeledBy. It names
its group from its own label, which it never draws, so give that label the caption's
words.
Charts and totals cover this window.
24hAPI
| Prop | Type | Default | Description |
|---|---|---|---|
children required | Snippet<[FieldWiring]> | no default | The control. Receives what it must carry for the message row to reach a reader. |
label | string | no default | Label rendered above the control. |
labelHidden | boolean | false | Keep the caption for screen readers only. Without group it stays a real label with a real for. |
group | boolean | false | The control is a group, such as a SegmentedControl or a FilterChips row. The caption is drawn as text with an id, and the wiring hands that id over as labeledBy, because a label's for never reaches a group. |
hint | string | no default | Helper text below the control. An error replaces it. |
error | string | no default | Error text below the control. Also marks the wiring invalid. |
required | boolean | false | Draw the marker beside the label. Decorative. Set required on the control too. |
id | string | no default | Overrides the generated id, for a control the page addresses by name. |
class | string | no default | Extra classes on the field wrapper. |
FieldWiring
What the snippet is handed. Apply the first three or the message row is decoration, and apply labeledBy inside a group Field or the caption names nothing.
| Prop | Type | Default | Description |
|---|---|---|---|
id required | string | no default | The id the control must carry. The label's for already points at it. |
describedBy required | string | undefined | no default | For aria-describedby. Undefined with neither a hint nor an error, so the attribute is left off rather than naming nothing. |
invalid required | boolean | no default | True while an error is showing. For the control's aria-invalid. |
labeledBy required | string | undefined | no default | The caption's id, for a group's aria-labelledby. Set only when the Field is a group with a label. |