Installation#
Usage#
import { Panel, PanelContent, PanelHeader, PanelIndicator, PanelTitle, PanelTrigger } from '@/components/ui/panel';<Panel>
<PanelHeader>
<PanelTitle>Header</PanelTitle>
</PanelHeader>
<PanelContent />
</Panel>Examples#
Basic#
import { Divider } from '@/components/ui/divider';
import { Panel, PanelContent, PanelHeader, PanelTitle } from '@/components/ui/panel';
export default function BasicDemo() {
return (
<Panel className="max-w-xs mx-auto">
<PanelHeader>
<PanelTitle>Order Summary</PanelTitle>
</PanelHeader>
<PanelContent>
<div className="flex flex-col text-sm">
<div className="flex justify-between">
<span className="text-muted-color">Wireless Headphones</span>
<span className="text-color font-medium">$79.00</span>
</div>
<div className="flex justify-between mt-3">
<span className="text-muted-color">Phone Case</span>
<span className="text-color font-medium">$15.00</span>
</div>
<div className="flex justify-between mt-3">
<span className="text-muted-color">Shipping</span>
<span className="text-color font-medium">$5.99</span>
</div>
<Divider />
<div className="flex justify-between">
<span className="text-color font-semibold">Total</span>
<span className="text-color font-semibold">$99.99</span>
</div>
</div>
</PanelContent>
</Panel>
);
}
Toggleable#
Use Panel.Trigger inside the header to make the panel collapsible. The defaultOpen prop sets the initial state, and content visibility is animated by default.
'use client';
import { ChevronDown } from '@primeicons/react';
import { Button } from '@/components/ui/button';
import { Divider } from '@/components/ui/divider';
import { Panel, PanelContent, PanelHeader, PanelTitle, PanelTrigger } from '@/components/ui/panel';
export default function ToggleableDemo() {
return (
<Panel defaultOpen className="max-w-xs mx-auto">
<PanelHeader>
<PanelTitle>Order Summary</PanelTitle>
<PanelTrigger as={Button} severity="secondary" variant="text" rounded iconOnly>
<ChevronDown className="transition-transform duration-200 in-data-open:rotate-180" />
</PanelTrigger>
</PanelHeader>
<PanelContent>
<div className="flex flex-col text-sm">
<div className="flex justify-between">
<span className="text-muted-color">Wireless Headphones</span>
<span className="text-color font-medium">$79.00</span>
</div>
<div className="flex justify-between mt-3">
<span className="text-muted-color">Phone Case</span>
<span className="text-color font-medium">$15.00</span>
</div>
<div className="flex justify-between mt-3">
<span className="text-muted-color">Shipping</span>
<span className="text-color font-medium">$5.99</span>
</div>
<Divider />
<div className="flex justify-between">
<span className="text-color font-semibold">Total</span>
<span className="text-color font-semibold">$99.99</span>
</div>
</div>
</PanelContent>
</Panel>
);
}
Controlled#
Control panel state from outside with the open and onOpenChange props.
'use client';
import { Button } from '@/components/ui/button';
import { Divider } from '@/components/ui/divider';
import { Panel, PanelContent, PanelHeader, PanelTitle, PanelTrigger } from '@/components/ui/panel';
import { ChevronDown } from '@primeicons/react';
import type { PanelRootOpenChangeEvent } from 'primereact/panel';
import { useState } from 'react';
export default function ControlledDemo() {
const [open, setOpen] = useState(true);
return (
<div className="space-y-4 max-w-xs mx-auto">
<div className="flex gap-2 justify-center">
<Button onClick={() => setOpen(true)} severity={open ? undefined : 'secondary'}>
Open
</Button>
<Button onClick={() => setOpen(false)} severity={!open ? undefined : 'secondary'}>
Close
</Button>
</div>
<Panel open={open} onOpenChange={(e: PanelRootOpenChangeEvent) => setOpen(e.value ?? false)}>
<PanelHeader>
<PanelTitle>Order Summary</PanelTitle>
<PanelTrigger as={Button} severity="secondary" variant="text" rounded iconOnly>
<ChevronDown className="transition-transform duration-200 in-data-open:rotate-180" />
</PanelTrigger>
</PanelHeader>
<PanelContent>
<div className="flex flex-col text-sm">
<div className="flex justify-between">
<span className="text-muted-color">Wireless Headphones</span>
<span className="text-color font-medium">$79.00</span>
</div>
<div className="flex justify-between mt-3">
<span className="text-muted-color">Phone Case</span>
<span className="text-color font-medium">$15.00</span>
</div>
<div className="flex justify-between mt-3">
<span className="text-muted-color">Shipping</span>
<span className="text-color font-medium">$5.99</span>
</div>
<Divider />
<div className="flex justify-between">
<span className="text-color font-semibold">Total</span>
<span className="text-color font-semibold">$99.99</span>
</div>
</div>
</PanelContent>
</Panel>
</div>
);
}
Indicator#
Panel.Indicator supports conditional rendering based on panel state. Use the match prop to render content only when the state matches.
<Panel.Header>
<Panel.Title>Header</Panel.Title>
<Panel.Trigger>
<Panel.Indicator match="open">
<Minus />
</Panel.Indicator>
<Panel.Indicator match="closed">
<Plus />
</Panel.Indicator>
</Panel.Trigger>
</Panel.Header>Available values: open, closed. Without the match prop, the indicator renders in all states.
Match open / closed
CSS-only with data attributes
'use client';
import { ChevronDown, Minus, Plus } from '@primeicons/react';
import { Button } from '@/components/ui/button';
import { Divider } from '@/components/ui/divider';
import { Panel, PanelContent, PanelHeader, PanelIndicator, PanelTitle, PanelTrigger } from '@/components/ui/panel';
const InvoiceContent = () => (
<div className="flex flex-col text-sm">
<div className="flex justify-between">
<span className="text-muted-color">Wireless Headphones</span>
<span className="text-color font-medium">$79.00</span>
</div>
<div className="flex justify-between mt-3">
<span className="text-muted-color">Phone Case</span>
<span className="text-color font-medium">$15.00</span>
</div>
<div className="flex justify-between mt-3">
<span className="text-muted-color">Shipping</span>
<span className="text-color font-medium">$5.99</span>
</div>
<Divider />
<div className="flex justify-between">
<span className="text-color font-semibold">Total</span>
<span className="text-color font-semibold">$99.99</span>
</div>
</div>
);
export default function IndicatorDemo() {
return (
<div className="flex flex-col gap-8 max-w-xs mx-auto">
<div>
<h3 className="text-sm font-medium text-surface-500 mb-2">Match open / closed</h3>
<Panel defaultOpen>
<PanelHeader>
<PanelTitle>Order Summary</PanelTitle>
<PanelTrigger as={Button} severity="secondary" variant="text" rounded iconOnly>
<PanelIndicator match="closed">
<Plus />
</PanelIndicator>
<PanelIndicator match="open">
<Minus />
</PanelIndicator>
</PanelTrigger>
</PanelHeader>
<PanelContent>
<InvoiceContent />
</PanelContent>
</Panel>
</div>
<div>
<h3 className="text-sm font-medium text-surface-500 mb-2">CSS-only with data attributes</h3>
<Panel defaultOpen>
<PanelHeader>
<PanelTitle>Order Summary</PanelTitle>
<PanelTrigger as={Button} severity="secondary" variant="text" rounded iconOnly>
<PanelIndicator>
<ChevronDown className="transition-transform duration-200 in-data-open:rotate-180" />
</PanelIndicator>
</PanelTrigger>
</PanelHeader>
<PanelContent>
<InvoiceContent />
</PanelContent>
</Panel>
</div>
</div>
);
}
Template#
Customize the header with custom layouts such as avatars, action buttons, and additional metadata alongside Panel.Trigger.
Product designer focused on accessible interfaces, scalable design systems and modern frontend workflows. Passionate about creating intuitive user experiences that balance usability, consistency and performance across platforms. Currently exploring AI-assisted UI tooling, component architecture and developer experience improvements to streamline design-to-development collaboration and build more maintainable products.
'use client';
import { Bookmark, ChevronDown, Cog, User } from '@primeicons/react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { Panel, PanelContent, PanelHeader, PanelTitle, PanelTrigger } from '@/components/ui/panel';
export default function TemplateDemo() {
return (
<Panel defaultOpen className="max-w-xs mx-auto">
<PanelHeader>
<PanelTitle>
<div className="flex items-center gap-2">
<Avatar shape="circle">
<AvatarImage src="https://primefaces.org/cdn/primevue/images/avatar/amyelsner.png" />
<AvatarFallback>A</AvatarFallback>
</Avatar>
<span className="font-bold">Amy Elsner</span>
</div>
</PanelTitle>
<div className="flex items-center gap-1">
<Button severity="secondary" rounded variant="text" iconOnly>
<Cog />
</Button>
<PanelTrigger as={Button} severity="secondary" variant="text" rounded iconOnly>
<ChevronDown className="transition-transform duration-200 in-data-open:rotate-180" />
</PanelTrigger>
</div>
</PanelHeader>
<PanelContent>
<p className="text-sm m-0">
Product designer focused on accessible interfaces, scalable design systems and modern frontend workflows. Passionate about
creating intuitive user experiences that balance usability, consistency and performance across platforms. Currently exploring
AI-assisted UI tooling, component architecture and developer experience improvements to streamline design-to-development
collaboration and build more maintainable products.
</p>
<div className="flex flex-wrap items-center justify-between gap-4 mt-4">
<div className="flex items-center gap-2">
<Button iconOnly rounded variant="text">
<User />
</Button>
<Button severity="secondary" iconOnly rounded variant="text">
<Bookmark />
</Button>
</div>
<span className="text-surface-500 dark:text-surface-400 text-sm">Updated 2 hours ago</span>
</div>
</PanelContent>
</Panel>
);
}
Accessibility#
Screen Reader#
For toggleable panels, the trigger button uses aria-controls to reference the content region and aria-expanded to reflect visibility state. Accessible labels can be customized with aria-label or aria-labelledby.
Keyboard Support#
| Key | Function |
|---|---|
tab | Moves focus to the next focusable element in the page tab sequence. |
shift + tab | Moves focus to the previous focusable element in the page tab sequence. |
enter | Toggles the visibility of the content. |
space | Toggles the visibility of the content. |