{"slug":"disclosure-group","name":"DisclosureGroup","packageName":"@liquidify/react","category":"controls","maturity":"experimental","summary":"A disclosure widget: a single tappable header (a label + a trailing chevron indicator) that toggles the visibility of arbitrary content beneath it.","importPath":"@liquidify/react/disclosure-group","sourcePath":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx","specPath":"packages/@liquidify/react/src/components/disclosure-group/spec.md","docsPath":"../liquidify-page/content/component-source/disclosure-group.md","documentationStatus":"draft","storyIds":["controls-disclosure-group--default"],"additionalExports":["ControlProps","DisclosureGroupVariant"],"anatomy":[{"name":"Root (.lq-disclosure-group, data-variant=\"plain\" | \"translucent\",","description":"Root (.lq-disclosure-group, data-variant=\"plain\" | \"translucent\","},{"name":"Header (.lq-disclosure-group-header, <button type=\"button\"","description":"Header (.lq-disclosure-group-header, <button type=\"button\""},{"name":"Label (.lq-disclosure-group-label)","description":"Label (.lq-disclosure-group-label) — the caller's label:"},{"name":"Chevron (.lq-disclosure-group-chevron, a wrapper <span","description":"Chevron (.lq-disclosure-group-chevron, a wrapper <span"},{"name":"Content region (.lq-disclosure-group-content, id={contentId})","description":"Content region (.lq-disclosure-group-content, id={contentId}) — the"},{"name":"The glass surface","description":"The glass surface — conditional, mirroring LabeledContent's posture"}],"props":[{"name":"animated","type":"boolean | undefined","required":false,"description":"When `false`, the expand/collapse roll (and its deferred unmount via {@link Presence}) is suppressed: toggling shows/hides `children` instantly, exactly as under `prefers-reduced-motion`. The chevron still rotates via CSS; kill that per-instance if an instant swap is wanted there too. Used by DatePicker, whose month/year wheel disclosure must not animate.","defaultValue":"`true`","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"children","type":"ReactNode","required":false,"description":"The revealed content (SwiftUI `\\@ViewBuilder content:`). Mounted in the DOM only while expanded (unmounted, not merely `hidden`, while collapsed) — supports a nested `<DisclosureGroup>`, which renders its own independent root with no auto-indent. Optional: a childless DisclosureGroup is a pure header toggle whose only effect is its event callbacks (e.g. lazy-loading content in {@link DisclosureGroupProps.onExpandedChange}).","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"className","type":"string | undefined","required":false,"description":"Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"defaultExpanded","type":"boolean | undefined","required":false,"description":"Uncontrolled seed for the internal expanded state (ignored once {@link DisclosureGroupProps.expanded} is supplied). SwiftUI's own default is collapsed.","defaultValue":"`false`","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"disabled","type":"boolean | undefined","required":false,"description":"When `true`, sets the native `disabled` attribute on the header `<button>`: removed from the tab order, not activatable via pointer or keyboard, dimmed via `--lq-opacity-muted`. The group's current collapsed/expanded content freezes at whatever it was.","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"expanded","type":"boolean | undefined","required":false,"description":"Controlled expanded state (SwiftUI `isExpanded: Binding<Bool>`). Passing this switches DisclosureGroup to controlled mode: the rendered state always reflects this value and the consumer owns it via {@link DisclosureGroupProps.onExpandedChange}.","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"icon","type":"ReactNode","required":false,"description":"The header's trailing glyph (SwiftUI disclosure indicator). Defaults to a right-pointing chevron `<Icon name=\"chevron-right\" tint=\"accent\" />` that rotates 90° on expand. To use a different glyph or colour, pass your OWN `<Icon>` (any `ReactNode`); it renders inside the rotating `.lq-disclosure-group-chevron` wrapper, so it still animates on toggle.","defaultValue":"`<Icon name=\"chevron-right\" tint=\"accent\" />`","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"label","type":"ReactNode","required":true,"description":"The header's label (SwiftUI `titleKey:` / custom label view). A `string | number` routes through the shared `TextLabel` guard into an internal `Text` styled `variant=\"headline\"`, `weight=\"bold\"`, `color=\"primary\"` — the fixed header typography (ADR-0028). To style it differently, pass your OWN `<Text>` (any `ReactNode`), which passes through unwrapped and owns its typography, OR use {@link DisclosureGroupProps.labelProps} to override individual axes while keeping the fixed structure. This is the button's accessible name.","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"labelProps","type":"LabelProps | undefined","required":false,"description":"Per-instance typography override for a `string | number` {@link * DisclosureGroupProps.label} (ADR-0028 rule 2, charter §2) — spread last into the header's primary `TextLabel`, so an explicit value here wins over the fixed `variant=\"headline\"`/`weight=\"bold\"`/`color=\"primary\"` defaults. Ignored when `label` is already a `ReactNode` element (e.g. a caller-supplied `<Text>`), which passes through unwrapped and owns its own axes.","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"onExpandedChange","type":"((expanded: boolean) => void) | undefined","required":false,"description":"Fires with the next boolean on every toggle path — pointer click anywhere in the header row, or `Space` / `Enter` on the focused header button. Always called, in both controlled and uncontrolled modes. The sole semantic callback (charter §1 rule 5) — there is no public click-level prop.","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"},{"name":"ref","type":"Ref<HTMLDivElement> | undefined","required":false,"description":"Forwarded ref to the rendered element — the component's native root (or the merged child element on a component that opts into {@link SlottableProps}). Generic over `E` (charter `docs/api-conventions.md` §4 rule 1, default `HTMLElement`) so a component with a polymorphic root (Link's navigation / action mode union, Text's `as`) can instantiate the concrete element per variant instead of leaving every consumer with a widened `Ref<HTMLElement>`. A component that does not narrow simply inherits the `HTMLElement` default — source-compatible with every pre-existing non-generic `extends ControlProps`.","declaredIn":"packages/@liquidify/react/src/liquid-component-props.ts"},{"name":"variant","type":"DisclosureGroupVariant | undefined","required":false,"description":"Material posture of the root.","defaultValue":"`\"plain\"`","declaredIn":"packages/@liquidify/react/src/components/disclosure-group/disclosure-group.tsx"}],"swiftUIMap":[{"swiftUI":"`DisclosureGroup(_ titleKey:, content:)` (string label)","react":"`label: ReactNode`","notes":"Title/header. A `string`/`number` routes through the internal `Text` (`variant=\"headline\"`, `weight=\"bold\"`, `color=\"primary\"`, ADR-0028); any other node passes through unwrapped. Becomes the header button's accessible name.","relationship":"direct"},{"swiftUI":"`init(content:, label:)` (custom label view)","react":"`label: ReactNode`","notes":"Same prop covers the custom-label case — element labels pass through `TextLabel` unwrapped.","relationship":"direct"},{"swiftUI":"`content:` closure (`@ViewBuilder`, may be `EmptyView`)","react":"`children?: ReactNode`","notes":"Arbitrary revealed content; supports a nested `<DisclosureGroup>` (no auto-indent — see Anatomy). **Optional** — a childless group is a pure header toggle whose effect is its callbacks; the expanded content region is simply empty.","relationship":"direct"},{"swiftUI":"`isExpanded: Binding<Bool>` (controlled)","react":"`expanded?: boolean` + `onExpandedChange?: (expanded: boolean) => void`","notes":"The binding splits into the controlled-state prop + change callback — the presentation triad (ADR-0015), not the value-bearing `checked`/`onChange` shape (see Interaction). No public click-level prop exists (charter §1 rule 5): DisclosureGroup's header button is an internal interactive element, so `onExpandedChange(next)` is the sole semantic callback.","relationship":"direct"},{"swiftUI":"bare init (uncontrolled, self-managed)","react":"`defaultExpanded?: boolean` (default `false`)","notes":"Uncontrolled seed. SwiftUI's own default is collapsed.","relationship":"renamed"},{"swiftUI":"`.disabled(_:)`","react":"`disabled?: boolean`","notes":"Standard control state; not exercised in the demo, part of the full SwiftUI surface.","relationship":"direct"},{"swiftUI":"`.font(_:)` / `.foregroundStyle(_:)` on the label","react":"`labelProps?: LabelProps`","notes":"Structured, token-backed typography override seam (ADR-0028) spread last into the header's primary `TextLabel`; a whole `<Text>` node passed as `label` bypasses it and owns its own axes.","relationship":"direct"},{"swiftUI":"disclosure-indicator glyph (SF Symbol `chevron.right`)","react":"`icon?: ReactNode` (default `<Icon name=\"chevron-right\" tint=\"accent\" />`)","notes":"The header's trailing glyph. Renders inside the rotating `.lq-disclosure-group-chevron` wrapper, so a custom `<Icon>` still animates on expand. Pass your own node to change glyph or colour.","relationship":"direct"},{"swiftUI":"`.glassEffect(in: .rect(cornerRadius: 20))` wrapper (`\"Nested on glass\"`)","react":"`variant?: \"plain\" | \"translucent\"` (default `\"plain\"`)","notes":"The glass panel applies ONLY to the translucent variant — mirrors `LabeledContent`'s `variant=\"translucent\"` convention exactly.","relationship":"direct"},{"swiftUI":"`disclosureGroupStyle(_:)`","react":"—","notes":"Not mapped for v1 — the demo exercises only the default `AutomaticDisclosureGroupStyle` (see Out of scope).","relationship":"direct"}],"tokens":["--lq-color-accent","--lq-color-label-primary","--lq-disclosure-group-gap","--lq-disclosure-group-hpad","--lq-disclosure-group-pad","--lq-disclosure-group-thumb-h","--lq-disclosure-group-track-h","--lq-duration-standard","--lq-easing-standard","--lq-focus-ring-color","--lq-glass-","--lq-radius-20","--lq-radius-22","--lq-space-","--lq-space-16","--lq-space-2"],"keyboard":[{"key":"Keyboard","action":"Keyboard: Tab focuses the header button; Space and Enter toggle expansion (WAI-ARIA Disclosure pattern); arrow keys do nothing — this is a disclosure, not an accordion or tree, so there is no roving tabindex across sibling groups. Nested groups are independently focusable buttons reached in DOM order."}],"variants":["plain","translucent"],"variantPages":[]}