Accordion
Sections that open and close in place, so a long rail or checklist shows its titles first and the detail on demand.
Playground
Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.
import { Accordion } from '@/ui/accordion';<Accordion />Rail sections
A record's details rail. Each trigger is 36px with the title in ink, an optional count and a 12px caret. Several sections can be open at once.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/ui/accordion';
import { Avatar } from '@/ui/avatar';
import { Lozenge } from '@/ui/lozenge';
const fields = [
['Owner', <span key="owner" className="flex items-center gap-075"><Avatar name="Joseph Ariel" size="small" />Joseph A.</span>],
['Stage', <Lozenge key="stage" phase="working">Interview</Lozenge>],
['Salary', '£85,000 to £95,000'],
] as const;
export default function AccordionRail() {
return (
<div className="w-85 rounded-xlarge bg-surface p-100 shadow-raised">
<Accordion defaultValue={['details']}>
<AccordionItem value="details">
<AccordionTrigger>Details</AccordionTrigger>
<AccordionContent>
<dl className="grid grid-cols-[96px_1fr] items-center gap-y-100">
{fields.map(([label, value]) => (
<div key={label} className="contents">
<dt className="text-text-subtle">{label}</dt>
<dd>{value}</dd>
</div>
))}
</dl>
</AccordionContent>
</AccordionItem>
<AccordionItem value="applications">
<AccordionTrigger count={3}>Applications</AccordionTrigger>
<AccordionContent>Senior Data Engineer, Analytics Lead and Data Platform Manager.</AccordionContent>
</AccordionItem>
<AccordionItem value="files">
<AccordionTrigger count={2}>Files</AccordionTrigger>
<AccordionContent>Priya Shah CV.pdf and Halcyon Bank terms.pdf.</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
}Disabled
A section with nothing inside yet can't be opened. It fades to 50%.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/ui/accordion';
// A section with nothing inside yet can't be opened.
export default function AccordionDisabled() {
return (
<div className="w-85 rounded-xlarge bg-surface p-100 shadow-raised">
<Accordion>
<AccordionItem value="applications">
<AccordionTrigger count={3}>Applications</AccordionTrigger>
<AccordionContent>Senior Data Engineer, Analytics Lead and Data Platform Manager.</AccordionContent>
</AccordionItem>
<AccordionItem value="placements" disabled>
<AccordionTrigger count={0}>Placements</AccordionTrigger>
<AccordionContent>No placements yet.</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
}Checklist
First-run steps: 44px rows in a card, the status icon first and no caret. The open step sits on the hovered fill with its one action. One step opens at a time.
import { CheckCircleIcon, CircleIcon } from '@phosphor-icons/react';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/ui/accordion';
import { Button } from '@/ui/button';
export default function AccordionChecklist() {
return (
<Accordion variant="checklist" multiple={false} defaultValue={['import']} className="max-w-130">
<AccordionItem value="mailbox">
<AccordionTrigger icon={<CheckCircleIcon role="img" aria-label="Done" className="text-success" />}>Connect your mailbox</AccordionTrigger>
<AccordionContent>
<p>Your mailbox is connected and syncing.</p>
</AccordionContent>
</AccordionItem>
<AccordionItem value="import">
<AccordionTrigger icon={<CircleIcon role="img" aria-label="To do" />}>Import your companies</AccordionTrigger>
<AccordionContent>
<p>Bring in your client list from a spreadsheet.</p>
<Button variant="primary" className="mt-150">
Import
</Button>
</AccordionContent>
</AccordionItem>
<AccordionItem value="team">
<AccordionTrigger icon={<CircleIcon role="img" aria-label="To do" />}>Invite your team</AccordionTrigger>
<AccordionContent>
<p>Add the consultants who work with you.</p>
<Button variant="primary" className="mt-150">
Invite
</Button>
</AccordionContent>
</AccordionItem>
</Accordion>
);
}Long and empty content
A long title cuts with an ellipsis and keeps its count and caret. A big count keeps its thousands separator. An empty section says so in words.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/ui/accordion';
// A long title cuts with an ellipsis and keeps its count and caret; a big count keeps its thousands separator.
// An empty section says so in words.
export default function AccordionLongContent() {
return (
<div className="w-70 rounded-xlarge bg-surface p-100 shadow-raised">
<Accordion defaultValue={['notes']}>
<AccordionItem value="applications">
<AccordionTrigger count={1234567}>Applications from the Halcyon Bank and Northwind Logistics framework</AccordionTrigger>
<AccordionContent>The latest 50 show here; open the list for the rest.</AccordionContent>
</AccordionItem>
<AccordionItem value="notes">
<AccordionTrigger count={0}>Notes</AccordionTrigger>
<AccordionContent>
<p className="text-text-subtle">No notes yet. Add one from the record's Notes tab.</p>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
}Motion
A section opens and closes with the expand and collapse pattern. Press Replay to watch it.
import { useState } from 'react';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/ui/accordion';
import { Button } from '@/ui/button';
// Replay closes and reopens Applications: height and fade in 200ms.
export default function AccordionMotion() {
const [open, setOpen] = useState<string[]>(['applications']);
const replay = () => {
setOpen([]);
setTimeout(() => setOpen(['applications']), 900);
};
return (
<div className="flex flex-col items-start gap-300">
<div className="w-85 rounded-xlarge bg-surface p-100 shadow-raised">
<Accordion value={open} onValueChange={(value) => setOpen(value as string[])}>
<AccordionItem value="applications">
<AccordionTrigger count={3}>Applications</AccordionTrigger>
<AccordionContent>Senior Data Engineer, Analytics Lead and Data Platform Manager.</AccordionContent>
</AccordionItem>
</Accordion>
</div>
<Button size="sm" onClick={replay}>
Replay
</Button>
</div>
);
}Toast
A short note, bottom right, that something just happened: a save, a move, an archive. It states the fact in the past tense and offers Undo when the action can be reversed.
Needs you list
The list on Home of what is waiting on you today, and the sections of the morning brief. Every row is one thing, with the one action that clears it.