Skip to main content

Frame

A structured container that nests panels, like a card inside a card.

Workspace

Plan and usage for the acme team.

Pro planActive

Monthly usage

4,210 of 10,000 requests

1import { Badge } from '@/components/ui/badge';
2import { Button } from '@/components/ui/button';
3import { Card } from '@/components/ui/card';
4import { Frame } from '@/components/ui/frame';
5
6export function Default() {
7 return (
8 <Frame className="max-w-md">
9 <Frame.Header>
10 <Frame.Title>Workspace</Frame.Title>
11 <Frame.Description>Plan and usage for the acme team.</Frame.Description>
12 </Frame.Header>
13 <Frame.Panel>
14 <div className="flex flex-col gap-3">
15 <div className="flex items-center justify-between">
16 <span className="text-sm font-medium">Pro plan</span>
17 <Badge variant="success">Active</Badge>
18 </div>
19 <Card variant="outline">
20 <Card.Header compact>
21 <Card.Title>Monthly usage</Card.Title>
22 <Card.Description>4,210 of 10,000 requests</Card.Description>
23 </Card.Header>
24 <Card.Content compact>
25 <div className="bg-muted h-1.5 w-full overflow-hidden rounded-full">
26 <div className="bg-primary h-full w-2/5 rounded-full" />
27 </div>
28 </Card.Content>
29 </Card>
30 </div>
31 </Frame.Panel>
32 <Frame.Footer className="justify-end">
33 <Button size="sm" variant="ghost">
34 Invoices
35 </Button>
36 <Button size="sm" variant="outline">
37 Manage plan
38 </Button>
39 </Frame.Footer>
40 </Frame>
41 );
42}

Installation

pnpm dlx nachui add frame

Anatomy

1import { Frame } from '@/components/ui/frame';
1<Frame>
2 <Frame.Header>
3 <Frame.Title>Workspace</Frame.Title>
4 <Frame.Description>Plan and usage for the acme team.</Frame.Description>
5 </Frame.Header>
6 <Frame.Panel>Panel content</Frame.Panel>
7 <Frame.Footer>Actions</Frame.Footer>
8</Frame>
The frame is a muted surface and each panel is a card sitting on it, with a smaller radius so the corners stay concentric. Panels take any content, including a Card or another Frame, which is how you get the card inside a card look.

Composition

Use the following composition to build a Frame:
Frame
├── Frame.Header
│ ├── Frame.Title
│ └── Frame.Description
├── Frame.Panel
└── Frame.Footer

Variants

Stacked

stacked removes the gaps and connects consecutive panels into one block with shared borders. Add dense to strip the panel padding and control it from your own content, useful for list rows.

Services

Status across regions, updated every minute.

auth-serviceus-east-1
Healthy
registry-apius-east-1
Healthy
rag-workereu-west-2
Degraded
ingest-croneu-west-2
Stopped
1import { Badge } from '@/components/ui/badge';
2import { Frame } from '@/components/ui/frame';
3
4const SERVICES = [
5 { name: 'auth-service', region: 'us-east-1', status: 'Healthy', variant: 'success' },
6 { name: 'registry-api', region: 'us-east-1', status: 'Healthy', variant: 'success' },
7 { name: 'rag-worker', region: 'eu-west-2', status: 'Degraded', variant: 'warning' },
8 { name: 'ingest-cron', region: 'eu-west-2', status: 'Stopped', variant: 'destructive' },
9] as const;
10
11export function Stacked() {
12 return (
13 <Frame stacked dense className="max-w-md">
14 <Frame.Header>
15 <Frame.Title>Services</Frame.Title>
16 <Frame.Description>Status across regions, updated every minute.</Frame.Description>
17 </Frame.Header>
18 {SERVICES.map((service) => (
19 <Frame.Panel key={service.name}>
20 <div className="flex items-center justify-between gap-3 px-4 py-3">
21 <div className="flex min-w-0 flex-col">
22 <span className="truncate font-mono text-sm">{service.name}</span>
23 <span className="text-muted-foreground text-xs">{service.region}</span>
24 </div>
25 <Badge variant={service.variant}>{service.status}</Badge>
26 </div>
27 </Frame.Panel>
28 ))}
29 </Frame>
30 );
31}

API Reference

Frame

PropTypeDefaultDescription
variant'default' | 'ghost''default'ghost drops the outer border and background
spacing'sm' | 'default' | 'lg''default'Padding of the frame and gap between panels
stackedbooleanfalseConnects consecutive panels with shared borders
densebooleanfalseRemoves the default panel padding
classNamestring-Additional CSS classes

Frame.Title

PropTypeDefaultDescription
as'h2' | 'h3' | 'h4' | 'h5' | 'h6''h3'Heading level
classNamestring-Additional CSS classes

Frame.Header, Frame.Description, Frame.Panel, Frame.Footer

Layout parts of the frame. Each accepts className and standard div props.
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue
Ctrl+I