caliper uidocs

Device frame

Free

Show live UI inside a laptop or a phone. The lid opens and the whole device turns, but the screen is an ordinary subtree laid out at the device's true width (1280 or 390 pixels) inside a size container, so breakpoints and container queries behave exactly as in the product.

Installs components/ui/device-frame.tsxDepends on @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add @caliper/device-frame
OverviewOct 2026
MRR$48.2k
Trials312
Churn1.4%
  • Acme Co · Team$199
  • Northwind · Pro$99
  • Initech · Pro$99
  • Globex · Team$199

When to use #

Use it for

  • Docs and pages that preview a responsive screen.
  • Comparing the same screen on a laptop and a phone.

Skip it when

  • A hero image. Use real UI so the preview stays honest.
  • Screens with state you cannot lose: changing device remounts the content.

Decisions #

Why it is built this way, so you know what to keep when you change your copy.

Behaviour

True width, then scaled
The content is laid out at 1280 or 390 CSS pixels and scaled down to fit the model, so it breaks where the product breaks.

Usage

The body is decoration
The laptop and phone are hidden from assistive technology. The screen content stays in the reading and tab order.

Installation #

terminal
pnpm dlx shadcn@latest add @caliper/device-frame

Adds its 7 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { DeviceFrame } from "@/components/ui/device-frame"

function OverviewScreen() {
  return (
    <div className="grid h-full content-start gap-4 p-6 text-[15px]">
      <div className="flex items-baseline justify-between text-xl font-bold tracking-tight">Overview<span className="font-mono text-xs font-medium text-muted-foreground">Oct 2026</span></div>
      <div className="grid grid-cols-1 gap-3 @xl:grid-cols-3">
        {[["MRR", "$48.2k"], ["Trials", "312"], ["Churn", "1.4%"]].map(([name, value]) => (
          <div key={name} className="rounded-xl border border-border p-3"><small className="block font-mono text-xs text-muted-foreground">{name}</small><strong className="text-2xl tracking-tight tabular-nums">{value}</strong></div>
        ))}
      </div>
      <div className="flex h-28 items-end gap-2 border-b border-border-strong">
        {[38, 52, 46, 64, 58, 82, 74].map((h, i) => <i key={i} className={`flex-1 rounded-t ${i === 5 ? "bg-brand" : "bg-foreground"}`} style={{ height: `${h}%` }} />)}
      </div>
      <ul className="m-0 grid list-none p-0">
        {[["Acme Co · Team", "$199"], ["Northwind · Pro", "$99"], ["Initech · Pro", "$99"], ["Globex · Team", "$199"]].map(([name, price]) => (
          <li key={name} className="flex justify-between border-b border-border py-2.5"><span>{name}</span><b>{price}</b></li>
        ))}
      </ul>
    </div>
  );
}

export function DeviceFrameDemo() {
  return <div className="w-full max-w-3xl"><DeviceFrame><OverviewScreen /></DeviceFrame></div>;
}

API reference #

DeviceFrame

component
PropTypeDefaultDescription
children *ReactNode—The screen. It is laid out at 1280px (laptop) or 390px (phone) wide.
deviceDeviceKind—
defaultDeviceDeviceKind"laptop"
onDeviceChange((device: DeviceKind) => void)—
openboolean—Whether the laptop lid is open.
defaultOpenbooleantrue
onOpenChange((open: boolean) => void)—
defaultTurnnumber-18Starting turn in degrees, -40 to 40.
controlsbooleantrueShow the device, lid and turn controls.
statusDepthStatus"ready"
messagesDepthMessages—
onRetry(() => void)—
disabledboolean—
classNamestring—

Accessibility #

It is built on native elements and keeps aria-hidden, aria-pressed, aria-label and aria-valuetext in step with state.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.