Components / Structure
Disclosure
Shows and hides one section behind a button: advanced options, extra detail, a long explanation. For several related sections that share state, use Accordion.
When to useLink to "When to use"
- Secondary content most people don't need, but some do (advanced settings, technical details).
- Long pages where collapsing optional parts keeps the main task visible.
Use something else when:
- several sections belong together: Accordion
- views are alternatives to each other: Tabs
- the content is required to complete the task: show it, don't hide it
UsageLink to "Usage"
import { Disclosure } from "@arshad-shah/cynosure";
<Disclosure summary="Advanced options">
<Stack gap="4">…</Stack>
</Disclosure>
<Disclosure summary="Details" open={open} onOpenChange={setOpen} variant="card" />VariantsLink to "Variants"
plain has no chrome, line adds a divider below, and card is a bordered surface with inner padding.
Edge and summary endLink to "Edge and summary end"
edge draws a coloured edge on the inline-start side, as Card's edge: a tone name uses that tone's colour and any other string is a CSS colour (a log entry's level, a tool call's status). Say the same thing in words too.
trailing puts content at the end of the summary row, before the chevron: a Badge with a count or a status, or a Button of its own. It is outside the trigger button, so its text is not part of the trigger's name and a Button there is a separate tab stop that does not toggle the section. Pressing plain content there, such as a Badge, toggles it. A long summary wraps before it.
<Disclosure summary="Tool call: run_query" edge="success" trailing={<Badge tone="success">Done</Badge>}>
…
</Disclosure>PropsLink to "Props"
| Prop | What it does | Type | Default |
|---|---|---|---|
summaryRequired | The always-visible trigger text. | ReactNode | |
open | Whether the content is shown, when you control it. | boolean | |
defaultOpen | Shown on first render when uncontrolled. | boolean | |
onOpenChange | Called with the new state when the trigger is pressed. | ((open: boolean) => void) | |
variant | plain no chrome, line bottom divider, card bordered surface. | "plain" | "line" | "card" | |
edge | A coloured edge on the inline-start side, as Card's edge: a tone name uses that tone's colour, any other string is a CSS colour (a log entry's level, a tool call's status). Say the same thing in words too. Default: no edge. | Edge | no edge |
trailing | Content at the end of the summary row, before the chevron and outside the trigger button: a Badge with a count or a status, or a Button of its own. Its text is not part of the trigger's name. Pressing a Badge or text there toggles the section; a control there is its own target. | ReactNode | |
size | Height of the trigger and size of the text. Default "md". | Size | "md" |
headingLevel | Wrap the trigger in a heading of this level (accordion sections in a document outline). | 2 | 3 | 4 | 5 | 6 | 1 | |
region | Expose the content as a labelled region (for a few large sections). | boolean | |
locked | The trigger cannot close the content right now (non-collapsible accordion). Sets aria-disabled. | boolean | |
onTriggerKeyDown | Keyboard handler on the trigger (Accordion uses it for arrow navigation). | ((event: KeyboardEvent<HTMLButtonElement>) => void) | |
className | Added to the root element, for layout. | string | |
style | Layout only. | CSSProperties | |
children | The content that is shown and hidden. | ReactNode | |
data-testid | Set on the root element, for tests. | string |
Generated from the type definitions in @arshad-shah/cynosure 0.5.0.
Behaviour and accessibilityLink to "Behaviour and accessibility"
| Input | Result |
|---|---|
| Click, tap, Enter, Space | Opens or closes |
| Tab | Skips content inside a closed section; reaches a control in trailing after the trigger |
- Wiring: the trigger is a button with
aria-expandedandaria-controls.headingLevelwraps it in a heading, andregionmakes the content a region labelled by the trigger. - Hidden means hidden: after closing, the content is not focusable and not announced.
- Motion: the height animates smoothly in every browser. Reduced motion (the OS setting, or
createTheme({ motion: "reduced" })) makes it instant. - Focus ring: sits outside the trigger in
plainandline, so it never touches the text, and inside thecardborder.
TouchLink to "Touch"
The trigger is the full width and at least 44px tall on coarse pointers.
ThemingLink to "Theming"
Tokens: --cy-disclosure-gap (summary to chevron, and around trailing), --cy-disclosure-radius (card), --cy-disclosure-padding-inline and --cy-disclosure-edge-width (the edge, 4px by default). The speed follows --cy-motion-base.
createTheme({ components: { disclosure: { radius: "8px", paddingInline: "20px" } } });RelatedLink to "Related"
These examples run Cynosure 0.5.0 (commit 50ce30b), the version on npm.