Skip to content
75NeoUI

component · react

Collapsible

One trigger and the panel it opens, across three variants and three sizes, with a collapsed height that turns it into a show-more.

collapsible

One set of styles in @75neo/themes, shipped for React and Vue by two thin adapters.

A collapsed height clips the panel instead of hiding it.

The first lines stay on the page, and the rest slides in behind them when the trigger is pressed.

A Collapsible is Accordion’s row without the accordion around it. It wears the same three variants, the same three sizes and the same slot names on purpose, so the two look like siblings on a page and a ui override written for one reads on the other.

<Collapsible label="What is 75NeoUI?">One set of styles, shipped for React and Vue.</Collapsible>
<Collapsible label="What is 75NeoUI?">
  One set of styles, shipped for React and Vue.
</Collapsible>

What is different is that nothing coordinates it. There is no set of rows taking turns, so there is no value to identify a row by and no multiple to allow a second one open. Reach for Accordion when the rows belong together; reach for this when one panel stands on its own.

The content

The panel holds whatever the framework calls children, rather than a string prop. A collapsible usually holds markup, and a prop would only get in the way. The trigger is the other way round: label is a string, with a label slot in Vue and renderLabel in React for anything richer.

Open and closed

Open is not a recipe variant. Ark writes data-state on the trigger and the panel, and the recipe styles itself off that, so one resolved class string covers both states.

React takes open with onOpenChange, or defaultOpen to leave the state alone. Vue takes v-model:open, with defaultOpen as the uncontrolled counterpart.

const [open, setOpen] = useState(false);

<Collapsible label="Controlled" open={open} onOpenChange={(details) => setOpen(details.open)}>
  The button that opens this one lives somewhere else.
</Collapsible>;
<script setup lang="ts">
const open = ref(false);
</script>

<template>
  <Collapsible v-model:open="open" label="Controlled">
    The button that opens this one lives somewhere else.
  </Collapsible>
</template>

A show more

collapsedHeight leaves the panel clipped rather than hidden, so the first lines stay on the page and the rest slides in behind them. It takes a CSS length or a number of pixels.

<Collapsible label="Show more" collapsedHeight="3rem">
  <p>The first lines are readable before anyone opens anything.</p>
  <p>The rest arrives when they do.</p>
</Collapsible>
<Collapsible label="Show more" collapsed-height="3rem">
  <p>The first lines are readable before anyone opens anything.</p>
  <p>The rest arrives when they do.</p>
</Collapsible>

The animation

The panel measures itself, and the content slot hands both of Ark’s measurements to keyframes shared with Accordion. --height is the panel’s natural size and --collapsed-height is whatever collapsedHeight asked it to shrink to, defaulting to nothing. That default is why the same keyframes serve Accordion, which always closes all the way shut.

unmountOnExit takes the panel out of the DOM once it has finished closing, and lazyMount keeps it out until it is opened the first time. Both are off by default, because a panel that stays mounted is the one that keeps its scroll position and its form state.

Slots

The same word names the recipe slot, the data-slot attribute and the key in ui.

  • base
  • trigger
  • leadingIcon
  • label
  • trailingIcon
  • content
  • body

Variants

Read off the recipe, so these are the values that actually resolve. A Theme layer can change which one is the default; it cannot add a value.

variant
outlinesoftghost
size
smmdlg

Props

Taken by both adapters, read out of the component module. The same names and the same types work in React and Vue.

ui

CollapsibleUI

Per-slot class overrides.

variant

"outline" | "soft" | "ghost"

Defaults to "outline"

size

"sm" | "md" | "lg"

Defaults to "md"

label

string

Trigger text.

icon

ReactNode

Icon shown before the label.

trailingIcon

ReactNode

Replaces the chevron.

disabled

boolean

collapsedHeight

string | number

How much of the panel stays visible while closed, as a CSS length or a number of pixels. Turns the component into a "show more": the panel is clipped rather than hidden, and the content underneath keeps its place in the tab order.

unmountOnExit

boolean

Remove the panel from the DOM once it has finished closing.

lazyMount

boolean

Keep the panel out of the DOM until it is opened for the first time.

React only

Also takes React.HTMLAttributes<HTMLDivElement>, and Pick<CollapsibleRootProps, "open" | "defaultOpen" | "onOpenChange" | "onExitComplete" | "ids">. A className among them reaches the base slot.

renderLabel

() => React.ReactNode

Replaces the trigger's contents with arbitrary markup. Falls back to `label`.