---
title: Disclosure
description: Disclosures progressively reveal content behind a trigger, such as FAQs, filter panels, or advanced settings that most users can skip.
---

import { meta as DisclosureMeta, Accordion, Default, IconEnd } from '@/content/stories/disclosure';
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',
		],
	},
};


# Disclosure

<p class="docs-lede">Disclosures progressively reveal content behind a trigger, such as FAQs, filter panels, or advanced settings that most users can skip.</p>

## Try it

<Demo of={Default} meta={DisclosureMeta} />

## Usage

Use `RuiDisclosureTrigger` and `RuiDisclosurePanel` inside `RuiDisclosure`. Group related disclosures with `RuiDisclosureGroup` when only one should be open.

```tsx
import {
  RuiDisclosure,
  RuiDisclosureTrigger,
  RuiDisclosurePanel,
} from '@ecopages/radiant-ui/disclosure';

<RuiDisclosure>
  <RuiDisclosureTrigger>Shipping details</RuiDisclosureTrigger>
  <RuiDisclosurePanel>Delivered in 3–5 business days.</RuiDisclosurePanel>
</RuiDisclosure>
```

`RuiDisclosureTrigger` renders an indicator arrow defaulting to `RuiIconChevronDown`. It points downward when collapsed and rotates 180° when expanded.

Set `iconPosition="end"` to position the arrow at the trailing edge (default is `"start"`). Pass `iconPosition` and `icon` directly to `RuiDisclosure` when using the `trigger` prop, or configure them on `RuiDisclosureTrigger`. Pass `icon={<RuiDisclosureIcon variant="plus" />}` or `icon={null}` to customize.

## Indicator position

<p>Place the toggle arrow at the start or trailing end using `iconPosition="end"`.</p>

<Canvas of={IconEnd} meta={DisclosureMeta} />

```tsx
import { RuiDisclosure } from '@ecopages/radiant-ui/disclosure';

<RuiDisclosure trigger="Return policy" iconPosition="end">
  Items can be returned within 30 days of delivery.
</RuiDisclosure>
```

## Custom markup

`<rui-disclosure>` 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/disclosure';

<rui-disclosure>
  <div class="rui-disclosure">
    <button type="button" data-disclosure-trigger class="rui-disclosure__trigger">
      Shipping details
    </button>
    <div data-disclosure-panel class="rui-disclosure__panel">
      <div class="rui-disclosure__panel-inner">Delivered in 3–5 business days.</div>
    </div>
  </div>
</rui-disclosure>
```

BEM classes are presentation-only. Do not set `aria-expanded`, `aria-controls`, `hidden`, or `data-state` — the host owns those.

## Accordion groups

<p>Wrap multiple disclosures in `RuiDisclosureGroup` with `multiple=\{false}` for accordion behavior.</p>

<Canvas of={Accordion} meta={DisclosureMeta} />

```tsx
import '@ecopages/radiant-ui/disclosure';

<rui-disclosure-group>
  <rui-disclosure>
    <button type="button" data-disclosure-trigger>Shipping</button>
    <div data-disclosure-panel hidden>Delivered in 3–5 days.</div>
  </rui-disclosure>
</rui-disclosure-group>
```

## Animated panels

<p>Enable `animated` for height transitions. Verify content remains readable during the animation.</p>

## Theming

Disclosure surfaces map to **semantic surface roles**, never Tailwind palette steps:

| Part | CSS roles |
| --- | --- |
| Trigger (`rui-disclosure__trigger`) | `on-background`; hover / focus `surface`, `--focus-ring` |
| Panel (`rui-disclosure__panel`) | `on-surface` |
| Group (`rui-disclosure-group`) | `background`, `border`, `rounded-container`; panel header `surface` |

Override roles at the theme layer, not in component CSS.

## API

Disclosure provides two custom elements: `<rui-disclosure>` (show/hide) and `<rui-disclosure-group>` (accordion). Compose the view helpers to render triggers and panels, or mark up `data-disclosure-trigger` / `data-disclosure-panel` elements directly.

### Attributes (`<rui-disclosure>`)

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | `false` | Whether the disclosure content starts expanded. |
| `value` | `string` | `''` | Optional value used when coordinating disclosures inside a group. |
| `animated` | `boolean` | `false` | Animate panel height; also enabled inside an animated group. |

### Light-DOM contract (`<rui-disclosure>`)

| Target | Required | Host writes | Author owns |
| --- | --- | --- | --- |
| `[data-disclosure-trigger]` | yes | `aria-expanded`, `aria-controls` | the trigger element |
| `[data-disclosure-panel]` | yes | `id`, `data-state`, `hidden`, `aria-hidden` (when animated) | panel content |

Do not set `aria-expanded`, `aria-controls`, `hidden`, or `data-state`. Nested hosts: none.

### Attributes (`<rui-disclosure-group>`)

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `multiple` | `boolean` | `false` | Allow more than one disclosure open (exclusive by default). |
| `animated` | `boolean` | `false` | Animate panel height for child disclosures. |

### Light-DOM contract (`<rui-disclosure-group>`)

| Target | Required | Host writes | Author owns |
| --- | --- | --- | --- |
| `rui-disclosure` | yes | `animated` propagated | one section per child |
| `[data-disclosure-trigger]` | yes (in each child) | roving `tabIndex` across group | trigger inside each `rui-disclosure` |

Nested host: `rui-disclosure` — owns `[data-disclosure-trigger]` and `[data-disclosure-panel]`.

### Events

| Event | Detail | Description |
| --- | --- | --- |
| `rui-disclosure-toggle` | `{ value, open }` | Emitted on every trigger activation; groups listen for exclusive behavior. |

### View helpers

| Component | Target stamped | Notes |
| --- | --- | --- |
| `RuiDisclosure` | `<rui-disclosure>` + `[data-ref="root"]` shell | `trigger` prop composes trigger + panel; accepts `icon` and `iconPosition`. |
| `RuiDisclosureGroup` | `<rui-disclosure-group>` + `[data-ref="root"]` shell | Expects `rui-disclosure` children. |
| `RuiDisclosureTrigger` | `[data-disclosure-trigger]` | Includes chevron down icon by default; accepts `icon` and `iconPosition`. |
| `RuiDisclosureIcon` | — | Presentation only; renders `RuiIconChevronDown` by default. |
| `RuiDisclosurePanel` | `[data-disclosure-panel]` | Wraps content in `rui-disclosure__panel-inner`. |

### CSS classes

Public BEM classes on the composed light-DOM surface (documented via `@cssclass`):

| Class | Description |
| --- | --- |
| `.rui-disclosure` | Root wrapper around trigger and panel regions. |
| `.rui-disclosure__trigger` | Trigger button; reflects `aria-expanded`. |
| `.rui-disclosure__trigger--icon-end` | Layout when `iconPosition="end"`. |
| `.rui-disclosure__icon` | Indicator wrapper (decorative, `aria-hidden`). |
| `.rui-disclosure__icon--chevron` | Rotates 180° when open. |
| `.rui-disclosure__icon--plus` | Toggles between `+` and `×`. |
| `.rui-disclosure__label` | Trigger text. |
| `.rui-disclosure__panel` | Panel content region. |
| `.rui-disclosure__panel-inner` | Panel padding wrapper (drives the height animation). |
| `.rui-disclosure-group` | Group surface (bordered card around stacked disclosures). |

### Theme roles

| Part | CSS variables consumed |
| --- | --- |
| Trigger | `--on-background`, `--surface` (hover), `--focus-ring`, `--space-control-*` |
| Panel | `--on-surface` |
| Group | `--background`, `--border`, `--surface` |

## Accessibility

- Triggers expose `aria-expanded` reflecting panel visibility.
- Panel content is associated with its trigger via `aria-controls`.
- Keyboard users activate disclosures with Enter or Space on the trigger.
