Skip to content

ComponentsNavigation

Disclosure

Collapsible sections of content, on their own or grouped as an accordion. Three group styles (line, card, separated), chevron or plus indicators, single or multiple expansion, controlled state, and a height animation that respects reduced motion. Collapsed content stays findable with the browser's find-in-page.

React AriaSource

Yes. Every component is built on React Aria and follows WAI-ARIA patterns.

Installation

pnpm dlx shadcn@latest add @desyne/disclosure

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import {
  Disclosure,
  DisclosureGroup,
  DisclosurePanel,
  DisclosureTrigger,
} from "@/components/ui/disclosure";
tsx
<DisclosureGroup>
  <Disclosure id="shipping">
    <DisclosureTrigger>Shipping</DisclosureTrigger>
    <DisclosurePanel>Free on orders over $50.</DisclosurePanel>
  </Disclosure>
  <Disclosure id="returns">
    <DisclosureTrigger>Returns</DisclosureTrigger>
    <DisclosurePanel>Return unworn items within 30 days.</DisclosurePanel>
  </Disclosure>
</DisclosureGroup>

A DisclosureGroup is an accordion: by default, opening one item closes the others. A Disclosure also works on its own, without a group.

When to use

  • Disclosure — optional or secondary content users can skip: FAQs, advanced settings, details on a product page, filter groups.
  • Tabs — switch between peer views where exactly one is visible and none should be hidden by default.
  • Dialog or Popover — content that needs focus or shouldn't push the page down.
  • Don't hide content most users need; long pages with clear headings are often easier to scan than a stack of collapsed sections.

Anatomy

tsx
<DisclosureGroup>             {/* optional: accordion, sets the variant */}
  <Disclosure>                {/* one section */}
    <DisclosureTrigger>       {/* <h3> › <button> with label and indicator */}
      <Icon />
      Label
    </DisclosureTrigger>
    <DisclosurePanel>         {/* collapsible content */}
      …
    </DisclosurePanel>
  </Disclosure>
</DisclosureGroup>
PartRendersNotes
DisclosureGroup<div>Manages which items are expanded and passes variant to its items.
Disclosure<div>One section. Needs an id inside a group for expandedKeys. Has the group/disclosure class.
DisclosureTrigger<h3> › <button slot="trigger">Full-width button with the children on the left and the indicator on the right.
Indicator<svg>Chevron (rotates 180°) or plus (rotates 45° into a ×). aria-hidden.
DisclosurePanel<div role="group"> › <div>The collapsible region. The outer element animates its height; the inner one gets the variant's padding.

Examples

Card

variant="card" wraps the whole group in one bordered card with dividers, and tints the open item's trigger. Icons in the trigger are sized to 16px and muted.

Invite people to the workspace and choose what they can access.

Separated

variant="separated" gives each item its own card with a gap between them. The open item gets a slightly stronger shadow. Suits FAQs and marketing pages.

Indicators

indicator="chevron" (default) rotates to point up when open. indicator="plus" turns into a × and reads well in FAQ lists.

Free on orders over $50. Ships in 1–2 days.

Free on orders over $50. Ships in 1–2 days.

Multiple expanded

allowsMultipleExpanded lets users open several items at once. Use it when items are independent and users may compare them, like product details or filters.

Over-ear wireless headphones with active noise cancelling and 40 hours of battery life.

Recycled aluminium frame, protein-leather cushions. Wipe with a dry cloth.

Rich trigger

The trigger's children are laid out in a row with gap-2, so icons, monospace labels and Badges line up. Keep interactive elements out of the trigger; it's already a button.

1 of 4 instances failing health checks since 09:42 UTC

Standalone

A single Disclosure without a group uses the line styles. It draws a bottom border by default; remove it with border-b-0 when it isn't part of a list.

Disabled

isDisabled on a Disclosure stops it from toggling and dims its trigger. isDisabled on DisclosureGroup disables every item. Explain why in the trigger text or nearby.

Controlled

Pass expandedKeys and onExpandedChange to DisclosureGroup to own the state, for example for Expand all and Collapse all buttons. For a single Disclosure, use isExpanded and onExpandedChange.

npm install -g @acme/cli

Custom trigger

DisclosureTrigger always renders a heading and a full-width button. For a different layout, put any React Aria button with slot="trigger" inside the Disclosure, such as a link-style Button below a preview. It gets aria-expanded and aria-controls automatically, and group-data-expanded/disclosure: classes can react to the state.

  • src/app/page.tsx
  • src/app/layout.tsx
  • src/components/checkout-form.tsx

Recipes

FAQ section

A two-column FAQ with a heading, a Link to contact support, and plus indicators.

Frequently asked questions

Can't find what you're looking for? Talk to our team.

You get every Pro feature for 14 days. We don't ask for a card up front, and you can pick a plan at any point during the trial.

Filter panel

Filter groups in a shop sidebar, each a disclosure containing a CheckboxGroup. Multiple groups can be open at once.

Order summary

A standalone disclosure for a checkout summary on mobile: the total stays visible in the trigger and the line items expand below.

Accessibility

  • Each trigger is a <button> with aria-expanded and aria-controls pointing at its panel, inside an <h3> so sections appear in the heading outline.
  • The panel has role="group" by default and is labelled by its trigger. Pass role="region" to DisclosurePanel for important sections that should be landmarks; use it sparingly.
  • Collapsed panels use hidden="until-found" where the browser supports it, so find-in-page can reveal and expand them.
  • The height animation is disabled when the user prefers reduced motion.
  • Triggers use their text as the accessible name; icons and indicators are decorative.

Keyboard

KeyAction
Tab / Shift+TabMoves between triggers and focusable content in open panels
Space / EnterExpands or collapses the focused section

Styling

Data attributes

AttributeOnPresent when
data-expandedDisclosureThe section is open
data-disabledDisclosure, DisclosureGroupDisabled
data-focus-visible-withinDisclosure, DisclosurePanelSomething inside has keyboard focus
data-hovered / data-pressedtrigger buttonHovered / being pressed
data-focus-visibletrigger buttonFocused with the keyboard

Each Disclosure has the group/disclosure class, so any child can react to its state with group-data-expanded/disclosure:…, as the indicator does.

Slots

data-slotElement
disclosure-groupGroup root
disclosureEach item
disclosure-panelEach panel (outer element)

Animation

The panel animates height to the --disclosure-panel-height variable that React Aria sets when it opens and closes (h-(--disclosure-panel-height) overflow-hidden transition-[height] duration-200). Reuse the same classes on your own DisclosurePanel from React Aria to get the same motion.

Customizing

  • className on DisclosureGroup, Disclosure and DisclosurePanel accepts a string or a function of their render props.
  • className on DisclosureTrigger goes to the button, e.g. className="py-2 text-xs" for denser lists.
  • The panel's padding and text color come from the variant and are on its inner element. Override them with a child wrapper or [&>div]:… on DisclosurePanel.

API Reference

DisclosureGroup

Prop

Type

Also accepts every prop of React Aria's DisclosureGroup.

Disclosure

Prop

Type

Also accepts every prop of React Aria's Disclosure.

DisclosureTrigger

Prop

Type

DisclosureTrigger takes only these props. It renders an <h3>; for another heading level or other button props, use a custom trigger with slot="trigger".

DisclosurePanel

Prop

Type

  • Tabs — switch between views, one at a time.
  • Sidebar — uses a Disclosure for collapsible submenus.
  • Checkbox — common content for filter panels.
  • Card — a container for a standalone disclosure.