Skip to content
v1.4.2
Fit

Segmented Control ​

A compact single-select toggle for 2–5 mutually exclusive options (views, ranges, modes). The selected thumb slides between options. For long or many options, use Select or Radio Group instead.

bash
jlds add segmented-control

Usage ​

html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/css/segmented-control.css">
<!-- behavior layer: selection + sliding thumb -->
<script src="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/js/core.js" defer></script>
<script src="https://cdn.jsdelivr.net/gh/jarooda/jlds@main/registry/js/segmented-control.js" defer></script>

<div class="jl-segmented" role="radiogroup" aria-label="View">
  <span class="jl-segmented__thumb" aria-hidden="true"></span>
  <button type="button" role="radio" class="jl-segmented__option" data-value="list" aria-checked="true">List</button>
  <button type="button" role="radio" class="jl-segmented__option" data-value="board" aria-checked="false">Board</button>
  <button type="button" role="radio" class="jl-segmented__option" data-value="calendar" aria-checked="false">Calendar</button>
</div>
vue
<script setup lang="ts">
import { ref } from "vue"
import { SegmentedControl } from "@/components/ui/segmented-control"

const view = ref("list")
</script>

<template>
  <SegmentedControl
    v-model="view"
    :options="['List', 'Board', 'Calendar']"
    aria-label="View"
  />
</template>
tsx
import { useState } from "react"
import { SegmentedControl } from "@/components/ui/segmented-control"

const [view, setView] = useState("List")

<SegmentedControl
  value={view}
  onChange={setView}
  options={["List", "Board", "Calendar"]}
  aria-label="View"
/>

Sizes ​

sm · md (default)

html
<div class="jl-segmented jl-segmented--sm" role="radiogroup">…</div>
<div class="jl-segmented" role="radiogroup">…</div>
vue
<template>
  <SegmentedControl size="sm" :options="['Day', 'Week', 'Month']" />
  <SegmentedControl size="md" :options="['Day', 'Week', 'Month']" />
</template>
tsx
<SegmentedControl size="sm" options={["Day", "Week", "Month"]} />
<SegmentedControl size="md" options={["Day", "Week", "Month"]} />

Full width ​

Add fullWidth (React/Vue) or jl-segmented--full (HTML) to stretch and share width equally.

html
<div class="jl-segmented jl-segmented--full" role="radiogroup">…</div>
vue
<template>
  <SegmentedControl full-width :options="['Monthly', 'Yearly']" />
</template>
tsx
<SegmentedControl fullWidth options={["Monthly", "Yearly"]} />

Props ​

React ​

SegmentedControl extends React.HTMLAttributes<HTMLDivElement> (minus onChange).

PropTypeDefaultDescription
options(string | { value, label?, icon?, disabled? })[]—The options
valuestring—Controlled selected value
defaultValuestringfirst optionUncontrolled initial value
size"sm" | "md""md"Control height
fullWidthbooleanfalseStretch, options share width
onChange(value: string) => void—Fires with the new value

Vue ​

Same options. Use v-model for the value; also emits change.

CSS classes (HTML) ​

ClassPurpose
.jl-segmentedTrack — always required (role="radiogroup")
.jl-segmented--smSmaller size
.jl-segmented--fullFull width, equal-width options
.jl-segmented__thumbThe sliding indicator (positioned by the behavior layer)
.jl-segmented__optionAn option (role="radio" + aria-checked; data-value feeds the change event)