Skip to content

Segmented control <l-segmented-control>

A hybrid between a button group, radio buttons, and tabs: pick one of a few closely related options or views, and the selection applies immediately. A sliding pill marks the active segment.

HTML tag<l-segmented-control>
Native
Progressive
Plain
Shadow-DOM
Progressive Custom Element
html
<l-segmented-control
  label="View"
  value="list"
>
  <button value="list">List</button>
  <button value="board">Board</button>
  <button value="calendar">Calendar</button>
</l-segmented-control>

Each segment is a native <button>. Set value on the control to the value of the initially-selected segment; the control emits a change event with the new value when the selection changes.

The immediate effect is the point: act on change and switch the view or re-run the query right away. For a value the user confirms later by submitting a form, use a radio group instead — same look, native radios, so arrow-key selection, submission, reset and validation come from the platform.

Options

Label

Add label to give the control an accessible name announced by screen readers.

Code
html
<l-segmented-control
  label="View"
  value="list"
>
  <button value="list">List</button>
  <button value="board">Board</button>
  <button value="calendar">Calendar</button>
</l-segmented-control>

Sizes

Add size (sm, lg, xl) to align the control with buttons and form controls of the same size. Defaults to md.

Code
html
<div class="flex flex-col items-start gap-3">
  <l-segmented-control
    label="Size sm"
    size="sm"
  >
    <button value="day">Day</button>
    <button value="week">Week</button>
    <button value="month">Month</button>
  </l-segmented-control>
  <l-segmented-control label="Size md">
    <button value="day">Day</button>
    <button value="week">Week</button>
    <button value="month">Month</button>
  </l-segmented-control>
  <l-segmented-control
    label="Size lg"
    size="lg"
  >
    <button value="day">Day</button>
    <button value="week">Week</button>
    <button value="month">Month</button>
  </l-segmented-control>
</div>

Full width

Add full-width to stretch the segments to fill the container.

Code
html
<l-segmented-control
  label="Billing period"
  value="yearly"
  full-width
  class="w-full"
>
  <button value="monthly">Monthly</button>
  <button value="yearly">Yearly</button>
</l-segmented-control>

Examples

With icons

Place an <l-icon> before the label inside a segment. A segment with no text at all is squared automatically — give each one an aria-label.

Code
html
<l-segmented-control label="Source">
  <button value="github">
    <l-icon name="lucide:github"></l-icon>
    GitHub
  </button>
  <button value="dribbble">
    <l-icon name="lucide:dribbble"></l-icon>
    Dribbble
  </button>
  <button value="youtube">
    <l-icon name="lucide:youtube"></l-icon>
    YouTube
  </button>
</l-segmented-control>

Filter toolbar

Several controls can share one filter bar. Give each its own label so assistive tech announces which filter a segment belongs to, and keep them at the same size so the bar stays even. A segment can hold any inline content — here a colour swatch before the label.

Code
html
<div class="flex flex-wrap items-center gap-2">
  <l-segmented-control
    label="Metric"
    value="cost"
  >
    <button value="volume">Volume</button>
    <button value="cost">Cost</button>
  </l-segmented-control>
  <l-segmented-control
    label="Color"
    value="all"
  >
    <button value="all">All</button>
    <button value="color">
      <span class="size-2.5 rounded-[3px] bg-[#ef4444]"></span>
      Color
    </button>
    <button value="black">
      <span class="size-2.5 rounded-[3px] bg-[#64748b]"></span>
      Black
    </button>
  </l-segmented-control>
</div>

Accessibility

Criteria

Role

Host gets role="radiogroup"; each segment gets role="radio" with aria-checked

WCAG1.3.1
RGAA9.3
Accessible name

label sets aria-label so assistive tech announces the control purpose

WCAG4.1.2
RGAA11.1
Keyboard

Arrow keys move selection through the segments; Home/End jump to the ends

WCAG2.1.1
RGAA7.3
Focus order

Roving tabindex: the group is a single Tab stop, arrows move within it

WCAG2.4.3
RGAA12.8
Icon-only segments

A segment with no visible text needs its own aria-label

WCAG4.1.2
RGAA11.1

Rules

  • Always add a label describing the control purpose
  • Segments must be native <button> elements with a value
  • Give each icon-only segment its own aria-label

Keyboard interactions

Tab
Moves focus into the control (to the selected segment)
ArrowRight
Selects and focuses the next segment, wrapping
ArrowLeft
Selects and focuses the previous segment, wrapping
ArrowDown
Same as ArrowRight
ArrowUp
Same as ArrowLeft
Home
Selects and focuses the first segment
End
Selects and focuses the last segment

API reference

Importing

js
import 'luxen-ui/segmented-control';
css
@import 'luxen-ui/css/segmented-control';

Attributes & Properties

valuestringProperty
Value of the selected segment (its value attribute, else its index).
sizeSegmentedControlSizedefault:'md'Property
Control height, aligned with buttons and form controls at the same size.
labelstring | undefinedProperty
Accessible label announced for the group. Not displayed on screen.
full-widthbooleandefault:falseProperty
Stretch segments to fill the container width.

Events

changeEvent
Fired when the selected segment changes. Not cancelable. Bubbles. Properties: value: string, index: number.

CSS custom properties

--indicator-colordefault:var(--l-color-surface)Custom property
Background of the sliding pill behind the selected segment.
--border-radiusdefault:var(--l-radius-lg)Custom property
Corner radius of the track.