Panel
A responsive, composable side panel layout for navigation, filters, or supporting content.
Import
import { Panel } from "@cfa/react-core";Guidelines
When to use
- Panel is a persistent side column beside the main content. It is part of the page layout, not an overlay.
Do
- Hold one coherent purpose in the side column.
Avoid
- Using Panel for temporary or task-scoped content that overlays the page. Use Drawer.
- Using Panel for a blocking decision. Use Modal.
- Using Panel for content in the main content area. Use Card or the main layout column.
Examples
Full Page
The standard Panel layout uses full-page positioning and should be used for application-level layouts. The docs site cannot render this version as a live example because the panel is positioned relative to the full viewport instead of the preview frame. Refer to Storybook for a working interactive example.
<Panel.Provider> <Panel.Root aria-label="Application navigation"> <Panel.Header> <span>Application</span> </Panel.Header> <Panel.Separator /> <Panel.Content> <a href="/dashboard">Dashboard</a> <a href="/projects">Projects</a> <a href="/settings">Settings</a> </Panel.Content> <Panel.Separator /> <Panel.Footer> <span>John Doe</span> </Panel.Footer> </Panel.Root> <Panel.Inset> <div style={{ padding: 16 }}> <Panel.Trigger /> <h1>Main Content</h1> <p> The panel can be toggled with the trigger button or the default Cmd+B / Ctrl+B keyboard shortcut. </p> </div> </Panel.Inset></Panel.Provider>Controlled
Use open and onOpenChange when the parent component needs to own the panel’s open state.
function ControlledPanelExample() { const [open, setOpen] = React.useState(true);
return ( <Panel.Provider open={open} onOpenChange={setOpen}> <Panel.Root aria-label="Controlled panel"> <Panel.Header> <span>Controlled Panel</span> </Panel.Header> <Panel.Content> <p>Panel is {open ? "open" : "closed"}.</p> </Panel.Content> </Panel.Root> <Panel.Inset> <div style={{ padding: 16 }}> <Panel.Trigger /> </div> </Panel.Inset> </Panel.Provider> );}Common Options
Use side="right" to position the panel on the right side. Use defaultOpen={false} for an uncontrolled panel that starts closed. The default keyboard shortcut is Cmd+B on Mac and Ctrl+B on Windows or Linux; set keyboardShortcut to customize the key or disableKeyboardShortcut to turn the shortcut off.
<Panel.Provider defaultOpen={false} side="right" keyboardShortcut="k"> {/* Panel.Root and Panel.Inset */}</Panel.Provider>Contained Layout
The default viewport layout is preferred for application shells. Use layout="contained" for bounded regions such as dashboard panels, split panes, or documentation previews.
Panel.Inset renders a main landmark by default because most Panel usage represents an application shell. When a contained Panel is rendered inside an existing page, use as="section" or as="div" to keep the inset layout behavior without creating another page-level main landmark.
<div style={{ height: 400, border: "1px solid #ccc", overflow: "hidden" }}> <Panel.Provider layout="contained" disableKeyboardShortcut> <Panel.Root aria-label="Contained navigation"> <Panel.Header> <span>Panel</span> </Panel.Header> <Panel.Content> <a href="/option-1">Option 1</a> <a href="/option-2">Option 2</a> <a href="/option-3">Option 3</a> </Panel.Content> </Panel.Root> <Panel.Inset as="section" aria-label="Contained panel content"> <div style={{ padding: 16 }}> <Panel.Trigger /> <h2>Contained Panel</h2> <p>This content is scoped to the bounded container.</p> </div> </Panel.Inset> </Panel.Provider></div>If a contained Panel is nested inside an application shell that already owns the keyboard shortcut, set disableKeyboardShortcut on the nested Panel.Provider to avoid competing Cmd+B or Ctrl+B handlers.