---
title: Collapsible
description: One details, dressed - a sentence that opens, for prose rather than lists.
source: https://adamjurek.com/components/collapsible
---

## Home


Collapsible is a single `<details>` element dressed with a summary line and a
chevron, for one expandable sentence or paragraph inside prose. Reach for
Accordion instead once there is a list of several items that should each be
able to open independently.

<!-- ::start:showcase demo="collapsible" height="380" -->
<!-- ::end:showcase -->

## Why it is built this way

It is a separate component from Accordion rather than an accordion of one
item, because the composed component imposes a list shape that a single line
inside a paragraph does not want. A collapsible inside prose is a sentence
that opens, not a stack of one.

## What it does not do

After the first paint, `defaultOpen` does not force anything - it only seeds
the initial state, and the reader's own toggle stands after that. There is no
controlled `open` prop or callback for tracking state from outside.

> [!NOTE] Install commands are not written here
> Anything in `packages/ui/registry.ts` gets its TanStack and shadcn commands
> attached automatically, so there is nothing to keep in sync.


## Install

<!-- ::start:tabs -->

### TanStack

```shell
tanstack add https://adamjurek.com/r/tanstack/collapsible.json
```

### shadcn

```shell
pnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/collapsible.json
```

### pnpm

```shell
pnpm add @sushindustries/ui @sushindustries/atoms
```

<!-- ::end:tabs -->

## What you get

| | |
| --- | --- |
| Version | 0.1.0 |
| Category | layout · Disclosure |
| Files | `collapsible.tsx` |
| Dependencies | None |
| Also installs | `icon` |
| Tags | details, no-deps |

> [!NOTE] No runtime dependencies
> It brings nothing with it beyond the stylesheet.

## Get Started


Install commands are on Home, attached from the registry - they are not written
here, because a second copy is a copy that goes stale. This tab starts after the
install worked.

## Use it

```tsx
import { Collapsible } from "@sushindustries/ui";

export function Example() {
	return (
		<Collapsible summary="What's included">
			<p>Source files, the compiled build, and a year of updates.</p>
		</Collapsible>
	);
}
```

## What you should see

One line of bold text with a chevron on the right, closed by default. It
looks identical to a single row from `Accordion` - it is the same
`<details>` markup - but there is no wrapping list around it, so it can
sit directly inside a paragraph of prose rather than inside a stacked
box.

## If nothing happens

If it opens on first paint when it should not, check `defaultOpen` was
not left set to `true` from an earlier edit - there is no way to force it
closed again after the reader has toggled it, and that is deliberate: the
prop only ever governs the very first render.


## Guides


## When to use this instead of Accordion

`Collapsible` is one `<details>` with no list wrapper, for a single
aside inside prose - a "read more", a spoiler, an FAQ answer that does
not belong to a set. Reach for `Accordion` instead the moment there are
two or more related rows that should look like a group; wrapping several
`Collapsible`s by hand only recreates what `Accordion` already does,
minus the shared border between rows.


## API


<!-- generated:api -->

## Props

| Prop | Type | Default | Does |
| --- | --- | --- | --- |
| `summary` | `ReactNode` | - | The always-visible line. A node rather than a string, so a section can put its own icon beside its name. It was a string, which forced every caller that wanted one to give up the component and hand-roll a `<details>` - which is the shape of a prop that is one type too narrow. Keep it to a line. This is a summary element: it is the click target and the thing a screen reader announces for the whole section, so a paragraph in here is a paragraph read out before anything else. |
| `children` | `ReactNode` | - |  |
| `defaultOpen?` | `boolean` | - | Open on the first paint. After that the reader's toggle stands - nothing re-forces it. |

<!-- /generated:api -->

## Notes

There is no controlled mode - `defaultOpen` sets the state for the first
render only, matching `Accordion`. `summary` is plain text, not a
`ReactNode`; anything that needs markup in the always-visible line
belongs in a custom `<details>` built by hand, not through this
component.


## Examples


Examples are the tab where the component is shown doing a job, not
demonstrating a prop. The API tab already lists the props.

<!-- ::start:showcase demo="collapsible" height="420" -->
<!-- ::end:showcase -->

Press Compare. The frames are real viewports, so a layout that breaks at 320
breaks here too rather than in somebody's hands.

## In a page

```tsx
import { Collapsible } from "@sushindustries/ui";

export function ChangelogEntry({ version, details }: { version: string; details: string }) {
	return (
		<article>
			<p>Version {version} shipped with the usual fixes.</p>
			<Collapsible summary="Full changelog">
				<p>{details}</p>
			</Collapsible>
		</article>
	);
}
```

## What this example is not

`Collapsible` sits inside a plain `<article>` here, not a list of
entries each with their own toggle - if the page needed several
changelog entries all independently expandable in a stack, `Accordion`
is the component that gives them the shared border and grouped look.
