# How to Use Astryx SegmentedControl for Tab-Like Interfaces

> Learn to use Astryx SegmentedControl for accessible tab-like interfaces. This component offers icons, disabled states, and responsive layouts for seamless navigation.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-08-06

---

**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`](https://github.com/facebook/astryx/blob/main/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:

```tsx
<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`](https://github.com/facebook/astryx/blob/main/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:

```tsx
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:

```tsx
<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:

```tsx
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:

```tsx
<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:

```tsx
<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`](https://github.com/facebook/astryx/blob/main/SegmentedControl.tsx), [`SegmentedControlItem.tsx`](https://github.com/facebook/astryx/blob/main/SegmentedControlItem.tsx), and [`SegmentedControlContext.ts`](https://github.com/facebook/astryx/blob/main/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`](https://github.com/facebook/astryx/blob/main/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`](https://github.com/facebook/astryx/blob/main/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.