Content Switcher
A short row of two to four options that switch the view below it. Three sizes, an on-colour version, and one option always chosen.
Version 0.4.1 ·
Installation
bunx --bun shadcn@latest add @dga/content-switcherUsage
import { useState } from "react";
import { ContentSwitcher, ContentSwitcherItem } from "@/components/ui/content-switcher";
export function PeriodSwitcher() {
const [period, setPeriod] = useState("week");
return (
<ContentSwitcher value={period} onValueChange={setPeriod} aria-label="Period">
<ContentSwitcherItem value="day">Day</ContentSwitcherItem>
<ContentSwitcherItem value="week">Week</ContentSwitcherItem>
<ContentSwitcherItem value="month">Month</ContentSwitcherItem>
</ContentSwitcher>
);
}Use a Content Switcher to change how one set of content is shown: a day, week or month view; a list or a map. All options show the same kind of thing. For separate sections of content use Tabs; for a choice that is saved in a form use Radio. Keep two to four short labels. Name the group with aria-label, and put the content it controls right under it.
Anatomy
- Switcher (
role="group"): the track, neutral-100 with a 1px border. 32, 40 or 48px high. - Option (a toggle button with
aria-pressed): a label and an optional icon. The chosen option has the primary fill and a white label.
Examples
Sizes
size is sm, md (default) or lg. The three switchers share one value here.
On colour
onColor is for a brand fill: a translucent black track and a white chosen option.
Disabled, icons and controlled
A disabled option stays visible and cannot be chosen. Put an icon first in an option when it helps, and pass value with onValueChange to own the selection.
RTL
The first option is on the right and the Left and Right arrows swap. The switcher is as wide as its options; give it className="w-full" to fill a row.
Accessibility
| Key | Action |
|---|---|
| Tab | Moves focus into the switcher (to the chosen option), then out of it. |
| ArrowRight | Moves focus to the next option (the previous one in Arabic). |
| ArrowLeft | Moves focus to the previous option (the next one in Arabic). |
| Enter | Chooses the focused option. |
| Space | Chooses the focused option. |
- Built on Base UI Toggle Group: a
groupof buttons witharia-pressed, one of them in the tab order (roving focus). Choosing the pressed option again does nothing, so one option is always chosen. - The chosen option is shown by the fill and by
aria-pressed. The label is 4.5:1 on the track, the chosen fill 3:1 against it, in light and dark. Focus is a 2px outline inside the option. - If the content below changes, announce it: put it in a region with
aria-live="polite"or move focus to it.
API Reference
ContentSwitcher
A short row of two to four options that switch the view below it (DGA: Content Switcher). One option is always chosen. One option is in the tab order; the arrow keys move between options (Left and Right swap on Arabic pages). Name it with `aria-label`.
| Prop | Type | Default |
|---|---|---|
"sm" | "md" | "lg"Height: `sm` 32px, `md` 40px, `lg` 48px. | "md" | |
| ||
booleanFor brand fills (`background-primary`, the nav header). | false | |
| ||
stringThe chosen option (controlled). | No default | |
| ||
stringThe option chosen at first (uncontrolled). The first option if you leave it out. | No default | |
| ||
(value: string) => voidCalled with the new value. One option is always chosen, so it is never empty. | No default | |
| ||
Also accepts every prop of the Base UI Toggle (opens in a new tab).
ContentSwitcherItem
One option. Its `value` is what ContentSwitcher reports. Put an icon first if you need one.
| Prop | Type | Default |
|---|---|---|
stringA unique string that identifies the toggle when used inside a toggle group. What this option is called in code. | No default | |
| ||
booleanWhether the component should ignore user interaction. | No default | |
| ||
Also accepts every prop of the Base UI Toggle (opens in a new tab).
Deviations from DGA
- Figma has two or three items with a white label and an OnColor variant; the CSS only sets
margin: 10px. The track and the chosen fill are designed here from the button tokens. - On a brand fill the track is a translucent black, not white: white lightens the fill and drops the white label to 3.34:1 (2.81:1 pressed).
| Change | Reason |
|---|---|
Content Switcher: design and on-colour | White on a white-lightened brand fill is 3.34:1 (down to 2.81:1 pressed); a darkened track keeps 4.5:1 or better, as the on-colour buttons do. |
Contrast
40 pairs checked · all pass WCAG 2.1 AAShow 14 statesHide
| Variant / state | Light | Dark |
|---|---|---|
| option text / default | 16.44:1 AAA | 14.42:1 AAA |
| focus ring / default | 16.44:1 3:1 | 15.89:1 3:1 |
| option text / hovered | 16.44:1 AAA | 9.59:1 AAA |
| focus ring / hovered | 16.44:1 3:1 | 10.57:1 3:1 |
| option text / pressed | 14.61:1 AAA | 6.95:1 AA |
| focus ring / pressed | 14.61:1 3:1 | 7.66:1 3:1 |
| chosen text | 4.74:1 AA | 4.74:1 AA |
| chosen fill | 4.31:1 3:1 | 3.34:1 3:1 |
| on-colour / option text | 6.30:1 AA | 6.30:1 AA |
| on-colour / option text, hovered | 7.31:1 AAA | 7.31:1 AAA |
| on-colour / option text, pressed | 8.46:1 AAA | 8.46:1 AAA |
| on-colour / chosen text | 6.60:1 AA | 6.60:1 AA |
| on-colour / chosen fill | 4.74:1 3:1 | 4.74:1 3:1 |
| on-colour / focus ring | 4.74:1 3:1 | 4.74:1 3:1 |
Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report