Frame
A framed container for grouping related information. Built with Base UI and Tailwind CSS. Copy-paste ready.
Section title
Section description
import {
Frame,
FrameDescription,
FrameFooter,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
export default function Particle() {
return (
<Frame className="w-full">
<FrameHeader>
<FrameTitle>Section header</FrameTitle>
<FrameDescription>Brief description about the section</FrameDescription>
</FrameHeader>
<FramePanel>
<h2 className="font-semibold text-sm">Section title</h2>
<p className="text-muted-foreground text-sm">Section description</p>
</FramePanel>
<FrameFooter>
<p className="text-muted-foreground text-sm">Footer</p>
</FrameFooter>
</Frame>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/frame
Usage
import {
Frame,
FrameDescription,
FrameFooter,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";<Frame>
<FrameHeader>
<FrameTitle>Title</FrameTitle>
<FrameDescription>Description</FrameDescription>
</FrameHeader>
<FramePanel>Content</FramePanel>
<FrameFooter>Footer</FrameFooter>
</Frame>Examples
Separated Panels
Renders the frame with visible dividers between each panel section for clearer visual hierarchy and content grouping.
Separated panel
Section description
Separated panel
Section description
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
export default function Particle() {
return (
<Frame className="w-full">
<FrameHeader>
<FrameTitle>Section header</FrameTitle>
<FrameDescription>Brief description about the section</FrameDescription>
</FrameHeader>
<FramePanel>
<h2 className="font-semibold text-sm">Separated panel</h2>
<p className="text-muted-foreground text-sm">Section description</p>
</FramePanel>
<FramePanel>
<h2 className="font-semibold text-sm">Separated panel</h2>
<p className="text-muted-foreground text-sm">Section description</p>
</FramePanel>
</Frame>
);
}
With Collapsible Panels
Panels can be individually expanded or collapsed using Collapsible, useful for long-form settings or multi-section layouts.
Initialize run to answer a user question using uploaded files and the knowledge base; cite sources when relevant.
import { ChevronRightIcon } from "lucide-react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Frame,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
export function Pattern() {
return (
<Frame className="w-full">
<Collapsible className="group/collapsible" defaultOpen>
<CollapsibleTrigger className="w-full">
<FrameHeader className="flex grow flex-row items-center justify-between gap-2">
<FrameTitle>Start</FrameTitle>
<ChevronRightIcon className="size-4 text-muted-foreground transition-transform duration-200 group-data-open/collapsible:rotate-90" />
</FrameHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<FramePanel>
<p className="text-muted-foreground text-sm">
Initialize run to answer a user question using uploaded files and
the knowledge base; cite sources when relevant.
</p>
</FramePanel>
</CollapsibleContent>
</Collapsible>
</Frame>
);
}
With Dense Layout
Use the stackedPanels prop to stack multiple panels together without gaps.
Warehouse A
Dense mode removes outer padding for a more compact appearance.
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
export function Pattern() {
return (
<Frame className="w-full max-w-sm">
<FrameHeader>
<FrameTitle>Inventory Check</FrameTitle>
<FrameDescription>Real-time stock monitoring</FrameDescription>
</FrameHeader>
<FramePanel>
<h2 className="font-semibold text-sm">Warehouse A</h2>
<p className="text-muted-foreground text-sm">
Dense mode removes outer padding for a more compact appearance.
</p>
</FramePanel>
</Frame>
);
}
With Header Action
Adds an icon button to the FrameHeader for a settings or overflow action, using a flex-row header layout.
Configure integrations, billing, and team permissions from the settings panel.
import { SettingsIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
export function Pattern() {
return (
<Frame className="w-full max-w-md">
<FrameHeader className="flex-row items-center justify-between">
<div>
<FrameTitle>Workspace settings</FrameTitle>
<FrameDescription>Manage your workspace preferences</FrameDescription>
</div>
<Button aria-label="Open settings" size="icon" variant="ghost">
<SettingsIcon aria-hidden="true" />
</Button>
</FrameHeader>
<FramePanel>
<p className="text-muted-foreground text-sm">
Configure integrations, billing, and team permissions from the
settings panel.
</p>
</FramePanel>
</Frame>
);
}
Stats Display
Three metric panels each showing a label, a large value, and a trend indicator — useful for summary dashboards and KPI cards.
Total revenue
+12.5%$48,295
Active users
+8.1%3,842
Conversion rate
-0.4%3.6%
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
const stats = [
{ label: "Total revenue", trend: "+12.5%", value: "$48,295" },
{ label: "Active users", trend: "+8.1%", value: "3,842" },
{ label: "Conversion rate", trend: "-0.4%", value: "3.6%" },
];
export function Pattern() {
return (
<Frame className="w-full max-w-sm">
<FrameHeader>
<FrameTitle>Overview</FrameTitle>
<FrameDescription>Last 30 days</FrameDescription>
</FrameHeader>
{stats.map((stat) => (
<FramePanel key={stat.label}>
<div className="flex items-center justify-between">
<p className="text-muted-foreground text-sm">{stat.label}</p>
<span
className={`font-medium text-xs ${stat.trend.startsWith("+") ? "text-success" : "text-destructive-foreground"}`}
>
{stat.trend}
</span>
</div>
<p className="font-semibold text-xl">{stat.value}</p>
</FramePanel>
))}
</Frame>
);
}
With Status Badge
Attaches a Badge to the header for live system status, with per-service indicators rendered inside the panel.
Authentication API
Storage API
Webhooks
import { Badge } from "@/components/ui/badge";
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
export function Pattern() {
return (
<Frame className="w-full max-w-md">
<FrameHeader className="flex-row items-start justify-between">
<div>
<FrameTitle>API Status</FrameTitle>
<FrameDescription>Current system health</FrameDescription>
</div>
<Badge variant="success">Operational</Badge>
</FrameHeader>
<FramePanel>
<div className="flex flex-col gap-2">
{[
{ label: "Authentication API", status: "operational" },
{ label: "Storage API", status: "operational" },
{ label: "Webhooks", status: "degraded" },
].map((service) => (
<div
className="flex items-center justify-between"
key={service.label}
>
<p className="text-sm">{service.label}</p>
<span
className={`size-2 rounded-full ${service.status === "operational" ? "bg-success" : "bg-warning"}`}
/>
</div>
))}
</div>
</FramePanel>
</Frame>
);
}
Key-Value Info
A read-only <dl> list inside a single panel, displaying subscription or account details as label / value pairs.
- Plan
- Pro
- Billing cycle
- Monthly
- Next renewal
- Jul 3, 2026
- Seats used
- 7 of 20
import {
Frame,
FrameDescription,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
const details = [
{ label: "Plan", value: "Pro" },
{ label: "Billing cycle", value: "Monthly" },
{ label: "Next renewal", value: "Jul 3, 2026" },
{ label: "Seats used", value: "7 of 20" },
];
export function Pattern() {
return (
<Frame className="w-full max-w-sm">
<FrameHeader>
<FrameTitle>Subscription details</FrameTitle>
<FrameDescription>Your current plan information</FrameDescription>
</FrameHeader>
<FramePanel>
<dl className="flex flex-col gap-2">
{details.map(({ label, value }) => (
<div className="flex items-center justify-between" key={label}>
<dt className="text-muted-foreground text-sm">{label}</dt>
<dd className="font-medium text-sm">{value}</dd>
</div>
))}
</dl>
</FramePanel>
</Frame>
);
}
With Footer Actions
A FrameFooter with cancel and save buttons, demonstrating how to add CTA controls at the bottom of a settings block.
import { Button } from "@/components/ui/button";
import {
Frame,
FrameDescription,
FrameFooter,
FrameHeader,
FramePanel,
FrameTitle,
} from "@/components/ui/frame";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function Pattern() {
return (
<Frame className="w-full max-w-md">
<FrameHeader>
<FrameTitle>Display name</FrameTitle>
<FrameDescription>
This is the name that will be visible to other users.
</FrameDescription>
</FrameHeader>
<FramePanel>
<div className="flex flex-col gap-2">
<Label htmlFor="display-name">Name</Label>
<Input defaultValue="Alex Rivera" id="display-name" type="text" />
</div>
</FramePanel>
<FrameFooter className="flex justify-end gap-2">
<Button type="reset" variant="outline">
Cancel
</Button>
<Button type="submit">Save changes</Button>
</FrameFooter>
</Frame>
);
}

