Skip to content
v1.0.3

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
valuestringControlled selected value
defaultValuestringfirst optionUncontrolled initial value
size"sm" | "md""md"Control height
fullWidthbooleanfalseStretch, options share width
onChange(value: string) => voidFires 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)