beta
Device Mockup — .mv-device
Pure-CSS device frames around any content: iPhone (Dynamic Island, buttons), Android (punch-hole), iPad portrait/landscape, MacBook (notch, aluminum base) and a browser window (traffic lights, address bar via data-url); dark or light bezels, all scaled from a single width.
| Category | Media |
|---|---|
| Type | CSS only (.mv-device) |
| Status | beta |
| Keywords | device, mockup, iphone, android, ipad, macbook, browser, safari, frame, screen |
When to use
- A screenshot, video or live demo of an app must be framed in a phone, tablet or laptop on a landing page
- A web product needs a browser window frame with traffic lights and an address bar around its screenshot
- The same product should be shown across mobile, tablet and desktop frames side by side
Avoid when
- Brand-accurate device renders are required; these are stylized CSS frames
- The address bar text carries meaning; it is CSS-generated and may not be read out, so repeat it in content
Install
node scripts/add.mjs device-mockup --out ./src/marvelousAI agent with the Marvelous UI MCP server: install_components({ slugs: ["device-mockup"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, components/device-mockup/device-mockup.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<div style="display:grid;gap:3rem;width:100%;justify-items:center">
<div style="display:flex;flex-wrap:wrap;gap:3rem 3.5rem;align-items:flex-end;justify-content:center">
<!-- iPhone: audio player -->
<div class="mv-device" data-device="iphone" style="--mv-device-width:15rem">
<div class="mv-device-screen" style="display:grid;grid-template-rows:1fr auto;background:linear-gradient(180deg,oklch(0.42 0.16 300),oklch(0.2 0.06 280));color:#fff;font:13px/1.35 var(--mv-font-sans)">
<div style="display:grid;place-items:center;padding:3.2rem 1.2rem 0">
<div aria-hidden="true" style="width:100%;aspect-ratio:1;border-radius:14px;background:radial-gradient(circle at 70% 30%,#fde68a 0 14%,transparent 15%),linear-gradient(160deg,transparent 55%,#4c1d95 55.5%),linear-gradient(180deg,#f472b6,#fb923c);box-shadow:0 20px 40px -12px #0008"></div>
</div>
<div style="padding:1rem 1.2rem 1.8rem;display:grid;gap:.7rem">
<div><strong style="display:block;font-size:15px">August Light</strong><span style="opacity:.7">Clear Nights</span></div>
<div aria-hidden="true" style="height:4px;border-radius:4px;background:linear-gradient(90deg,#fff 38%,#fff4 38%)"></div>
<div aria-hidden="true" style="display:flex;justify-content:space-between;font-size:11px;opacity:.7"><span>1:24</span><span>-2:11</span></div>
<div aria-hidden="true" style="display:flex;justify-content:center;gap:1.6rem;align-items:center;font-size:18px"><span>⏮</span><span style="display:grid;place-items:center;width:44px;height:44px;border-radius:50%;background:#fff;color:#1e1b4b;font-size:16px">❚❚</span><span>⏭</span></div>
</div>
</div>
</div>
<!-- Android: messaging, light bezels -->
<div class="mv-device" data-device="android" data-tone="light" style="--mv-device-width:14.5rem">
<div class="mv-device-screen" style="font:12px/1.35 var(--mv-font-sans);padding:2.6rem .9rem 1rem;display:grid;align-content:start;gap:.55rem;background:var(--mv-bg-subtle)">
<strong style="font-size:18px;letter-spacing:-.02em;padding-inline:.2rem">Messages</strong>
<div style="display:flex;gap:.6rem;align-items:center;padding:.55rem;border-radius:12px;background:var(--mv-surface)"><span aria-hidden="true" style="flex:none;width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#f472b6,#8b5cf6)"></span><span style="min-width:0"><b style="display:block">Olivia Park</b><span style="color:var(--mv-fg-muted)">Meet at 7 p.m.?</span></span></div>
<div style="display:flex;gap:.6rem;align-items:center;padding:.55rem;border-radius:12px;background:var(--mv-surface)"><span aria-hidden="true" style="flex:none;width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#34d399,#0ea5e9)"></span><span style="min-width:0"><b style="display:block">Studio Team</b><span style="color:var(--mv-fg-muted)">Kenji: mockup approved</span></span></div>
<div style="display:flex;gap:.6rem;align-items:center;padding:.55rem;border-radius:12px;background:var(--mv-surface)"><span aria-hidden="true" style="flex:none;width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#fbbf24,#f97316)"></span><span style="min-width:0"><b style="display:block">Mom</b><span style="color:var(--mv-fg-muted)">Got the photo ❤</span></span></div>
<div style="display:flex;gap:.6rem;align-items:center;padding:.55rem;border-radius:12px;background:var(--mv-surface)"><span aria-hidden="true" style="flex:none;width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#60a5fa,#6366f1)"></span><span style="min-width:0"><b style="display:block">Boston Carpool</b><span style="color:var(--mv-fg-muted)">Pickup confirmed for 8:15</span></span></div>
</div>
</div>
<!-- iPad landscape: dashboard -->
<div class="mv-device" data-device="ipad" data-orientation="landscape" style="--mv-device-width:27rem">
<div class="mv-device-screen" style="display:grid;grid-template-columns:5.5rem 1fr;font:11px/1.3 var(--mv-font-sans)">
<nav aria-hidden="true" style="display:grid;align-content:start;gap:.35rem;padding:.9rem .6rem;background:var(--mv-bg-muted);color:var(--mv-fg-muted)"><b style="color:var(--mv-fg);margin-bottom:.3rem">Lumen</b><span style="padding:.3rem .4rem;border-radius:6px;background:var(--mv-accent-subtle);color:var(--mv-accent-fg)">Overview</span><span style="padding:.3rem .4rem">Sales</span><span style="padding:.3rem .4rem">Customers</span><span style="padding:.3rem .4rem">Settings</span></nav>
<div style="padding:.9rem;display:grid;gap:.6rem;align-content:start">
<b style="font-size:14px">This month</b>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:.45rem"><span style="padding:.5rem;border-radius:8px;border:1px solid var(--mv-border)">Revenue<b style="display:block;font-size:14px">$48,210</b></span><span style="padding:.5rem;border-radius:8px;border:1px solid var(--mv-border)">Orders<b style="display:block;font-size:14px">1,284</b></span><span style="padding:.5rem;border-radius:8px;border:1px solid var(--mv-border)">Avg. order<b style="display:block;font-size:14px">$37.50</b></span></div>
<div aria-hidden="true" style="display:flex;align-items:flex-end;gap:5px;height:6.5rem;padding:.6rem;border-radius:8px;border:1px solid var(--mv-border)"><span style="flex:1;height:38%;border-radius:3px;background:var(--mv-accent-subtle)"></span><span style="flex:1;height:52%;border-radius:3px;background:var(--mv-accent-subtle)"></span><span style="flex:1;height:45%;border-radius:3px;background:var(--mv-accent-subtle)"></span><span style="flex:1;height:66%;border-radius:3px;background:var(--mv-accent-subtle)"></span><span style="flex:1;height:58%;border-radius:3px;background:var(--mv-accent-subtle)"></span><span style="flex:1;height:78%;border-radius:3px;background:var(--mv-accent-subtle)"></span><span style="flex:1;height:70%;border-radius:3px;background:var(--mv-accent-subtle)"></span><span style="flex:1;height:92%;border-radius:3px;background:var(--mv-accent)"></span></div>
<div style="display:flex;justify-content:space-between;color:var(--mv-fg-muted)"><span>Last order · 2 min ago</span><b style="color:var(--mv-success)">+12.4%</b></div>
</div>
</div>
</div>
</div>
</div>
<div style="display:flex;flex-wrap:wrap;gap:3rem;align-items:flex-end;justify-content:center;width:100%">
<!-- MacBook: landing page -->
<div class="mv-device" data-device="macbook" style="--mv-device-width:30rem">
<div class="mv-device-screen" style="display:grid;place-items:center;text-align:center;background:radial-gradient(80% 70% at 50% 0%,var(--mv-accent-subtle),transparent),var(--mv-bg);font:12px/1.4 var(--mv-font-sans);padding:1.2rem">
<div>
<span style="display:inline-block;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--mv-border);color:var(--mv-fg-muted);font-size:10px">New · Version 3.0</span>
<p style="margin:.6rem 0 .3rem;font:700 22px/1.1 var(--mv-font-sans);letter-spacing:-.03em">Design faster,<br>ship better.</p>
<p style="margin:0 0 .8rem;color:var(--mv-fg-muted)">Production-ready components.</p>
<span style="display:inline-block;padding:.45rem .9rem;border-radius:8px;background:var(--mv-accent);color:var(--mv-fg-on-accent);font-weight:600">Get started</span>
</div>
</div>
</div>
<!-- Browser: address bar, follows the theme -->
<div class="mv-device" data-device="browser" data-url="lumen.studio/pricing" style="--mv-device-width:30rem">
<div class="mv-device-screen" style="padding:1.2rem;display:grid;gap:.8rem;font:12px/1.4 var(--mv-font-sans)">
<div style="text-align:center"><b style="font-size:16px;letter-spacing:-.02em">Simple pricing</b><p style="margin:.2rem 0 0;color:var(--mv-fg-muted)">No commitment, cancel anytime.</p></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.6rem">
<div style="padding:.8rem;border-radius:10px;border:1px solid var(--mv-border)"><b>Solo</b><p style="margin:.2rem 0;font-size:18px;font-weight:700">$9<small style="font-size:11px;color:var(--mv-fg-muted);font-weight:400"> /mo</small></p><span style="color:var(--mv-fg-muted)">1 project · 5 GB</span></div>
<div style="padding:.8rem;border-radius:10px;border:1px solid var(--mv-accent);background:var(--mv-accent-subtle)"><b>Studio</b><p style="margin:.2rem 0;font-size:18px;font-weight:700">$29<small style="font-size:11px;color:var(--mv-fg-muted);font-weight:400"> /mo</small></p><span style="color:var(--mv-fg-muted)">Unlimited · 200 GB</span></div>
</div>
</div>
</div>
</div>
</div>API
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
data-device | iphone | android | ipad | macbook | browser | Device type. | |
data-tone | dark | light | dark (browser: follows the theme) | Dark or light bezels; for browser, forces a light or dark toolbar. |
data-orientation | portrait | landscape | portrait | ipad: orientation. |
data-url | string | browser: address bar text (no address bar without the attribute). |
CSS classes
| Name | Description |
|---|---|
mv-device | Frame; every dimension (bezels, radii, buttons) scales with its width. |
mv-device-screen | Screen area (direct child): clips its content; img/video/iframe fill the screen. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-device-width | 18rem · 30rem (ipad) · 38rem (landscape) · 40rem (macbook) · 100% (browser) | Reference width. |
--mv-device-ratio | 9/19.5 · 9/20 · 3/4 · 16/10 · auto | Screen aspect ratio. |
--mv-device-bezel / --mv-device-frame / --mv-device-button | Colors of the glass, metal frame and buttons (artistic). | |
--mv-device-screen-bg | var(--mv-bg) | Screen background. |
--mv-device-shadow | var(--mv-shadow-xl) | Drop shadow. |
--mv-device-toolbar / --mv-device-url-bg | browser: toolbar and address field. | |
--mv-device-close / --mv-device-minimize / --mv-device-zoom | browser: traffic lights. |
Accessibility
Purely decorative: the chrome is made of pseudo-elements, and only the screen content is exposed. The address bar (data-url) is generated by CSS and may not be read out: give the content a title or alt text if the URL matters. No animation.