Collapsible
Pojedynczy obszar, który można pokazać lub ukryć — najmniejsza jednostka stopniowego ujawniania treści.
import { ChevronsUpDownIcon, FileCodeIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
const files = ["button.tsx", "dialog.tsx", "select.tsx"]
function FileRow({ name }: { name: string }) {
return (
<div className="flex h-9 items-center gap-2.5 rounded-lg border bg-card px-3 font-mono text-code text-foreground shadow-raised">
<FileCodeIcon
className="size-4 text-muted-foreground"
aria-hidden="true"
/>
{name}
</div>
)
}
export default function CollapsibleDemo() {
return (
<Collapsible className="flex w-full max-w-72 flex-col gap-2">
<div className="flex items-center justify-between gap-4 pl-1">
<span className="text-sm font-semibold text-foreground">
components/ui{" "}
<span className="font-normal text-muted-foreground">· 4 pliki</span>
</span>
<CollapsibleTrigger
render={
<Button variant="ghost" size="icon-sm" aria-label="Pokaż lub ukryj pliki" />
}
>
<ChevronsUpDownIcon />
</CollapsibleTrigger>
</div>
<FileRow name="accordion.tsx" />
<CollapsibleContent className="flex flex-col gap-2">
{files.map((file) => (
<FileRow key={file} name={file} />
))}
</CollapsibleContent>
</Collapsible>
)
}Anatomia
dialog.tsx
select.tsx
- 1WyzwalaczPrzycisk, który pokazuje i ukrywa treść. Ma atrybut
aria-expanded. - 2TreśćObszar, który się otwiera i zamyka. Jego wysokość jest animowana, a po zamknięciu jest ukryty dla wszystkich.
Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/collapsible.jsonUżycie
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"<Collapsible>
<CollapsibleTrigger render={<Button variant="ghost" size="sm" />}>
Show details
</CollapsibleTrigger>
<CollapsibleContent>Anything you want to reveal.</CollapsibleContent>
</Collapsible>CollapsibleTrigger renderuje <button> bez stylów. Żeby nadać mu wygląd, przekaż przez render Button z prfct — zachowa on własne style i zyska działanie oraz atrybuty ARIA wyzwalacza.
Przykłady
Pokaż więcej
Odsłoń resztę długiego podsumowania. Kontroluj open, gdy etykieta wyzwalacza ma się zmieniać razem ze stanem — Pokaż wszystkie zmiany zmienia się w Pokaż mniej.
Szybsze kompilacje, spokojniejsze logi
Kompilacje przyrostowe korzystają teraz z pamięci podręcznej między gałęziami, co skraca medianę czasu kompilacji o 38%. Logi grupują powtarzające się ostrzeżenia, zamiast wypisywać każde z osobna.
"use client"
import * as React from "react"
import { ChevronDownIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
export default function CollapsibleShowMore() {
const [open, setOpen] = React.useState(false)
return (
<Collapsible
open={open}
onOpenChange={setOpen}
className="flex w-full max-w-md flex-col gap-3 rounded-xl border bg-card p-5 shadow-raised"
>
<div className="flex flex-col gap-1">
<span className="text-xs font-medium text-muted-foreground">
Wydanie 2.4 · 18 września
</span>
<h3 className="text-heading-sm text-foreground">
Szybsze kompilacje, spokojniejsze logi
</h3>
</div>
<p className="text-sm leading-[1.375rem] text-muted-foreground">
Kompilacje przyrostowe korzystają teraz z pamięci podręcznej między gałęziami, co skraca medianę czasu kompilacji o 38%. Logi grupują powtarzające się ostrzeżenia, zamiast wypisywać każde z osobna.
</p>
<CollapsibleContent>
<ul className="flex list-disc flex-col gap-1.5 pl-5 text-sm leading-[1.375rem] text-muted-foreground marker:text-gray-8">
<li>Wdrożenia podglądowe przy zimnym starcie uruchamiają się 2× szybciej.</li>
<li>CLI wyświetla tabelę podsumowania po każdym wdrożeniu.</li>
<li>Zmienne środowiskowe można ograniczyć do jednej gałęzi.</li>
<li>Naprawiono rzadką awarię przy anulowaniu kompilacji w trakcie przesyłania.</li>
</ul>
</CollapsibleContent>
<CollapsibleTrigger
render={<Button variant="link" size="sm" className="w-fit" />}
>
{open ? "Pokaż mniej" : "Pokaż wszystkie zmiany"}
<ChevronDownIcon
data-icon="inline-end"
className="transition-transform duration-base ease-standard in-data-panel-open:rotate-180"
/>
</CollapsibleTrigger>
</Collapsible>
)
}Drzewo plików
Komponenty Collapsible bez problemu się zagnieżdżają, dlatego są podstawą drzew. Każdy folder to osobny Collapsible; strzałka odczytuje atrybut data-panel-open swojego wyzwalacza, więc każdy poziom obraca się niezależnie.
import { ChevronRightIcon, FileIcon, FolderIcon } from "lucide-react"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
type Node = { name: string; children?: Node[] }
const tree: Node[] = [
{
name: "app",
children: [
{ name: "layout.tsx" },
{ name: "page.tsx" },
{
name: "docs",
children: [{ name: "layout.tsx" }, { name: "page.tsx" }],
},
],
},
{
name: "components",
children: [
{ name: "ui", children: [{ name: "button.tsx" }, { name: "card.tsx" }] },
],
},
{ name: "package.json" },
]
function TreeNode({ node, depth }: { node: Node; depth: number }) {
const indent = { paddingLeft: `${depth * 1.25 + 0.5}rem` }
if (!node.children) {
return (
<div
className="flex h-8 items-center gap-2 rounded-md pr-2 text-sm text-foreground"
style={indent}
>
<span className="size-3.5" aria-hidden="true" />
<FileIcon className="size-4 text-muted-foreground" aria-hidden="true" />
{node.name}
</div>
)
}
return (
<Collapsible defaultOpen={depth === 0}>
<CollapsibleTrigger
className="flex h-8 w-full items-center gap-2 rounded-md pr-2 text-left text-sm text-foreground focus-ring-inset hover:bg-accent"
style={indent}
>
<ChevronRightIcon
className="size-3.5 text-muted-foreground transition-transform duration-base ease-standard in-data-panel-open:rotate-90"
aria-hidden="true"
/>
<FolderIcon
className="size-4 text-muted-foreground"
aria-hidden="true"
/>
{node.name}
</CollapsibleTrigger>
<CollapsibleContent>
{node.children.map((child) => (
<TreeNode key={child.name} node={child} depth={depth + 1} />
))}
</CollapsibleContent>
</Collapsible>
)
}
export default function CollapsibleFileTree() {
return (
<div className="w-full max-w-64 rounded-xl border bg-card p-1.5 shadow-raised">
{tree.map((node) => (
<TreeNode key={node.name} node={node} depth={0} />
))}
</div>
)
}role="tree" z nawigacją strzałkami i wyszukiwaniem przez wpisywanie) to inny wzorzec — sięgnij po nie, gdy użytkownicy przeglądają setki węzłów.Ustawienia zaawansowane
Schowaj rzadko zmieniane opcje pod najważniejszymi polami formularza. Pola pozostają w kolejności DOM dokładnie tam, gdzie mają zastosowanie, więc formularz nadal czyta się od góry do dołu.
import { ChevronDownIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Switch } from "@/components/ui/switch"
export default function CollapsibleAdvancedSettings() {
return (
<div className="flex w-full max-w-sm flex-col gap-5">
<FieldGroup>
<Field>
<FieldLabel htmlFor="webhook-url">Adres URL punktu końcowego</FieldLabel>
<Input
id="webhook-url"
defaultValue="https://api.example.com/hooks"
/>
</Field>
</FieldGroup>
<Collapsible className="group/advanced flex flex-col gap-4">
<CollapsibleTrigger
render={<Button variant="ghost" size="sm" className="-ml-3 w-fit" />}
>
Ustawienia zaawansowane
<ChevronDownIcon
data-icon="inline-end"
className="transition-transform duration-base ease-standard group-data-open/advanced:rotate-180"
/>
</CollapsibleTrigger>
<CollapsibleContent>
<FieldGroup className="rounded-xl border bg-surface p-4">
<Field>
<FieldLabel htmlFor="webhook-secret">Klucz do podpisywania</FieldLabel>
<Input id="webhook-secret" placeholder="whsec_…" />
<FieldDescription>
Pozwala sprawdzić, czy żądania pochodzą od nas.
</FieldDescription>
</Field>
<Field orientation="horizontal">
<Switch id="webhook-retry" defaultChecked />
<FieldLabel htmlFor="webhook-retry" className="font-normal">
Ponawiaj nieudane dostarczenia
</FieldLabel>
</Field>
</FieldGroup>
</CollapsibleContent>
</Collapsible>
<Button className="w-fit">Zapisz webhook</Button>
</div>
)
}Ruch
CollapsibleContent animuje zmierzoną wysokość (--collapsible-panel-height) z czasem trwania base i krzywą przejścia standard, w tym samym takcie płynnie pokazując lub ukrywając treść. Animację wyłączysz, nadpisując przejście w swojej kopii komponentu, a o ograniczony ruch nie musisz się martwić: przy prefers-reduced-motion panel otwiera się natychmiast.
Wytyczne
Kiedy używać
- Do ukrycia jednego bloku drugorzędnej treści za czytelną etykietą: szczegółów, opcji zaawansowanych, reszty długiej listy.
- Jako podstawa własnych wzorców rozwijania treści — drzew plików, sekcji paska bocznego, podsumowań z „pokaż więcej”.
Kiedy nie używać
- Do kilku powiązanych sekcji, które mają działać jako zestaw — użyj komponentu Accordion.
- Do treści, która ma unosić się nad stroną — użyj komponentu Popover.
- Do ukrywania treści potrzebnej do wykonania bieżącego zadania.
Nazwij efekt
Wyzwalacz powinien mówić, co się pojawi — Ustawienia zaawansowane, Pokaż wszystkie zmiany, Jeszcze 3 pliki — a nie tylko Więcej. Połącz tekst z obracającą się strzałką; sama ikona nie mówi, co jest ukryte.
Zostaw wyzwalacz na miejscu
Umieść wyzwalacz przed treścią, którą steruje, i nie przesuwaj go po otwarciu panelu. Użytkownik powinien móc zamknąć to, co właśnie otworzył, bez szukania kontrolki.
Dostępność
Wyzwalacz to natywny <button> z aria-expanded, a gdy panel jest otwarty — także z aria-controls wskazującym na panel.
| Klawisz | Działanie |
|---|---|
Tab | Przenosi fokus na wyzwalacz, a potem do treści otwartego panelu. |
EnterSpacja | Otwiera lub zamyka panel. |
- Wyszukiwanie na stronie. Ustaw
hiddenUntilFoundnaCollapsibleContent, a przeglądarka będzie mogła przeszukiwać zamknięty panel i otworzy go po znalezieniu dopasowania. - Fokus zostaje na miejscu. Otwarcie panelu nie przenosi fokusu; następne naciśnięcie Tab przeniesie go do środka.
- Własne wyzwalacze. Gdy przez
renderrenderujesz coś innego niżButton, upewnij się, że to nadal prawdziwy przycisk — albo przekażnativeButton={false}, żeby Base UI dodał semantykę przycisku.
Dokumentacja API
Collapsible
Zarządza stanem otwarcia. Renderuje <div>. Przyjmuje wszystkie propsy Collapsible.Root z Base UI.
CollapsibleTrigger
Przycisk, który otwiera i zamyka panel. Gdy panel jest otwarty, dostaje data-panel-open — to przydatne przy obracaniu ikon. Przyjmuje wszystkie propsy Collapsible.Trigger z Base UI, w tym render i nativeButton.
CollapsibleContent
Panel. Domyślnie animuje swoją wysokość.