Skip to content
LyEve UI v0.60.1

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.

Window

Charts and totals cover this window.

Value: 24h

API

PropTypeDefaultDescription
children requiredSnippet<[FieldWiring]> no defaultThe control. Receives what it must carry for the message row to reach a reader.
label string no defaultLabel rendered above the control.
labelHidden booleanfalseKeep the caption for screen readers only. Without group it stays a real label with a real for.
group booleanfalseThe 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 defaultHelper text below the control. An error replaces it.
error string no defaultError text below the control. Also marks the wiring invalid.
required booleanfalseDraw the marker beside the label. Decorative. Set required on the control too.
id string no defaultOverrides the generated id, for a control the page addresses by name.
class string no defaultExtra 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.

PropTypeDefaultDescription
id requiredstring no defaultThe id the control must carry. The label's for already points at it.
describedBy requiredstring | undefined no defaultFor aria-describedby. Undefined with neither a hint nor an error, so the attribute is left off rather than naming nothing.
invalid requiredboolean no defaultTrue while an error is showing. For the control's aria-invalid.
labeledBy requiredstring | undefined no defaultThe caption's id, for a group's aria-labelledby. Set only when the Field is a group with a label.