Content Switcher
Segmented button control enabling instant toggling between alternative views or data modes.
Content Switcher
The Content Switcher (also known as a segmented button or pill toggle group) lets users rapidly toggle between mutually exclusive views, filtering modes, or display formats—such as switching between “Grid” and “List” layouts, or “Monthly” and “Annual” billing plans.
Unlike standard buttons, all segments are joined in a single cohesive container where exactly one option remains selected at all times.
Import
import { ContentSwitcher, ToggleButton, Icon } from "@hashti/react";
Examples
Basic Content Switcher
Construct a content switcher by wrapping two or more <ToggleButton> components inside <ContentSwitcher>:
import { useState } from "react";
import { ContentSwitcher, ToggleButton } from "@hashti/react";
import type { Key } from "@hashti/react";
export function BasicContentSwitcherExample() {
const [view, setView] = useState<Key>("grid");
return (
<div className="space-y-4">
<ContentSwitcher
selectedKeys={[view]}
onSelectionChange={(keys) => {
const [first] = Array.from(keys);
if (first) setView(first);
}}
aria-label="View presentation style"
>
<ToggleButton id="grid">Grid View</ToggleButton>
<ToggleButton id="table">Table View</ToggleButton>
<ToggleButton id="compact">Compact View</ToggleButton>
</ContentSwitcher>
<div className="p-4 border border-subtle rounded bg-layer-1 text-sm">
Active View: <strong>{view}</strong>
</div>
</div>
);
}
With Icons & Labels
Combine icons with descriptive labels to provide instant visual affordances:
import { useState } from "react";
import { ContentSwitcher, ToggleButton, Icon } from "@hashti/react";
export function IconContentSwitcherExample() {
const [mode, setMode] = useState<string>("code");
return (
<ContentSwitcher
selectedKeys={[mode]}
onSelectionChange={(keys) => {
const [first] = Array.from(keys);
if (first) setMode(String(first));
}}
aria-label="Editor display mode"
>
<ToggleButton id="code">
<Icon name="code" size={16} />
<span>Source Code</span>
</ToggleButton>
<ToggleButton id="preview">
<Icon name="view" size={16} />
<span>Live Preview</span>
</ToggleButton>
<ToggleButton id="split">
<Icon name="splitScreen" size={16} />
<span>Split Screen</span>
</ToggleButton>
</ContentSwitcher>
);
}
Sizes
Hashti offers three density sizes (sm, md, lg) to match adjacent input heights and dense toolbars:
import { ContentSwitcher, ToggleButton } from "@hashti/react";
export function ContentSwitcherSizesExample() {
return (
<div className="space-y-4">
{/* Small */}
<ContentSwitcher size="sm" defaultSelectedKeys={["day"]} aria-label="Timeframe small">
<ToggleButton id="day">Day</ToggleButton>
<ToggleButton id="week">Week</ToggleButton>
<ToggleButton id="month">Month</ToggleButton>
</ContentSwitcher>
{/* Medium (default) */}
<ContentSwitcher size="md" defaultSelectedKeys={["day"]} aria-label="Timeframe medium">
<ToggleButton id="day">Day</ToggleButton>
<ToggleButton id="week">Week</ToggleButton>
<ToggleButton id="month">Month</ToggleButton>
</ContentSwitcher>
{/* Large */}
<ContentSwitcher size="lg" defaultSelectedKeys={["day"]} aria-label="Timeframe large">
<ToggleButton id="day">Day</ToggleButton>
<ToggleButton id="week">Week</ToggleButton>
<ToggleButton id="month">Month</ToggleButton>
</ContentSwitcher>
</div>
);
}
Binary Toggle Switcher
Ideal for two-option toggles such as pricing tiers or light/dark themes:
import { useState } from "react";
import { ContentSwitcher, ToggleButton } from "@hashti/react";
export function BinarySwitcherExample() {
const [billing, setBilling] = useState<string>("annual");
return (
<div className="flex flex-col items-center gap-3">
<ContentSwitcher
selectedKeys={[billing]}
onSelectionChange={(keys) => {
const [first] = Array.from(keys);
if (first) setBilling(String(first));
}}
aria-label="Billing cycle"
>
<ToggleButton id="monthly">Monthly Billing</ToggleButton>
<ToggleButton id="annual">Annual Billing (Save 20%)</ToggleButton>
</ContentSwitcher>
<p className="text-xs text-subtle">
{billing === "annual" ? "$80 / year (billed annually)" : "$10 / month"}
</p>
</div>
);
}
Props Reference
ContentSwitcher Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Height and padding density applied to all contained toggle buttons. |
selectedKeys | Iterable<Key> | — | The keys of the currently active toggle button (controlled). |
defaultSelectedKeys | Iterable<Key> | — | Initial selected key when uncontrolled. |
onSelectionChange | (keys: Set<Key>) => void | — | Callback fired whenever the user selects a segment. |
aria-label | string | — | Recommended: Accessible name describing the toggle group to screen readers. |
children | ReactNode | Required | One or more <ToggleButton> components. |
className | string | — | Additional CSS class names. |
ToggleButton Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | Key | Required | Unique identifier for this button within the switcher. |
children | ReactNode | Required | Text label, icon, or content rendered inside the segment. |
isDisabled | boolean | false | Whether this individual segment is disabled. |
Accessibility (a11y)
- WAI-ARIA Pattern: Built on React Aria Components’
ToggleButtonGroupwithselectionMode="single"anddisallowEmptySelection. - Selection State: Active toggle buttons expose
aria-pressed="true", while unselected options exposearia-pressed="false". - Keyboard Navigation:
- Use ArrowLeft and ArrowRight to move focus between segments.
- Press Space or Enter to select the focused option.
- Focus order respects RTL layouts automatically.
- Group Labelling: Always supply an
aria-label(oraria-labelledby) on the<ContentSwitcher>so assistive technology users understand the grouping’s purpose.
Keyboard Interactions
| Key | Action |
|---|---|
| → / ← | Moves selection across switcher segments. |
| Space / Enter | Activates focused segment. |
WAI-ARIA & Data Attributes
| Attribute | Target | Description |
|---|---|---|
[data-selected] | Switcher Button | Active on selected view. |
[data-focused] | Switcher Button | Present on keyboard focus. |