<!-- Generated by scripts/build-agent-kit.ts for @aistrike-dev/ui@5.0.1. Do not edit. -->

# ToggleButtonGroup vs Tabs vs individual Buttons

<!-- use-when: A row of side-by-side labels: is it a filter, a view mode, a set of tabs, or actions? -->

These three controls look similar in a mockup — a row of clickable, side-by-side labels — but they mean
different things to a user. Picking the wrong one is one of the most common sources of confusing UI,
because the visual pattern promises a behaviour the control does not deliver.

The distinction comes down to one question: **what happens when the user clicks?**

| Control | The click means | Selection | What changes |
| --- | --- | --- | --- |
| `ToggleButtonGroup` | "Show me this differently" | Single (`exclusive`) or multiple | State of the **current** view — density, mode, format, filter |
| `Tabs` | "Show me a different part of this" | Single, always | Which **content panel** renders in the region below |
| `Button` (individual) | "Do this" | None — buttons hold no selected state | Something happens: data is saved, a dialog opens, a job runs |

A `Button` is a **verb**. `Tabs` are a **place**. A `ToggleButtonGroup` is an **adjective** applied to what
is already on screen.

## Decision flow

```
Does clicking it perform an action or cause a side effect
(save, delete, run scan, open dialog, navigate away)?
        │
        ├── Yes ──────────────────────────────► Button
        │                                       (ButtonGroup if the actions are a related cluster)
        └── No — it selects something
                │
                ├── Does the selection swap in a different set of content,
                │   as a persistent structure of the screen?
                │        │
                │        ├── Yes ─────────────► Tabs
                │        └── No
                │
                └── Does it change how the existing content is
                    displayed, sorted, filtered, or formatted?
                             │
                             ├── One choice at a time ──► ToggleButtonGroup exclusive
                             └── Several at once ───────► ToggleButtonGroup (non-exclusive)
```

## ToggleButtonGroup

Use a `ToggleButtonGroup` for a **compact, always-visible set of options that reconfigure the content
already on screen**. The content region does not change identity — it re-renders with a different
presentation.

**Reach for it when:**

- Switching a view mode: table vs card, list vs grid, graph vs raw JSON.
- Changing a time window on a chart: 24h / 7d / 30d.
- Setting severity or status filters on a findings list.
- Toggling display options: wrap lines, show resolved, group by asset.
- Formatting toolbars, where several options can be active simultaneously.

Single-select uses `exclusive` with a scalar `value`:

```tsx
import { ToggleButton, ToggleButtonGroup } from '@aistrike-dev/ui';
import ViewListIcon from '@mui/icons-material/ViewList';
import ViewModuleIcon from '@mui/icons-material/ViewModule';

<ToggleButtonGroup
  exclusive
  value={view}
  onChange={(_, next) => next && setView(next)}
  aria-label="view mode"
  size="small"
>
  <ToggleButton value="list" aria-label="list view">
    <ViewListIcon />
  </ToggleButton>
  <ToggleButton value="grid" aria-label="grid view">
    <ViewModuleIcon />
  </ToggleButton>
</ToggleButtonGroup>
```

Multi-select omits `exclusive`; `value` becomes an array:

```tsx
<ToggleButtonGroup value={severities} onChange={(_, next) => setSeverities(next)} aria-label="severity filter">
  <ToggleButton value="critical">Critical</ToggleButton>
  <ToggleButton value="high">High</ToggleButton>
  <ToggleButton value="medium">Medium</ToggleButton>
</ToggleButtonGroup>
```

**Practical limits:** keep it to 2–5 options with short labels or recognisable icons. The group is
rendered inline, so it cannot wrap gracefully or scroll — past five options, or with labels longer than
a word or two, use a `Select` (single) or a `FilterPanel` / `Autocomplete` (many). With `exclusive`,
handle the `null` value that MUI emits when the user deselects the active option, otherwise the group can
end up with nothing selected.

## Tabs

Use `Tabs` when a screen has **several peer sections of content, and only one is relevant at a time**.
Tabs are part of a screen's structure: they persist while the user works, and each tab is a destination
the user can be pointed to ("check the Findings tab").

**Reach for it when:**

- An asset detail page splits into Overview / Findings / Attack Paths / Activity.
- A settings page groups unrelated forms: Profile / Notifications / Integrations.
- A dialog or drawer holds two distinct bodies of content that would be too long stacked.

```tsx
import { Tabs, Tab } from '@aistrike-dev/ui';
import { Box } from '@mui/material';

<Box>
  <Tabs value={tab} onChange={(_, next) => setTab(next)} aria-label="asset detail tabs">
    <Tab label="Overview" />
    <Tab label="Findings" />
    <Tab label="Attack Paths" />
  </Tabs>
  <Box sx={{ pt: 2 }}>{panels[tab]}</Box>
</Box>
```

The design system ships the tab strip only — **the panel below it is owned by the app**. Render the
active panel yourself, keyed off `value`, and give it `role="tabpanel"` when the content is substantial.

**Practical limits:** tab labels are nouns, not actions ("Findings", not "View findings"). For more tabs
than fit the container use `variant="scrollable"` with `scrollButtons="auto"`; for a narrow sidebar use
`orientation="vertical"`. Tabs are for peer content within one screen — not for primary app navigation
(use `LeftNavigation`) and not for sequential steps (use `Stepper`).

## Individual Buttons

Use `Button` when each control **does something**. Buttons carry no selected state: after the click, the
button looks exactly as it did before, and something else in the system has changed.

**Reach for it when:**

- Committing or discarding work: Save changes, Cancel.
- Opening a dialog, drawer, or export flow.
- Running an operation: Rescan asset, Isolate host, Re-run query.
- Destructive operations, using `variant="destructive"` plus a confirmation step.

Buttons come with an emphasis ladder — `primary` (at most one per view), `secondary` (the default),
`ghost` (tertiary), `destructive`. The component reference entry for `Button` carries the full rules.

```tsx
import { Button } from '@aistrike-dev/ui';
import { Stack } from '@mui/material';

<Stack direction="row" spacing={1}>
  <Button variant="ghost" onClick={cancel}>Cancel</Button>
  <Button variant="primary" onClick={save}>Save changes</Button>
</Stack>
```

When several **actions** belong together visually — an export split into formats, a paging cluster —
wrap them in a `ButtonGroup`. A `ButtonGroup` still holds actions, not a selection: if one of the
segments should stay visibly active afterwards, you wanted a `ToggleButtonGroup`.

## Worked examples

| Scenario | Control | Why |
| --- | --- | --- |
| Findings list can render as a table or as cards | `ToggleButtonGroup exclusive` | Same data, different presentation |
| Chart range: 24h / 7d / 30d | `ToggleButtonGroup exclusive` | Reconfigures the chart in place |
| Filter findings by any combination of severities | `ToggleButtonGroup` (non-exclusive) | Multiple options active at once |
| Asset page: Overview / Findings / Attack Paths | `Tabs` | Distinct peer content panels |
| Settings: Profile / Notifications / Integrations | `Tabs` | Unrelated sections of one screen |
| Onboarding: Connect → Configure → Review | `Stepper` | Sequential, order matters |
| App-level areas: Dashboard / Assets / Findings | `LeftNavigation` | Primary navigation, not a screen's internals |
| Export as CSV / JSON / PDF | `ButtonGroup` of `Button`s (or a `Menu`) | Each option performs an action |
| Save / Cancel on a form | Individual `Button`s | Actions with an emphasis hierarchy |
| Enable a single setting | `Switch` | Binary on/off, not a set of choices |
| Pick one of eight regions | `Select` | Too many options for a segmented control |

## Anti-patterns

- **Tabs used as a filter.** If the "tabs" only narrow the same list (All / Open / Resolved) and the
  columns never change, that is a filter — use a `ToggleButtonGroup` and keep the table identity stable.
- **A ToggleButtonGroup that swaps entire panels.** Once each option brings in a different layout with
  its own header and controls, users expect tabs. Segmented controls signal a lightweight change.
- **Buttons that hold selected state.** Manually restyling a `Button` to look active is re-implementing
  `ToggleButtonGroup` without its keyboard behaviour or ARIA state. Never do this.
- **A `ButtonGroup` for mutually exclusive selection.** It has no notion of a selected value; use
  `ToggleButtonGroup`.
- **Tabs for wizard steps.** Tabs imply free movement between peers; a sequence with prerequisites is a
  `Stepper`.
- **Tabs as primary navigation.** Top-level app areas belong in `LeftNavigation`; tabs live inside a
  screen.
- **A single lone `ToggleButton`.** For one binary option, `Switch` (or `Checkbox` in a form) reads more
  clearly.
- **Mixing actions and selections in one row.** A row that contains both "Grid" and "Delete" makes both
  ambiguous. Separate them visually.

## Accessibility

- `Tabs` are keyboard-navigable with arrow keys and expose `role="tab"`. Give the strip an `aria-label`,
  and pair substantial panel content with `role="tabpanel"` so screen readers announce the relationship.
- `ToggleButtonGroup` communicates state via `aria-pressed`. Give the group an `aria-label` describing
  what is being chosen ("view mode", "severity filter"), and every icon-only `ToggleButton` its own
  `aria-label`.
- Never rely on colour alone to show which option is active — the selected state must also read as text,
  weight, or an icon change.
- Icon-only controls of any kind need an accessible label and, ideally, a `Tooltip`.

## When you are unsure, ask

These boundaries are genuinely fuzzy in real designs: a filter can look like a tab, and a view switch can
grow into a panel swap. **If you cannot confidently pick between a `ToggleButtonGroup`, `Tabs`, and
individual `Button`s for a given piece of UI, stop and ask the end user before building it — and state
the decision you are weighing, along with the option you are leaning toward and why.**

Ask in concrete terms, not in the abstract:

> "The findings view can show a table or cards. I'm leaning toward a `ToggleButtonGroup` since it's the
> same data in a different layout, rather than `Tabs` — but if each view gets its own filters and header,
> `Tabs` would fit better. Which behaviour do you want?"

> "This row is All / Open / Resolved. I'm treating it as a `ToggleButtonGroup` filter over one table. Did
> you intend these as separate `Tabs` with their own columns?"

A single clarifying question is far cheaper than shipping a control whose behaviour surprises the user.
When in doubt, ask.

## References

- **Atoms → ToggleButton** — segmented single- and multi-select stories.
- **Molecules → Tabs** — standard, fullWidth, scrollable, and vertical stories.
- **Atoms → Button** — the variant emphasis ladder.
- **Molecules → ButtonGroup**, **Organisms → Stepper**, **Organisms → LeftNavigation** — the neighbouring
  controls referenced above.
- [MUI ToggleButton](https://mui.com/material-ui/react-toggle-button/) · [MUI Tabs](https://mui.com/material-ui/react-tabs/) · [MUI Button](https://mui.com/material-ui/react-button/)
