Skip to content
Frames & Mocks primitive

Laptop Frame

A lid, a hinge and a tapered base, with the taper done by clip-path rather than the border trick that cannot round or carry a background. Degrades to a boxier laptop where clip-path is missing.

68 linesNo dependenciesAdded 13 Sept 2026
  • laptop
  • macbook
  • mockup
  • device
  • hero

What's included

  • components/ui/laptop-frame.tsx
  • No runtime dependencies

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add laptop-frame

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Live

Interactive, in your current theme. Every state below is the real component — type in it, tab through it, switch the theme.

Source

One file. Create components/ui/laptop-frame.tsx and paste.

components/ui/laptop-frame.tsx
/**
 * <LaptopFrame> — a lid, a hinge and a base around your own content.
 *
 * The one frame people reach for in a hero, and the one that is almost
 * always a PNG. As markup it costs three divs, inherits dark mode, and lets
 * the screen hold a live interface rather than a picture of one.
 *
 * The base is the part worth getting right and the part every CSS version
 * gets wrong. A real laptop's base is wider than its lid and tapers — so it
 * is drawn as a trapezoid, with the taper done by `clip-path` rather than by
 * the old border trick. Borders cannot be rounded independently and cannot
 * carry a background, which is why the border version always ends up as a
 * flat grey wedge with a hard edge.
 *
 * `clip-path` degrades safely: with no support the base renders as a plain
 * rectangle, which is a slightly boxier laptop and nothing worse.
 */

import * as React from 'react'

export interface LaptopFrameProps {
  children?: React.ReactNode
  /** Rendered width of the lid. The base extends past it on both sides. */
  width?: number
  /** Aspect ratio of the screen, as a Tailwind class. */
  aspect?: string
  /** Draw the base and hinge. Off gives a bare monitor. */
  base?: boolean
  className?: string
}

export function LaptopFrame({
  children,
  width = 420,
  aspect = 'aspect-[16/10]',
  base = true,
  className = '',
}: LaptopFrameProps) {
  return (
    <div className={`shrink-0 ${className}`} style={{ width }}>
      <div className="overflow-hidden rounded-xl border-[10px] border-b-[14px] border-foreground/85 bg-foreground/85 shadow-xl">
        <div className={`${aspect} relative w-full overflow-hidden bg-background`}>
          <span
            aria-hidden
            className="absolute left-1/2 top-1 z-10 size-1 -translate-x-1/2 rounded-full bg-foreground/30 border border-transparent"
          />
          {children}
        </div>
      </div>

      {base ? (
        <div aria-hidden className="relative mx-auto" style={{ width: '112%' }}>
          <div
            className="h-3 rounded-b-lg bg-foreground/85 border border-transparent"
            // 2.6% on each side is the taper. Expressed as a percentage so it
            // holds at any `width`, which a pixel value would not.
            style={{ clipPath: 'polygon(2.6% 0, 97.4% 0, 94% 100%, 6% 100%)' }}
          />
          {/* The notch a thumb goes into. Drawn as a light bar on the base
              rather than as a cut-out, because a cut-out needs a second
              clip-path and shows the page through the laptop. */}
          <div className="absolute left-1/2 top-0 h-1 w-16 -translate-x-1/2 rounded-b-md bg-background/25 border border-transparent" />
        </div>
      ) : null}
    </div>
  )
}

Props

Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.

PropTypeDefault
childrenReact.ReactNode—
widthRendered width of the lid. The base extends past it on both sides.number420
aspectAspect ratio of the screen, as a Tailwind class.string'aspect-[16/10]'
baseDraw the base and hinge. Off gives a bare monitor.booleantrue
classNamestring''

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

More frames & mocks

View all
Open the full page for this primitive

Browser Frame

Desktop browser chrome that wraps whatever you put inside it — a screenshot, a video, or the live component itself. Three variants, no image asset, and the window controls are grey rather than a copy of one vendor’s.

Frames & Mocks139 lines1 dep
Open the full page for this primitive

Phone & Tablet Frame

One handset bezel with the cut-out as a prop, so iPhone, Android and tablet are three settings rather than three assets. Status bar and home indicator included, and the clock is a prop so it can never be a hydration mismatch.

Frames & Mocks145 linesNo deps
Open the full page for this primitive

Device Showcase

The product-hero arrangement: a handset overlapping a laptop, overlapped with a negative inline-start margin so both stay in flow and the group keeps an honest height. Stacks below sm instead of shrinking to nothing.

Frames & Mocks291 linesNo deps