# ContentToggle

> Shows one keyed child at a time and slides between them, for pagers and step content.

URL: https://apogee.avaui.dev/docs/content-toggle
Last updated: 2026-10-15

Shows one keyed child at a time and slides between them, for pagers and step content.

Last updated October 15, 2026

Uses JavaScript

A small island watches `data-active` on the root: the outgoing child slides out toward `direction`, the new one slides in from the other side, and `apogee:content-toggle-change` fires with `{ active }`.

**Without JS:** The child matching the initial active key shows (marked server-side); changing data-active has no effect.

```astro
<ContentToggle active="1">
<p data-key="1">Chapter one.</p>
<p data-key="2">Chapter two.</p>
<p data-key="3">Chapter three.</p>
</ContentToggle>
<Button onclick="…dataset.active = '2'">Next</Button>
```

## Add

```sh
npx @ava-ui/apogee add content-toggle
```

Depends on `utils` (`island`).

## Usage

```astro
---
import ContentToggle from '@/components/apogee/ContentToggle/ContentToggle.astro'
---
<ContentToggle active={step} direction="end">
  <div data-key="details">…</div>
  <div data-key="payment">…</div>
</ContentToggle>
```

Then, from your own script: `root.dataset.active = 'payment'`.

## API

`@composable` — exposed to Content Editors in AVA. Any other attribute passes through to the root `<div>`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `string` | — | `data-key` of the visible child. Change `data-active` on the root to swap. |
| `direction` | `'start' \| 'end'` | `'end'` | Side the outgoing content leaves toward; the new one enters from the opposite side. Logical, so it mirrors in RTL. |
| `class` | `string` | — | Extra classes, merged last. |

Children are any elements with a `data-key`.

## Direction

Both values:

```astro
<ContentToggle active="a" direction="start">…</ContentToggle>
<ContentToggle active="a" direction="end">…</ContentToggle>
```

## Events

The root dispatches `apogee:content-toggle-change` (`detail: { active }`, bubbling) after every swap.

## States

-   **Active** — `display: block`, slides in (`apogee-ct-in`).
-   **Leaving** — the previous child keeps rendering for one animation (`apogee-ct-out`), then hides.
-   **Reduced motion** — no slide; the swap is instant.

## Examples

Log in and sign up behind a SegmentedControl. The control sets `data-active` on change:

```astro
<div onchange="this.querySelector('.apogee-content-toggle').dataset.active = event.target.value">
<SegmentedControl name="auth" options={[{ label: 'Log in', value: 'login' }, { label: 'Sign up', value: 'signup' }]} value="login" aria-label="Account" />
<ContentToggle active="login" class="mt-4 text-sm">
  <p data-key="login">Welcome back. Log in with your work email.</p>
  <p data-key="signup">New here? Create an account in under a minute.</p>
</ContentToggle>
</div>
```

## Accessibility

-   Only the active child is in the accessibility tree (the rest are `display: none`).
-   Focus does not move on swap; move it yourself when the new content starts a task (a form step).

## Tokens

| Token | Used for |
| --- | --- |
| `--duration-medium` / `--ease-standard` | Enter and exit slides |

## Do / Don’t

-   **Do** keep children the same height where you can; the box is a grid so it takes the tallest one.
-   **Don’t** use it for tab panels; use [Tabs](https://apogee.avaui.dev/docs/tabs), which carries the ARIA pattern.

## Content

-   Each child should make sense alone; the reader never sees two at once.
-   Name the control that switches them for what the user picks (“Log in”, “Sign up”), not “Option 1”.

## Related

[

TabsSwitch between panels of related content in the same space.

](https://apogee.avaui.dev/docs/tabs)[

SegmentedControlA row of connected, mutually exclusive options backed by native radios — pick one from a small, always-visible set.

](https://apogee.avaui.dev/docs/segmented-control)[

CollapsibleA single show/hide region, built on native <details>. Zero JS.

](https://apogee.avaui.dev/docs/collapsible)
