Skip to Content
Hashti Design System
Components

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.

Content SwitcherBinary or ternary view toggles with pill geometry.

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

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Height and padding density applied to all contained toggle buttons.
selectedKeysIterable<Key>—The keys of the currently active toggle button (controlled).
defaultSelectedKeysIterable<Key>—Initial selected key when uncontrolled.
onSelectionChange(keys: Set<Key>) => void—Callback fired whenever the user selects a segment.
aria-labelstring—Recommended: Accessible name describing the toggle group to screen readers.
childrenReactNodeRequiredOne or more <ToggleButton> components.
classNamestring—Additional CSS class names.

ToggleButton Props

PropTypeDefaultDescription
idKeyRequiredUnique identifier for this button within the switcher.
childrenReactNodeRequiredText label, icon, or content rendered inside the segment.
isDisabledbooleanfalseWhether this individual segment is disabled.

Accessibility (a11y)

  • WAI-ARIA Pattern: Built on React Aria Components’ ToggleButtonGroup with selectionMode="single" and disallowEmptySelection.
  • Selection State: Active toggle buttons expose aria-pressed="true", while unselected options expose aria-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 (or aria-labelledby) on the <ContentSwitcher> so assistive technology users understand the grouping’s purpose.

Keyboard Interactions

KeyAction
→ / ←Moves selection across switcher segments.
Space / EnterActivates focused segment.

WAI-ARIA & Data Attributes

AttributeTargetDescription
[data-selected]Switcher ButtonActive on selected view.
[data-focused]Switcher ButtonPresent on keyboard focus.