---
title: Cycle Toggle
description: Cycle through exclusive values with a single button press.
category: Actions
---
import { meta as CycleToggleMeta, Default, SortOrder } from '@/content/stories/cycle-toggle';
import Canvas from '@/components/component-docs/canvas';
import Demo from '@/components/component-docs/demo';
export const config = {
dependencies: {
components: [Canvas, Demo],
scripts: [
'../../components/component-docs/demo.script.tsx',
'../../components/component-docs/canvas.script.tsx',
'../../components/component-docs/controls.script.tsx',
],
},
};
# Cycle Toggle
Cycle toggles advance through a fixed set of values on each click, such as theme preference, sort order, or any small exclusive choice.
## Try it
## Usage
Compose `RuiCycleToggleItem` children inside `RuiCycleToggle`. Only the active item is visible in the button. Listen for `rui-change` to react to the new `detail.value`.
```tsx
import { RuiCycleToggle, RuiCycleToggleItem } from '@ecopages/radiant-ui/cycle-toggle';
System
Light
Dark
```
## Custom markup
`` coordinates any light-DOM tree that matches its query contract. The `Rui*` helpers stamp these targets; they are not required.
```tsx
import '@ecopages/radiant-ui/cycle-toggle';
```
BEM classes are presentation-only. Do not set `hidden` on items — the host owns visibility.
## Sort order
## Theming
The cycle button reuses the **`rui-button` action roles**, never Tailwind palette steps. `variant` / `size` map straight to the button system:
| Part | CSS roles |
| --- | --- |
| Button (`.rui-cycle-toggle__button`) | `rui-button` chrome, `primary` filled, `on-surface` ghost, `error` destructive |
| Item (`.rui-cycle-toggle__item`) | `space-inline` gap |
Button geometry (`--size-control-*`, `--radius-control`) is inherited from `rui-button` so the cycle control aligns with form rows. Override at the theme layer, not in component CSS.
## API
`RuiCycleToggle` is a custom element (``); the view composes the inner `rui-button`.
### Attributes
| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | `''` | Active item id. |
| `variant` | `filled` · `outline` · `destructive` · `ghost` · `link` | `filled` | Button variant passed to the inner button. |
| `size` | `none` · `sm` · `md` · `lg` | `md` | Button size passed to the inner button. |
| `label` | `string` | `''` | Accessible name prefix for the cycle button. |
| `disabled` | `boolean` | `false` | Disables the cycle button. |
### Light-DOM contract
| Target | Required | Host writes | Author owns |
| --- | --- | --- | --- |
| `button[data-cycle-toggle-button]` | yes | `aria-label`, `disabled`, `rui-button--*` classes | the button element |
| `[data-cycle-value]` | yes (per item) | `hidden` | `data-cycle-value`, `aria-disabled` |
Do not set `hidden` on items. Nested hosts: none.
### Events
| Event | Detail | Description |
| --- | --- | --- |
| `rui-change` | `{ value: string }` | Emitted after `value` advances; `detail.value` is the new id. |
### Methods
| Method | Description |
| --- | --- |
| `resync()` | Re-apply button labeling and visible item from the current `value`. |
### View helpers
| Component | Target stamped | Notes |
| --- | --- | --- |
| `RuiCycleToggle` | `` + `RuiCycleToggleButton` | Wraps items in the button. |
| `RuiCycleToggleItem` | `[data-cycle-value]` | Pass `id`; maps to `data-cycle-value`. |
| `RuiCycleToggleButton` | `button[data-cycle-toggle-button]` | Composes `rui-button` variant/size classes. |
### CSS classes
Public BEM classes (documented via `@cssclass`):
| Class | Description |
| --- | --- |
| `.rui-cycle-toggle__button` | Cycle toggle button root (adds `rui-button rui-button--*`). |
| `.rui-cycle-toggle__item` | Cycle option row (icon + label). |
### Theme roles
| Part | CSS variables consumed |
| --- | --- |
| Button chrome | `--primary`, `--on-primary`, `--size-control-*`, `--radius-control` (via `rui-button`) |
| Item | `--space-inline` |
## Accessibility
- The host `label` sets `aria-label` on the cycle button.
- Item children can include visible text, icons, or screen-reader-only labels.
- Space and Enter activate the native button.