DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Collapsible

A single disclosure that animates open and closed — built on Base UI's Collapsible primitive with four variants, five sizes, rich trigger slots, tone-aware theming and full RTL support.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import {
  CollapsibleRoot,
  CollapsibleHeader,
  CollapsibleTrigger,
  CollapsiblePanel,
} from "@tessinaui/ui";
<CollapsibleRoot variant="outline">
  <CollapsibleHeader>
    <CollapsibleTrigger>Show advanced options</CollapsibleTrigger>
  </CollapsibleHeader>
  <CollapsiblePanel>
    Tweak request timeouts, retry policy, and SDK telemetry — most teams leave
    these on the defaults.
  </CollapsiblePanel>
</CollapsibleRoot>

Examples

Default

A basic disclosure with the outline variant, closed by default.

Variants

Four visual treatments — ghost, outline, filled, and elevated.

Sizes

Five sizes from xs to xl scale the trigger and panel together.

Indicators

Built-in indicator sets — chevron, plus, caret, none — plus the contained style and the start side. All three knobs live on the root.

Rich trigger

leadingIcon, description and trailingText slots, with the header rendered as a real heading via headingLevel. The slots reach the button through aria-describedby — the accessible name stays the title alone.

Order summary

The checkout idiom: trailingText keeps the total visible while collapsed, the breakdown reveals on demand.

Header actions

Actions are siblings of the trigger inside CollapsibleHeader — never children of the button. Set fullWidth={false} so the trigger shares the row.

FAQ list

Independent disclosures in a divider list — several stay open at once, and long questions wrap rather than truncate. For a single-open group with arrow keys, use Accordion.

Nested

caret carries hierarchy semantics (leading, points along the reading direction closed, down open — the HIG disclosure triangle). Rotation state is scoped per disclosure, so levels never leak into each other.

Form panel

A controlled disclosure hosting real form controls. onOpenChangeComplete fires when the height animation settles — the moment to focus a revealed control or lazy-load panel data.

Open emphasis

Style the open state through Base UI's data attributes — data-open on the root, data-panel-open on the trigger — no extra prop needed.

On color

tone="on-color" derives ink, borders, washes and the indicator from the surface ink published by the plate underneath.

Intents

A subtle border accent for status context on non-ghost variants.

Loading

Skeleton placeholders mirror the collapsed and open layouts. label takes the real title's character count so the bar measures what the title measures.

When to use

Reach for Collapsible whenever you have a single block of secondary content that should be hidden by default and revealed on demand — advanced form options, an FAQ row, an order-summary breakdown, an expandable list row. Keep the trigger label short and descriptive (users decide whether to expand from the label alone), and never hide content most users need.

Two look-alikes are different components: a row that navigates somewhere points its chevron along the reading direction and belongs to a list or menu — a disclosure's chevron points down and reveals in place. And multiple related disclosures where only one should stay open belong to Accordion, which adds arrow-key movement and the single-or-multiple-open semantic.

API Reference

CollapsibleRoot props

PropTypeDefaultDescription
size"xs" | "sm" | "md" | "lg" | "xl""md"Scales trigger padding, text, indicator, and panel padding
variant"ghost" | "outline" | "filled" | "elevated""ghost"Visual treatment — ghost is just trigger + panel, the others wrap everything in a shell
rounded"none" | "sm" | "md" | "lg" | "full""lg"§2 panel map (full caps at rounded-3xl). Rounds the shell — or the ghost trigger itself
intent"none" | "primary" | "error" | "warning" | "success" | "info""none"Border accent on non-ghost shells. No-op on ghost
indicator"chevron" | "plus" | "caret" | "none""chevron"Trigger indicator set. caret = leading hierarchy semantics
indicatorStyle"contained" | "plain""plain"Bare glyph, or a tinted circle behind it
iconSide"start" | "end""end"Which edge the indicator sits on (logical — mirrors in RTL)
tone"default" | "on-color""default"on-color derives chrome from the surface ink
dir"ltr" | "rtl"—No default: direction inherits from <html dir>; set only to override
open / defaultOpen / onOpenChange——Controlled / uncontrolled state. onOpenChange(open, eventDetails) — eventDetails carries the reason and a cancel()
onOpenChangeComplete(open: boolean) => void—Fires when the height animation settles (immediately under reduced motion)
disabledbooleanfalseDisables the trigger and prevents toggling
renderReactElement | (props, state) => ReactElement—Base UI polymorphic prop (equivalent to Radix asChild)

CollapsibleHeader props

A flex row for the trigger plus any sibling actions.

PropTypeDefaultDescription
headingLevel2 | 3 | 4 | 5 | 6—Renders the header as a real <h2>–<h6> so the disclosure joins the page outline. Keep actions outside a heading header

CollapsibleTrigger props

PropTypeDefaultDescription
leadingIconReactNode—Decorative media before the title — kept out of the accessible name
descriptionReactNode—Supporting line under the title, associated via aria-describedby
trailingTextReactNode—Meta before the indicator (value, count, price) — also aria-describedby
customIndicatorReactNode—Replaces the built-in indicator glyph
fullWidthbooleantrueStretch the trigger to fill its container
render / nativeButton——Base UI polymorphism — render as a different element while preserving trigger behaviour

Renders a <button> with aria-expanded and aria-controls (APG disclosure). The title wraps rather than truncating. Keyboard: Enter and Space toggle; a lone disclosure has no arrow-key behaviour by design.

Row layout. trailingText sits on the title's line, baseline-aligned, and the description takes the full text column beneath both — so a long description reflows in the whole width rather than in whatever the value left over. The indicator is chrome outside that block: pinned to the trigger's trailing edge and optically centred over however many lines the row grows to. That is the dominant phone convention (TES-72 surveyed 44 apps: 13 of the 16 carrying a real trailing value keep it on the title's line, none move it above the title, and none truncate the title to protect it). Keep trailingText short — a value, a count, a price — and put anything longer in description.

CollapsiblePanel props

PropTypeDefaultDescription
keepMountedbooleanfalseKeep the panel in the DOM while closed. Ignored when hiddenUntilFound is set
hiddenUntilFoundbooleanfalsehidden="until-found" — closed content stays reachable by find-in-page; a browser match reveals the panel one-way and state syncs through onOpenChange
withPaddingbooleantrueSize-scaled panel padding (set false when wrapping a Card)
renderReactElement | (props, state) => ReactElement—Base UI polymorphic prop

CollapsibleSkeleton props

PropTypeDefaultDescription
variant / size / rounded / iconSide / indicatorStyle—component defaultsMirrors the real component's frame — same class maps, touch floor included
openbooleanfalseRender the open-panel placeholder
linesnumber3Body text lines in the open panel
labelnumber20Character count of the real title — the bar measures what the title measures
showIndicatorbooleantrueRender the indicator placeholder

Notes

  • Built on @base-ui/react/collapsible. State attributes for styling and testing: data-open / data-closed / data-starting-style / data-ending-style on root and panel, data-panel-open on the trigger, data-disabled everywhere while disabled. Every part also carries a stable data-slot (collapsible-root, -header, -trigger, -trigger-title, -trigger-description, -trigger-meta, -trigger-icon, -indicator, -panel, -panel-content, -skeleton) for agents and tests.
  • The animation transitions height between 0 and var(--collapsible-panel-height) — any content height, no measurement, and it is motion-safe-gated so reduced motion gets an instant toggle.
  • hiddenUntilFound needs the panel to participate in layout — never style the panel display: none/contents yourself. Safari support for hidden="until-found" is recent (late 2025); older Safari degrades to hidden-until-opened with no find-in-page reveal.
  • For multi-item disclosures, prefer Accordion. For floating revealed content, prefer Popover or HoverCard. For clamped "show more" text, a dedicated spoiler component is tracked in TES-187.
CodeBlockColor Picker

On this page

PlaygroundInstallationUsageExamplesDefaultVariantsSizesIndicatorsRich triggerOrder summaryHeader actionsFAQ listNestedForm panelOpen emphasisOn colorIntentsLoadingWhen to useAPI ReferenceCollapsibleRoot propsCollapsibleHeader propsCollapsibleTrigger propsCollapsiblePanel propsCollapsibleSkeleton propsNotes