How to Use Astryx SegmentedControl for Tab-Like Interfaces

The Astryx SegmentedControl component implements a radio-group interface that visually resembles a tab bar, providing accessible navigation through a container-item composition pattern with support for icons, disabled states, and responsive layouts.

The Astryx SegmentedControl component from the facebook/astryx repository offers a robust solution for creating tab-like navigation interfaces in React applications. This component implements the ARIA radio group pattern while providing visual styling that resembles traditional tab bars, making it ideal for view toggles, mode selectors, and segmented navigation patterns.

Component Architecture and Context API

The SegmentedControl system consists of three core files working together to provide state management and rendering:

  • SegmentedControl.tsx – The container component that creates the SegmentedControlContext and manages focus coordination
  • SegmentedControlItem.tsx – Individual segment buttons that consume context via useSegmentedControlContext
  • SegmentedControlContext.ts – Context definition providing value, onChange, size, layout, isDisabled, and hasDisabledMessage

In SegmentedControl.tsx, the container implements handleContainerFocus to synchronize focus changes with selection, ensuring the UI follows ARIA Authoring Practices for radio groups. The component hierarchy requires wrapping SegmentedControlItem components inside the SegmentedControl container:

<SegmentedControl value={view} onChange={setView} label="View mode">
  <SegmentedControlItem value="grid" label="Grid" />
  <SegmentedControlItem value="list" label="List" />
</SegmentedControl>

Layout and Sizing Configuration

The component supports two distinct layout modes through the layout prop defined in the container:

layout="hug" (default) – Each segment sizes to its content width, creating a compact tab bar that fits its contents.

layout="fill" – All segments share equal portions of the container width, useful for full-width toolbars. This applies the fill style from SegmentedControl.tsx to distribute space evenly.

Size variants apply to both container and items through the size prop, supporting sm, md, and lg tokens that affect padding, typography, and overall dimensions.

Accessibility and Keyboard Navigation

The implementation provides comprehensive accessibility features according to WAI-ARIA guidelines:

The container renders with role="radiogroup" and accepts an aria-label prop, while each item renders as <button role="radio"> with aria-checked attributes. When isLabelHidden is true on an item, the component automatically applies an aria-label to maintain screen reader compatibility.

Keyboard navigation leverages the useListFocus primitive for roving-tabindex behavior, supporting arrow key navigation between tabs, Home/End keys for jumping to first/last items, and a selection-follows-focus pattern that updates the controlled value as users navigate.

Usage Examples

Basic Tab-Like Control

Implement a standard three-tab view selector using controlled state:

import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core';

function ViewToggle() {
  const [view, setView] = React.useState('grid');

  return (
    <SegmentedControl
      value={view}
      onChange={setView}
      label="View mode"
    >
      <SegmentedControlItem value="grid" label="Grid" />
      <SegmentedControlItem value="list" label="List" />
      <SegmentedControlItem value="table" label="Table" />
    </SegmentedControl>
  );
}

Equal-Width Tabs with Fill Layout

Create a full-width control where tabs share equal space:

<SegmentedControl
  value={mode}
  onChange={setMode}
  label="Mode"
  layout="fill"
>
  <SegmentedControlItem value="edit" label="Edit" />
  <SegmentedControlItem value="preview" label="Preview" />
</SegmentedControl>

Icon-Based Tabs

Combine icons with labels or display icon-only tabs while preserving accessibility:

import { GridIcon, ListIcon } from './icons';

<SegmentedControl
  value={display}
  onChange={setDisplay}
  label="Display"
>
  <SegmentedControlItem
    value="grid"
    label="Grid"
    icon={<GridIcon />}
  />
  <SegmentedControlItem
    value="list"
    label="List"
    icon={<ListIcon />}
    isLabelHidden
  />
</SegmentedControl>

Disabled States

Disable the entire control with a tooltip explanation:

<SegmentedControl
  value="grid"
  onChange={setDisplay}
  label="Display"
  isDisabled
  disabledMessage="Switching display is unavailable while loading."
>
  <SegmentedControlItem value="grid" label="Grid" />
  <SegmentedControlItem value="list" label="List" />
</SegmentedControl>

Or disable individual items:

<SegmentedControl value={view} onChange={setView} label="View">
  <SegmentedControlItem value="grid" label="Grid" />
  <SegmentedControlItem value="list" label="List" isDisabled />
</SegmentedControl>

Summary

  • Astryx SegmentedControl uses a compound component pattern with SegmentedControl as the container and SegmentedControlItem as children
  • File locations: Core logic resides in SegmentedControl.tsx, SegmentedControlItem.tsx, and SegmentedControlContext.ts
  • Layout options: Choose layout="hug" for content-sized tabs or layout="fill" for equal-width distribution
  • Accessibility: Implements role="radiogroup" with roving-tabindex keyboard navigation via useListFocus
  • Styling: Built with StyleX (styles.container, styles.fill, sizeStyles) using theme tokens for colors, spacing, and border radius
  • Disabled states: Support both global disabling with tooltips (isDisabled + disabledMessage) and per-item disabling

Frequently Asked Questions

How do I implement keyboard navigation in Astryx SegmentedControl?

Keyboard navigation is automatic and requires no additional configuration. The component uses the useListFocus primitive to manage roving-tabindex behavior, allowing users to navigate between tabs using arrow keys, jump to the first tab with Home, and jump to the last tab with End. Focus changes automatically update the selected value following the selection-follows-focus pattern defined in SegmentedControl.tsx.

Can I use icons without text labels in SegmentedControl?

Yes, pass the icon prop to SegmentedControlItem along with isLabelHidden to display icon-only tabs. When isLabelHidden is true, the component automatically applies an aria-label to the button element, ensuring screen readers can still identify the tab's purpose. The icon renders before the label position when both are present.

What is the difference between hug and fill layouts?

The layout="hug" setting (default) allows each segment to size according to its content width, creating a compact tab bar. The layout="fill" setting forces all segments to share equal portions of the container width, implemented through the fill style in SegmentedControl.tsx. Use fill layouts for full-width toolbars or when you need visual balance between tabs of varying label lengths.

How does the disabled state with tooltip work?

When isDisabled is applied to the SegmentedControl container, the entire control dims visually. If you also provide a disabledMessage prop, the component uses the useTooltip hook to display an explanation when users hover over or focus the selected segment. The selected segment remains focusable while disabled to maintain keyboard navigation continuity, while unselected segments become non-interactive.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →