Skip to content

Segmented control <l-segmented-control>

A single-select switch between a few mutually-exclusive options, with a sliding pill behind the selected 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.

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>

Form field

It is form-associated: add name and the selected value is submitted with the form, like a native radio group. Reset restores the initial selection, and disabled opts the control out of submission.

Code
html
<form class="flex flex-col items-start gap-3">
  <l-segmented-control
    name="billing"
    label="Billing period"
    value="yearly"
  >
    <button value="monthly">Monthly</button>
    <button value="yearly">Yearly</button>
  </l-segmented-control>
  <button
    class="l-button"
    type="reset"
  >
    Reset
  </button>
</form>

Examples

With icons

Place an <l-icon> before the label inside a segment.

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>

Icon-only

A segment with no text is squared automatically. Give each one an aria-label.

Code
html
<l-segmented-control label="Text alignment">
  <button
    value="left"
    aria-label="Align left"
  >
    <l-icon name="lucide:align-left"></l-icon>
  </button>
  <button
    value="center"
    aria-label="Align center"
  >
    <l-icon name="lucide:align-center"></l-icon>
  </button>
  <button
    value="right"
    aria-label="Align right"
  >
    <l-icon name="lucide:align-right"></l-icon>
  </button>
</l-segmented-control>

Filter toolbar

Segmented controls line up with .l-button triggers and l-select at the same size, so a filter bar stays visually consistent. Here the dropdowns take a quiet neutral fill (bg-fill-neutral-subtle) so the chrome recedes and the active selections carry the emphasis.

Code
html
<div class="flex flex-wrap items-center gap-2">
  <button class="l-button bg-fill-neutral-subtle">
    <l-icon name="lucide:layout-list"></l-icon>
    List view
    <l-icon name="lucide:chevron-down"></l-icon>
  </button>
  <button class="l-button bg-fill-neutral-subtle">
    <l-icon name="lucide:arrow-down-narrow-wide"></l-icon>
    Sort
    <l-icon name="lucide:chevron-down"></l-icon>
  </button>
  <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

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.