Components · Data Entry
Segmented
Switch between a few mutually-exclusive options inline — labels, leading icons, or icon-only, in three sizes.
Examples
Size
Content
Block
Segmented · leading icons · a disabled option · size medium (default)
Interactive · click to switch
Icon-only (aria-label) · block · whole-control disabled
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-segmented> is the SAME shared custom element React and Vue consume.
`options` is pipe-separated labels OR a JSON array of {value,label?,icon?,disabled?,ariaLabel?};
`size` is small|medium|large, `block` stretches full-width. Emits a composed `change` event. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js'; // <aha-icon> (leading/icon-only glyphs)
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-segmented.js'; // registers <aha-segmented>
</script>
<!-- Simple pipe-string form -->
<aha-segmented id="range" options="Day|Week|Month" value="Week"></aha-segmented>
<!-- Rich form — leading icons, a disabled option, large size -->
<aha-segmented size="large" value="week"
options='[
{"value":"day","label":"Day","icon":"system-calendar-dot"},
{"value":"week","label":"Week","icon":"system-calendar-dots"},
{"value":"month","label":"Month","icon":"system-table"},
{"value":"year","label":"Year","disabled":true}
]'></aha-segmented>
<script>
// Plain DOM — no framework. Read e.detail.value off the composed CustomEvent.
document.getElementById('range').addEventListener('change', (e) => {
console.log('range:', e.detail.value);
});
</script>
import '@ahaslides-product/design/aha-segmented'; // registers <aha-segmented>
import '@ahaslides-product/design/icons'; // registers <aha-icon> (leading/icon-only glyphs)
import { useRef, useEffect } from 'react';
// React 18 needs a thin wrapper; React 19 can use <aha-segmented> directly.
// `options` is a pipe-string OR a JSON array of {value,label?,icon?,disabled?,ariaLabel?}.
function AhaSegmented({ options, value, size, block, disabled, onChange }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
el.value = value;
const h = (e) => onChange?.(e.detail.value); // composed CustomEvent
el.addEventListener('change', h);
return () => el.removeEventListener('change', h);
});
const opts = Array.isArray(options) && typeof options[0] === 'object'
? JSON.stringify(options) // rich items → JSON string attribute
: [].concat(options).join('|'); // labels → pipe string
return <aha-segmented ref={ref} options={opts} size={size} block={block || undefined} disabled={disabled || undefined} />;
}
// usage — rich items with leading icons
<AhaSegmented
size="large"
value={range}
onChange={setRange}
options={[
{ value: 'day', label: 'Day', icon: 'system-calendar-dot' },
{ value: 'week', label: 'Week', icon: 'system-calendar-dots' },
{ value: 'month', label: 'Month', icon: 'system-table' },
{ value: 'year', label: 'Year', disabled: true },
]}
/>
// main.ts — register the elements + mark aha-* as custom elements
import '@ahaslides-product/design/aha-segmented';
import '@ahaslides-product/design/icons'; // <aha-icon> for leading/icon-only glyphs
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue
<script setup>
import { ref } from 'vue';
const range = ref('week');
// `options` is a pipe-string OR a JSON array of {value,label?,icon?,disabled?,ariaLabel?}
const options = JSON.stringify([
{ value: 'day', label: 'Day', icon: 'system-calendar-dot' },
{ value: 'week', label: 'Week', icon: 'system-calendar-dots' },
{ value: 'month', label: 'Month', icon: 'system-table' },
{ value: 'year', label: 'Year', disabled: true },
]);
</script>
<template>
<aha-segmented
:options="options"
size="large"
:value.prop="range"
@change="range = $event.detail.value"
/>
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
options | string | JSON array | — | Pipe labels "Day|Week|Month" OR items [{value,label?,icon?,disabled?,ariaLabel?}] |
value | string | first option | Selected option value |
size | 'small' | 'medium' | 'large' | medium | Height/padding/font/radius scale |
block | boolean | false | Full-width; options flex-equal |
disabled | boolean | false | Dims and disables the whole control |
change | CustomEvent<{value,index}> | — | Composed event; read e.detail.value |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-segmented'; // registers <aha-segmented>
Agent feed for this component (absolute, fetchable anywhere): segmented.agent.json · segmented.md · segmented.llms.txt
When to use
When to use
- Segmented — 2–4 short, mutually-exclusive views or modes that switch in place
- Tabs — navigating between larger content panels
- Select — one option from a longer list that needn't be all visible
Keep labels to one word where possible and the option count small (2–4). Icon-only options MUST carry an aria-label. It is a single-select control — for a magnitude use Rate or a Slider.
Surfaces
editor dashboard settings
Spec
Track gray-20 #F7F7F7, radius 8 (sm 6 · lg 12), pad 2 · Thumb bg-container #FFFFFF, radius 6, slides on select · Segment medium h32 pad 0 16 · small h24 12/12px · large h40 20/16px, SemiBold 600 · Content label · leading icon (16) + label · icon-only (aria-label required) · Selected text-default #1A1A1A over the thumb · Disabled per-option or whole control → text-disabled, not-allowed · Block full-width track, options flex-equal