Collapsible
Shows and hides a section of content.
- v1.0.0
- WAI-ARIA: Disclosure (Show/Hide)
- SSR
- Zoneless
- 1.8 kB gzip
@peduarte starred 3 repositories
import { Component } from '@angular/core';
import { NdIcon } from '@ndop/core';
import { Button } from '@/ui/button/button';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/ui/collapsible/collapsible';
/** A list of starred repositories that expands to show the rest. */
@Component({
selector: 'example-collapsible-demo',
imports: [
Button,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
NdIcon,
],
template: `
<div ndCollapsible class="flex w-[350px] flex-col gap-2">
<div class="flex items-center justify-between gap-4 px-4">
<h2 class="text-sm font-semibold">
@peduarte starred 3 repositories
</h2>
<button
ndCollapsibleTrigger
ndButton
variant="ghost"
size="icon"
class="size-8"
>
<svg ndIcon="chevrons-up-down"></svg>
<span class="sr-only">Show all repositories</span>
</button>
</div>
<div class="rounded-md border px-4 py-2 font-mono text-sm">
@radix-ui/primitives
</div>
<div ndCollapsibleContent class="flex flex-col gap-2">
<div class="rounded-md border px-4 py-2 font-mono text-sm">
@radix-ui/colors
</div>
<div class="rounded-md border px-4 py-2 font-mono text-sm">
@stitches/react
</div>
</div>
</div>
`,
})
export class CollapsibleDemoExample {}
A collapsible shows and hides a section of content with a button, the
WAI-ARIA disclosure pattern. The trigger says whether the content is
shown, with aria-expanded, and hidden content is out of the tab order.
It has no styles of its own: style the trigger as a Button and the
content as you like, and use data-state to rotate a chevron.
Installation
npx @ndop/cli add collapsiblepnpm dlx @ndop/cli add collapsibleyarn dlx @ndop/cli add collapsiblebunx @ndop/cli add collapsibleInstall the dependencies
npm install @ndop/primitives @ndop/corepnpm add @ndop/primitives @ndop/coreyarn add @ndop/primitives @ndop/corebun add @ndop/primitives @ndop/coreCopy the code into your project
At the paths your
ndop.jsonaliases point to; these use the defaults.src/app/ui/collapsible/collapsible.tsimport { computed, Directive, input } from '@angular/core'; import { NdCollapsible, NdCollapsibleContent, NdCollapsibleTrigger, } from '@ndop/primitives/collapsible'; import { cn } from '@/lib/utils'; /** * A collapsible (`@ndop/primitives/collapsible`): a trigger that shows and * hides a section of content, which stays in the page and slides open and * closed. Style the trigger with other items, such as a ghost Button. * `open` is two-way bindable. * * @example * ```html * <div ndCollapsible [(open)]="open"> * <button ndButton variant="ghost" ndCollapsibleTrigger>Show more</button> * <div ndCollapsibleContent>…</div> * </div> * ``` */ @Directive({ selector: '[ndCollapsible]', hostDirectives: [ { directive: NdCollapsible, inputs: ['open', 'defaultOpen', 'disabled'], outputs: ['openChange'], }, ], host: { 'data-slot': 'collapsible' }, }) export class Collapsible {} /** * The button that shows and hides its collapsible's content, with * `aria-expanded` and `aria-controls`. * * @example * ```html * <button ndButton variant="ghost" ndCollapsibleTrigger>Show more</button> * ``` */ @Directive({ selector: 'button[ndCollapsibleTrigger]', hostDirectives: [NdCollapsibleTrigger], host: { 'data-slot': 'collapsible-trigger' }, }) export class CollapsibleTrigger {} /** * The content a collapsible shows and hides, sliding open and closed; * hidden content leaves the accessibility tree and the tab order. It clips * its content only while it animates, so focus rings inside it show. * * @example * ```html * <div ndCollapsibleContent>…</div> * ``` */ @Directive({ selector: '[ndCollapsibleContent]', hostDirectives: [NdCollapsibleContent], host: { 'data-slot': 'collapsible-content', '[class]': 'classes()' }, }) export class CollapsibleContent { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'data-animating:overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down', // tw-animate-css's collapsible keyframes read this height. '[--radix-collapsible-content-height:var(--nd-collapsible-content-height)]', this.class(), ), ); }src/app/lib/utils.ts/** * Class helpers used by every styled item. They come from `@ndop/core`; * this file is yours, so it is the place to customize them, for example by * wrapping `cn` with an extended tailwind-merge configuration for your own * utilities. * * @packageDocumentation */ export { cn, variants, type ClassValue, type VariantOf } from '@ndop/core';Update the import paths to match your project
The files import each other through the
@/uiand@/libaliases.
Usage
import { Button } from '@/ui/button/button';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/ui/collapsible/collapsible';<div ndCollapsible class="flex w-[350px] flex-col gap-2">
<div class="flex items-center justify-between gap-4 px-4">
<h2 class="text-sm font-semibold">
@peduarte starred 3 repositories
</h2>
<button
ndCollapsibleTrigger
ndButton
variant="ghost"
size="icon"
class="size-8"
>
<svg ndIcon="chevrons-up-down"></svg>
<span class="sr-only">Show all repositories</span>
</button>
</div>
<div class="rounded-md border px-4 py-2 font-mono text-sm">
@radix-ui/primitives
</div>
<div ndCollapsibleContent class="flex flex-col gap-2">
<div class="rounded-md border px-4 py-2 font-mono text-sm">
@radix-ui/colors
</div>
<div class="rounded-md border px-4 py-2 font-mono text-sm">
@stitches/react
</div>
</div>
</div>Examples
Collapsible settings
Advanced options shown on demand.
import { Component } from '@angular/core';
import { NdIcon } from '@ndop/core';
import { Button } from '@/ui/button/button';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/ui/collapsible/collapsible';
import { Input } from '@/ui/input/input';
import { Label } from '@/ui/label/label';
/**
* Advanced options a form shows on demand; the chevron turns with
* `data-state`.
*/
@Component({
selector: 'example-collapsible-settings',
imports: [
Button,
Input,
Label,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
NdIcon,
],
template: `
<form class="grid w-full max-w-sm gap-4">
<div class="grid gap-2">
<label ndLabel for="collapsible-settings-name">Project name</label>
<input ndInput id="collapsible-settings-name" value="ndop-docs" />
</div>
<div ndCollapsible class="grid gap-4">
<button
ndCollapsibleTrigger
ndButton
variant="ghost"
size="sm"
class="group justify-self-start"
>
Advanced options
<svg
ndIcon="chevron-right"
class="transition-transform group-data-[state=open]:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]:rotate-90"
></svg>
</button>
<div ndCollapsibleContent class="grid gap-2">
<label ndLabel for="collapsible-settings-dir">Output folder</label>
<input ndInput id="collapsible-settings-dir" value="dist" />
</div>
</div>
</form>
`,
})
export class CollapsibleSettingsExample {}
Accessibility
Implements the Disclosure (Show/Hide) pattern of the WAI-ARIA Authoring Practices.
Keyboard
Keys the component handles.
| Key | Action |
|---|---|
| Enter | On the trigger, shows or hides the content. |
| Space | On the trigger, shows or hides the content. |
Focus
- Focus stays on the trigger when the content shows or hides.
- Shown content joins the tab order after the trigger; hidden content leaves it.
Right-to-left
Nothing mirrors.
API reference
Collapsible
[ndCollapsible]A collapsible (@ndop/primitives/collapsible): a trigger that shows and
hides a section of content, which stays in the page and slides open and
closed. Style the trigger with other items, such as a ghost Button.
open is two-way bindable.
| Collapsible binding | Type | Default |
|---|---|---|
[defaultOpen]Initial open state when open is not bound. | boolean | false |
[disabled]Stops the trigger from toggling the content. | boolean | false |
[(open)]Whether the content is shown. Two-way bindable with [(open)]. | boolean | false |
| Data attribute | Values |
|---|---|
data-slot | collapsible |
data-state | — |
data-disabled | — |
CollapsibleTrigger
button[ndCollapsibleTrigger]The button that shows and hides its collapsible's content, with
aria-expanded and aria-controls.
Manages aria-expanded, aria-controls.
| Data attribute | Values |
|---|---|
data-slot | collapsible-trigger |
data-state | — |
data-disabled | — |
CollapsibleContent
[ndCollapsibleContent]The content a collapsible shows and hides, sliding open and closed; hidden content leaves the accessibility tree and the tab order. It clips its content only while it animates, so focus rings inside it show.
| CollapsibleContent binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | collapsible-content |
data-state | — |
data-animating | — |
Agent notes
Do
- Put
ndCollapsibleTriggeron a nativebutton, often withndButton. - Write a trigger label that says what it reveals, such as "Show 3 more repositories".
- Start it open with
defaultOpen, or bind[(open)]to control it.
Don't
- Don't use it for a set of headings that each show a section; use an Accordion.
- Don't hide content that most people need.