Skip to content
LyEve UI v0.60.1

Form controls

SegmentedControl

A small set of exclusive options as one joined row. It is a radio group, not a row of buttons: the whole control holds one tab stop, the arrow keys move the choice, and each segment announces as checked or not. Use it for three or four short options a user switches between often. For a longer list use RadioGroup, and for a list that does not fit on screen use Select.

Basic

Click into the row, then press the left or right arrow. The selection moves with the focus and wraps at the ends, Home and End jump to the ends, and the value below changes as it goes. That is the difference from three plain buttons: those cost three presses of Tab to step over and move nothing when an arrow is pressed.

View
Value: grid

The selected segment draws its label in the brand color on a brand-tinted fill, the way a sidebar marks the current page, and at a heavier weight, so the choice survives a palette a reader cannot separate. Every segment reserves the width of its own bold copy, so the row does not shuffle under the pointer as the selection moves along it.

Compact, with the caption dropped

labelHidden removes the caption above the row and keeps the group's accessible name, for a control whose meaning is obvious from where it sits. size="sm" is the dense row. The default md is the height of an Input, so the two line up side by side.

Alignment

Under a touch screen the small row grows to the control height, so it still lines up with an Input, and every segment takes a 44px target while its drawing stays inside the track. Each segment is at least 44px wide, so a short label such as 6h keeps a target of its own. The track draws its frame as an inset outline there instead of a border, so a row that scrolls sideways does not clip a pixel off each target, and the error color moves to the outline. A mouse sees no change.

A label never breaks inside its segment. When the options do not fit beside each other, in a narrow panel, a flex row or a grid column, the row stacks them, each segment as wide as its container, and lies back down once there is room.

Inside a form

The row is a div with role="radiogroup" and its segments are buttons, so it serializes nothing on its own. Pass name and the value reaches a form post through one hidden input rather than through the segments, which would each post their own.

Report window
Posted as range=7d

To save on change, submit from onchange. It fires before the hidden input holds the new value, so wait a tick() before requestSubmit() or the form posts the value it held before. A Select's onvaluechange already waits.

<script lang="ts">
  import { tick } from 'svelte';

  let form: HTMLFormElement;

  async function save() {
    await tick();
    form.requestSubmit();
  }
</script>

<form method="POST" action="?/range" bind:this={form}>
  <SegmentedControl label="Report window" name="range" options={ranges} bind:value={range} onchange={save} />
</form>

Hints and errors

hint puts a line of help under the row, and error replaces it while it is set. The group's aria-describedby points at whichever line shows, so a screen reader reads it when focus enters the row. An error also turns the frame red, and a row of radios then carries aria-invalid. Use a hint for what a choice costs or changes, and an error for a choice the form refused. Pick Lifetime below to see the error replace the hint.

Billing period

Charged at the start of each period.

id names the group, and the hint and the error take their ids from it. Left out, the group carries no id and the messages take a generated one. A row given none of these props looks and reads as it did before 0.38.0.

Since 0.38.0 every row takes a number from Svelte's id counter, even with none of these props set. A generated id rendered after a SegmentedControl or a FilterChips on the same page is therefore renumbered. Only a snapshot test that records those ids notices.

Inside a Field

Field draws the caption and its own message. Give it group, because a label's for never reaches a group of radios, and pass the wiring it hands over as id, describedBy and labeledBy. The group then takes its name from the Field's caption, and the Field's message is read with the row. A row inside a Field takes no hint or error of its own, because it names its message from the same id as the Field and a reader would then hear one of the two twice. Pass labelHidden too, so the caption shows once. The row's label stays required and names the group wherever no labeledBy is set.

Theme

Applies to every console you sign in to.

Kept in the URL

Give every option an href and the segments render as links instead of radios. The current segment is still the one whose value matches value, and it carries aria-current="page", so a page can keep the choice in its URL and the control works before any script has loaded. The arrow keys move focus between the segments, and only following a link changes the choice. Every option carries an href or none does, and a control that mixes the two throws. FilterChips shows a filter kept this way. A row of links takes hint and error too. ARIA refuses aria-invalid on a group of links, so there the error reaches a reader through the description alone.

API

PropTypeDefaultDescription
value requiredT no defaultBindable. One of the option values. In a row of links, the current segment's value.
options requiredSegmentOption<T>[] no defaultThe segments.
label requiredstring no defaultThe group's accessible name, unless labeledBy names a caption. Required, because an unnamed group announces as a bare set of controls.
labelHidden booleanfalseDrop the caption above the row. The group keeps its accessible name.
id string no defaultThe group's id. The hint and the error take theirs from it. One is generated for the messages when left out.
hint string no defaultShown under the row while there is no error.
error string no defaultReplaces the hint while it is set and turns the frame red. A row of radios is also marked invalid. A row of links is not, because ARIA refuses the attribute there.
describedBy string no defaultIds of other elements that describe the group, separated by spaces, such as a wrapping Field's message. Read after the row's own hint or error.
labeledBy string no defaultThe id of a caption drawn outside the row, such as a group Field's. The group takes its name from it in place of label.
name string no defaultSubmits the value with the surrounding form, through one hidden input.
size 'sm' | 'md''md''md' matches the height of a single-line control.
disabled booleanfalseDisable every segment.
class string no defaultExtra classes on the field wrapper.
onchange (value: T) => void no defaultFires only when the value actually changes, not on every press. A row of links never fires it. It fires before the hidden input is written, so to submit on change, await tick() before requestSubmit(). Select's onvaluechange already waits.
noAI booleanfalseKeep the value from an assistant: a FormAssist never reports it or fills it, and the control is marked data-no-ai for a browser agent.

SegmentOption

PropTypeDefaultDescription
value requiredT no defaultThe chosen value, and the key the segment is rendered under.
label requiredstring no defaultNames this one segment. The group is named by label.
icon Component no defaultA lucide component, drawn before the text. The control sizes it.
href string no defaultRenders the segment as a link to this URL instead of a radio. Every option carries one or none does.