
Mobile App
- 269 installs
- 83.7k repo stars
- Updated August 5, 2026
- nexu-io/open-design
mobile-app is an Open Design skill that scaffolds pixel-accurate iPhone 15 Pro mobile screen mockups from six layout archetypes for developers who need native-feeling app UI before production mobile code.
About
mobile-app is an Open Design skill in nexu-io/open-design that renders a single mobile screen inside a pixel-accurate iPhone 15 Pro device frame as exportable HTML. Agents copy the seed assets/template.html, map active DESIGN.md tokens to six :root CSS variables, and paste one of six screen archetypes from references/layouts.md—Feed, Detail, Onboarding, Profile, Checkout, or Focus—while preserving the Dynamic Island, status bar SVG icons, home indicator, and tab bar already drawn in the template. A references/checklist.md enforces P0 anti-fake-device gates so previews keep real device chrome instead of reimplemented inline frames. Developers reach for mobile-app when a brief asks for mobile app, iOS app, Android app, phone screen, or app UI explorations tied to Open Design brand systems. Output lands as index.html in the project root with one accent color used at most twice on screen. The workflow reads the active design system, fills bracketed copy from the brief, and self-reviews against layout and typography constraints before handoff to engineering.
- Screen scaffolds
- Touch targets
- Native navigation
- Component patterns
- Responsive mobile layout
Mobile App by the numbers
- 269 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #384 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nexu-io/open-design --skill mobile-appAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 269 |
|---|---|
| repo stars | ★ 83.7k |
| Last updated | August 5, 2026 |
| Repository | nexu-io/open-design ↗ |
How do you mock up native mobile app screens quickly?
Scaffold mobile app screen layouts with native-feeling navigation, touch targets, and component patterns for iOS- or Android-style product UIs.
Who is it for?
Developers or designers prototyping iOS-style product screens inside Open Design before committing to SwiftUI, Jetpack Compose, or React Native code.
Skip if: Production mobile builds requiring real navigation controllers, accessibility audits, or platform-native component libraries without a design-system pass.
When should I use this skill?
The brief mentions mobile app, iOS app, Android app, phone screen, or app UI inside an Open Design project.
What you get
index.html mobile mockup with iPhone 15 Pro device frame, DESIGN.md token mapping, and a chosen Feed/Detail/Onboarding/Profile/Checkout/Focus archetype.
- index.html mobile screen mockup
- DESIGN.md token mapping
By the numbers
- Includes 6 screen archetypes in references/layouts.md
- Maps design tokens through six :root CSS variables in the seed template
Files
Mobile App Skill
Produce a single mobile-app screen mockup, framed inside a real-feeling iPhone 15 Pro device.
Resource map
mobile-app/
├── SKILL.md ← you're reading this
├── assets/
│ └── template.html ← seed: device frame + screen primitives (READ FIRST)
└── references/
├── layouts.md ← 6 screen archetypes (Feed / Detail / Onboarding / Profile / Checkout / Focus)
└── checklist.md ← P0/P1/P2 self-review (anti-fake-device)Workflow
Step 0 — Pre-flight
1. Read `assets/template.html` end-to-end through the <style> block. The Dynamic Island, status bar SVG icons, home indicator, side rails, and tab bar are all already drawn in HTML/SVG — do not re-implement them inline on each screen. 2. Read `references/layouts.md` so you know which 6 archetypes exist. 3. Read the active DESIGN.md — map its tokens to the six :root variables in the seed.
Step 1 — Copy the seed
Copy assets/template.html to the project root as index.html. Replace the six :root variables with the active design system's tokens. Replace the page <title> and the caption above the device.
Step 2 — Pick exactly one archetype
| Brief language | Use |
|---|---|
| feed, inbox, timeline, list, messages, notifications | A — Feed |
| article, post, item, recipe, song, product, song detail | B — Detail |
| sign-up, welcome, intro, walkthrough, tour | C — Onboarding |
| profile, account, user page, someone's bio | D — Profile |
| checkout, payment, order, form, settings step | E — Checkout |
| timer, map, dashboard widget, single big number | F — Focus / hero card |
A mobile screen does one job. If the brief seems to combine two, ship one screen and offer the other as a follow-up.
Step 3 — Paste and fill
Copy the archetype block from layouts.md into <main class="content">, replacing the placeholder card. Fill bracketed text with real, specific copy from the brief. Drop the `<nav class="tabbar">` block entirely for archetypes that don't show one (B, C, E).
Step 4 — Self-check
Run through references/checklist.md. Pay extra attention to:
- Frame still has the Dynamic Island, status bar SVGs, and home indicator
- Tap targets ≥ 44px
- One accent, used ≤ 2× on the screen
- Display headings still use
var(--font-display)(serif)
Step 5 — Emit the artifact
<artifact identifier="mobile-slug" type="text/html" title="Mobile — Screen Name">
<!doctype html>
<html>...</html>
</artifact>One sentence before describing what's there. Stop after </artifact>.
Hard rules
- The phone is real. Dynamic Island gap, SVG status icons, home indicator. The seed protects all three — don't rewrite the frame.
- Single screen, single job. No multi-tab tours, no spliced flows.
- Accent budget = 2. One active tab + one primary action is the default.
- Numerics in mono via
.numclass. - Display in serif via
var(--font-display). - No external images — use
.ph-imgplaceholders.
<!doctype html>
<!--
OD mobile-app seed.
A pixel-accurate iPhone 15 Pro frame (390 × 844) with Dynamic Island,
status-bar SVG icons, and home indicator — drawn entirely in HTML/SVG, no
external image. The screen content lives inside `<main class="screen">`;
paste in one of the layouts from `references/layouts.md`.
Tokens at the top of `<style>` mirror the web-prototype seed so a single
DESIGN.md flows into both. Mobile spacing is tighter (~25%) and type sizes
drop one step from desktop — all pre-applied here.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>[REPLACE] Screen name · brand</title>
<style>
:root {
--bg: #fafaf7;
--surface: #ffffff;
--fg: #1a1916;
--muted: #6b6964;
--border: #e8e5df;
--accent: #c96442;
--accent-soft: color-mix(in oklch, var(--accent) 14%, transparent);
--fg-soft: color-mix(in oklch, var(--fg) 6%, transparent);
--font-display: 'Iowan Old Style', 'Charter', Georgia, serif;
--font-body: -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
--font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
/* mobile type — one step down from web-prototype defaults */
--fs-h1: 26px;
--fs-h2: 20px;
--fs-h3: 16px;
--fs-body: 15px;
--fs-meta: 12px;
--radius-card: 18px;
--radius-pill: 999px;
}
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
background:
radial-gradient(60% 80% at 50% 0%, color-mix(in oklch, var(--accent) 6%, var(--bg)) 0%, var(--bg) 60%);
color: var(--fg);
font-family: var(--font-body);
font-size: var(--fs-body);
line-height: 1.4;
-webkit-font-smoothing: antialiased;
display: grid;
place-items: center;
padding: 32px;
}
/* ─── caption above the device ──────────────────────────────────── */
.stage {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.caption {
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
.caption strong { color: var(--fg); font-weight: 500; }
/* ─── device frame ──────────────────────────────────────────────── */
.device {
position: relative;
width: 390px;
height: 844px;
border-radius: 56px;
padding: 12px;
background:
linear-gradient(160deg, #2a2a2c 0%, #1a1a1c 50%, #0e0e10 100%);
box-shadow:
0 0 0 1px rgba(255,255,255,0.04) inset,
0 0 0 2px #000 inset,
0 28px 60px -12px rgba(0,0,0,0.45),
0 8px 20px -8px rgba(0,0,0,0.35);
isolation: isolate;
}
/* metallic side rails */
.device::before, .device::after {
content: '';
position: absolute;
width: 3px;
background: linear-gradient(to bottom, transparent 0%, rgba(255,255,255,0.06) 8%, transparent 16%, transparent 84%, rgba(255,255,255,0.04) 92%, transparent 100%);
top: 100px;
bottom: 100px;
pointer-events: none;
}
.device::before { left: -1px; }
.device::after { right: -1px; }
/* Dynamic Island */
.island {
position: absolute;
top: 22px;
left: 50%;
transform: translateX(-50%);
width: 124px;
height: 36px;
background: #000;
border-radius: 999px;
z-index: 5;
}
/* hardware buttons (subtle) */
.btn-rail {
position: absolute;
width: 4px;
background: #0a0a0c;
border-radius: 2px;
}
.btn-rail.left-1 { left: -3px; top: 174px; height: 32px; } /* silent */
.btn-rail.left-2 { left: -3px; top: 220px; height: 60px; } /* vol+ */
.btn-rail.left-3 { left: -3px; top: 290px; height: 60px; } /* vol- */
.btn-rail.right-1 { right: -3px; top: 250px; height: 100px; } /* power */
/* ─── screen surface ────────────────────────────────────────────── */
.screen {
position: relative;
width: 100%; height: 100%;
background: var(--bg);
border-radius: 44px;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Status bar — 47px to clear the island. SF-style time, signal/wifi/battery SVG. */
.statusbar {
flex: 0 0 47px;
padding: 18px 26px 0;
display: flex;
align-items: flex-start;
justify-content: space-between;
font-family: var(--font-body);
font-size: 15px;
font-weight: 600;
color: var(--fg);
letter-spacing: -0.01em;
}
.statusbar .right { display: inline-flex; align-items: center; gap: 6px; }
.statusbar svg { width: 17px; height: 11px; fill: var(--fg); }
.statusbar .battery { width: 25px; }
/* Content region — owns its scroll, frame stays still */
.content {
flex: 1 1 auto;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
padding: 8px 0 28px;
}
.content::-webkit-scrollbar { display: none; }
/* Home indicator (must always be the last visible thing) */
.home-indicator {
flex: 0 0 28px;
position: relative;
}
.home-indicator::after {
content: '';
position: absolute;
left: 50%; bottom: 8px;
transform: translateX(-50%);
width: 134px; height: 5px;
background: var(--fg);
border-radius: 999px;
opacity: 0.85;
}
/* ─── screen primitives — used by layouts.md ────────────────────── */
.pad { padding-inline: 20px; }
.stack { display: flex; flex-direction: column; gap: 16px; }
.row { display: flex; align-items: center; gap: 12px; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.header {
padding: 8px 20px 12px;
display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.header h1 {
font-family: var(--font-display);
font-size: var(--fs-h1);
letter-spacing: -0.02em;
line-height: 1.1;
margin: 0;
}
.header .icon-btn {
width: 36px; height: 36px;
border-radius: 999px;
background: var(--surface);
border: 1px solid var(--border);
display: grid; place-items: center;
color: var(--fg);
}
.header .icon-btn svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.7; }
.greeting {
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
margin: 0 0 4px;
}
.h2 { font-family: var(--font-display); font-size: var(--fs-h2); letter-spacing: -0.015em; line-height: 1.2; margin: 0; }
.h3 { font-size: var(--fs-h3); font-weight: 600; line-height: 1.3; margin: 0; }
.meta { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--muted); }
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
/* card */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-card);
padding: 16px;
}
.card.accent {
background: var(--accent);
color: #fff;
border-color: transparent;
}
.card.accent .meta { color: rgba(255,255,255,0.72); }
.card.flat { background: transparent; border: 0; padding: 12px 0; border-top: 1px solid var(--border); border-radius: 0; }
.card.flat:first-child { border-top: 0; padding-top: 0; }
/* list row */
.list-row {
display: grid;
grid-template-columns: 40px 1fr auto;
align-items: center;
gap: 12px;
padding: 12px 0;
border-top: 1px solid var(--border);
}
.list-row:first-child { border-top: 0; }
.list-row .avatar {
width: 40px; height: 40px;
border-radius: 50%;
background:
linear-gradient(135deg, var(--accent-soft), var(--fg-soft)),
var(--surface);
border: 1px solid var(--border);
}
.list-row .body .title { font-size: 15px; font-weight: 500; line-height: 1.25; }
.list-row .body .sub { color: var(--muted); font-size: 13px; line-height: 1.3; margin-top: 2px; }
/* tab bar */
.tabbar {
flex: 0 0 auto;
display: grid;
grid-template-columns: repeat(var(--tabs, 4), 1fr);
padding: 8px 8px 0;
border-top: 1px solid var(--border);
background: color-mix(in oklch, var(--surface) 92%, transparent);
backdrop-filter: blur(20px);
}
.tab {
display: flex; flex-direction: column; align-items: center; gap: 2px;
padding: 8px 0;
color: var(--muted);
font-size: 10px;
letter-spacing: 0.02em;
}
.tab.active { color: var(--accent); }
.tab svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.7; }
.tab.active svg { stroke-width: 2; }
/* primary button — full-width, 48px tap target */
.btn-primary {
display: flex; align-items: center; justify-content: center;
width: 100%;
min-height: 48px;
padding: 14px 20px;
background: var(--accent);
color: #fff;
border: 0;
border-radius: 14px;
font: inherit;
font-size: 15px;
font-weight: 600;
letter-spacing: -0.005em;
cursor: pointer;
}
.btn-secondary {
display: flex; align-items: center; justify-content: center;
width: 100%;
min-height: 48px;
padding: 14px 20px;
background: transparent;
color: var(--fg);
border: 1px solid var(--border);
border-radius: 14px;
font: inherit;
font-size: 15px;
font-weight: 500;
}
/* image placeholder */
.ph-img {
background:
linear-gradient(135deg, var(--accent-soft), var(--fg-soft)),
var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
aspect-ratio: 4 / 3;
display: grid; place-items: center;
color: var(--muted);
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.04em;
}
.ph-img.square { aspect-ratio: 1 / 1; }
.ph-img.wide { aspect-ratio: 16 / 9; }
/* pill / tag */
.pill {
display: inline-flex; align-items: center; gap: 4px;
padding: 4px 10px;
background: var(--accent-soft);
color: var(--accent);
border-radius: 999px;
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.tag {
display: inline-flex;
padding: 3px 9px;
background: transparent;
color: var(--muted);
border: 1px solid var(--border);
border-radius: 999px;
font-size: 11px;
}
/* progress */
.progress { height: 6px; background: rgba(255,255,255,0.25); border-radius: 999px; overflow: hidden; }
.progress > span { display: block; height: 100%; background: #fff; }
</style>
</head>
<body>
<div class="stage">
<div class="caption"><strong>[REPLACE] App</strong> · [REPLACE] Screen name</div>
<div class="device" data-od-id="device">
<span class="btn-rail left-1" aria-hidden></span>
<span class="btn-rail left-2" aria-hidden></span>
<span class="btn-rail left-3" aria-hidden></span>
<span class="btn-rail right-1" aria-hidden></span>
<span class="island" aria-hidden></span>
<div class="screen">
<!-- ─── Status bar ─── -->
<div class="statusbar">
<span class="num">9:41</span>
<span class="right">
<!-- signal -->
<svg viewBox="0 0 17 11" aria-hidden>
<rect x="0" y="7" width="3" height="4" rx="0.6"/>
<rect x="4" y="5" width="3" height="6" rx="0.6"/>
<rect x="8" y="3" width="3" height="8" rx="0.6"/>
<rect x="12" y="0" width="3" height="11" rx="0.6"/>
</svg>
<!-- wifi -->
<svg viewBox="0 0 17 11" aria-hidden>
<path d="M8.5 1.5C5.5 1.5 2.7 2.6 0.5 4.6L2 6.1C3.8 4.5 6.1 3.6 8.5 3.6c2.4 0 4.7 0.9 6.5 2.5l1.5-1.5c-2.2-2-5-3.1-8-3.1zM3.5 7.6L5 9.1c1-0.9 2.2-1.4 3.5-1.4 1.3 0 2.5 0.5 3.5 1.4l1.5-1.5c-1.4-1.3-3.1-2-5-2-1.9 0-3.6 0.7-5 2zM6.5 10.6l2 2 2-2c-0.5-0.5-1.2-0.8-2-0.8s-1.5 0.3-2 0.8z"/>
</svg>
<!-- battery -->
<svg class="battery" viewBox="0 0 25 11" aria-hidden>
<rect x="0.5" y="0.5" width="21" height="10" rx="2.5" fill="none" stroke="currentColor" stroke-opacity="0.45"/>
<rect x="22" y="3.5" width="1.5" height="4" rx="0.4" fill="currentColor" fill-opacity="0.45"/>
<rect x="2" y="2" width="18" height="7" rx="1.4"/>
</svg>
</span>
</div>
<!-- ─── Scrollable content (paste a layout from references/layouts.md HERE) ─── -->
<main class="content" data-od-id="content">
<div class="header" data-od-id="header">
<div>
<p class="greeting">Tuesday · April 22</p>
<h1>[REPLACE] Hi there.</h1>
</div>
<button class="icon-btn" aria-label="Settings">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>
</button>
</div>
<div class="pad stack" data-od-id="empty-slot">
<div class="card" style="text-align: center; padding: 28px 20px;">
<p class="meta" style="margin: 0 0 6px;">PASTE A LAYOUT FROM</p>
<p class="h3" style="margin: 0 0 6px;">references/layouts.md</p>
<p style="margin: 0; color: var(--muted); font-size: 13px;">into <code style="font-family: var(--font-mono);"><main class="content"></code></p>
</div>
</div>
</main>
<!-- ─── Tab bar (drop if the screen kind doesn't have one) ─── -->
<nav class="tabbar" style="--tabs: 4;" data-od-id="tabbar">
<a class="tab active">
<svg viewBox="0 0 24 24"><path d="M3 12 12 3l9 9"/><path d="M5 10v10h14V10"/></svg>
Home
</a>
<a class="tab">
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
Search
</a>
<a class="tab">
<svg viewBox="0 0 24 24"><path d="M22 12c0 5.5-4.5 10-10 10S2 17.5 2 12 6.5 2 12 2s10 4.5 10 10z"/><path d="M12 6v6l4 2"/></svg>
Activity
</a>
<a class="tab">
<svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-7 8-7s8 3 8 7"/></svg>
Profile
</a>
</nav>
<div class="home-indicator" aria-hidden></div>
</div>
</div>
</div>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tomato — focus screen</title>
<style>
:root {
--bg: #fafaf9; --fg: #1c1b1a; --muted: #6b6964; --border: #e6e4e0;
--accent: #c96442; --surface: #ffffff;
}
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; background: var(--bg); display: flex; align-items: center; justify-content: center; padding: 32px; font: 14px/1.5 -apple-system, system-ui, sans-serif; color: var(--fg); }
.frame { width: 390px; height: 844px; background: black; border-radius: 56px; padding: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.18); position: relative; }
.frame::before { content: ''; position: absolute; top: 22px; left: 50%; transform: translateX(-50%); width: 124px; height: 36px; background: black; border-radius: 999px; z-index: 5; }
.screen { width: 100%; height: 100%; background: var(--bg); border-radius: 44px; overflow: hidden; display: flex; flex-direction: column; }
.status { padding: 14px 24px 6px; display: flex; justify-content: space-between; align-items: center; font-size: 14px; font-weight: 600; }
.status .right { display: flex; gap: 6px; align-items: center; }
.header { padding: 56px 24px 24px; }
.header .greeting { color: var(--muted); font-size: 14px; margin: 0 0 4px; }
.header h1 { margin: 0; font-size: 22px; letter-spacing: -0.01em; }
.timer-card { margin: 12px 24px; background: var(--accent); color: white; border-radius: 24px; padding: 28px 24px; text-align: center; }
.timer-card .label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.85; margin: 0 0 4px; }
.timer-card .countdown { font-size: 64px; line-height: 1; letter-spacing: -0.03em; font-weight: 600; margin: 6px 0 18px; font-variant-numeric: tabular-nums; }
.timer-card .progress { height: 6px; background: rgba(255,255,255,0.25); border-radius: 999px; overflow: hidden; margin-bottom: 16px; }
.timer-card .progress > span { display: block; width: 38%; height: 100%; background: white; }
.timer-card .actions { display: flex; gap: 10px; justify-content: center; }
.timer-card button { font: inherit; cursor: pointer; padding: 10px 22px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.4); background: rgba(255,255,255,0.12); color: white; font-weight: 500; }
.timer-card button.primary { background: white; color: var(--accent); border-color: white; }
.section { padding: 18px 24px 0; }
.section .label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 10px; }
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.stat { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 12px; }
.stat .v { font-size: 22px; letter-spacing: -0.01em; line-height: 1; margin-bottom: 4px; }
.stat .l { font-size: 11px; color: var(--muted); }
.tasks { padding: 18px 24px 8px; }
.task { display: flex; align-items: center; gap: 12px; padding: 14px 0; border-top: 1px solid var(--border); }
.task:first-child { border-top: none; }
.check { width: 20px; height: 20px; border: 1.5px solid var(--border); border-radius: 50%; flex-shrink: 0; }
.task.done .check { background: var(--accent); border-color: var(--accent); }
.task.done .check::after { content: '✓'; color: white; font-size: 13px; display: block; text-align: center; line-height: 18px; }
.task .body { flex: 1; }
.task .title { font-size: 14.5px; line-height: 1.3; }
.task.done .title { color: var(--muted); text-decoration: line-through; }
.task .meta { font-size: 11px; color: var(--muted); margin-top: 2px; }
.tabbar { margin-top: auto; display: grid; grid-template-columns: repeat(4, 1fr); padding: 10px 16px 28px; border-top: 1px solid var(--border); background: var(--surface); }
.tab { text-align: center; color: var(--muted); font-size: 11px; padding: 6px 0; }
.tab.active { color: var(--accent); font-weight: 500; }
.tab .icon { font-size: 18px; line-height: 1; margin-bottom: 4px; }
</style>
</head>
<body>
<div class="frame" data-od-id="frame">
<div class="screen">
<div class="status"><span>9:41</span><span class="right">·· 5G · 100%</span></div>
<div class="header" data-od-id="header">
<p class="greeting">Tuesday · April 22</p>
<h1>Two pomodoros to lunch.</h1>
</div>
<div class="timer-card" data-od-id="timer">
<p class="label">Focus session</p>
<div class="countdown">15:42</div>
<div class="progress"><span></span></div>
<div class="actions">
<button>Skip</button>
<button class="primary">Pause</button>
</div>
</div>
<div class="section" data-od-id="stats">
<p class="label">Today</p>
<div class="stats">
<div class="stat"><div class="v">3</div><div class="l">Sessions</div></div>
<div class="stat"><div class="v">75m</div><div class="l">Focused</div></div>
<div class="stat"><div class="v">2</div><div class="l">Tasks done</div></div>
</div>
</div>
<div class="tasks" data-od-id="tasks">
<p class="section label" style="padding: 0;">Up next</p>
<div class="task done"><div class="check"></div><div class="body"><div class="title">Review Q2 OKRs</div><div class="meta">25m · completed</div></div></div>
<div class="task"><div class="check"></div><div class="body"><div class="title">Draft sync-engine post</div><div class="meta">2 sessions estimated</div></div></div>
<div class="task"><div class="check"></div><div class="body"><div class="title">1:1 prep with Mira</div><div class="meta">1 session</div></div></div>
</div>
<nav class="tabbar" data-od-id="tabbar">
<div class="tab active"><div class="icon">⏱</div>Focus</div>
<div class="tab"><div class="icon">✓</div>Tasks</div>
<div class="tab"><div class="icon">📊</div>Stats</div>
<div class="tab"><div class="icon">⚙</div>Settings</div>
</nav>
</div>
</div>
</body>
</html>
Mobile app checklist
Run this before emitting <artifact>. P0 must pass.
P0 — must pass
- [ ] Frame looks like a phone, not a generic card. Dynamic Island visible, status bar SVG icons present (signal/wifi/battery), home indicator at bottom. The seed already does this — verify you didn't accidentally delete the island/rails/indicator markup.
- [ ] Status bar shows real glyphs, not text like
· · · 5G · 100%. Use the SVG icons from the seed. - [ ] Home indicator is the last visible thing. Anything below it (e.g. extra padding, accidental
<div>) breaks the illusion. - [ ] Content scrolls, frame doesn't.
<main class="content">hasoverflow-y: auto; the surrounding.devicedoes not. The page background never moves. - [ ] Tap targets ≥ 44px tall. The seed's
.btn-primary(48px),.tab(~50px),.icon-btn(36px ≥ touch with padding),.list-row(≥48px with padding) all pass. Don't ship a button under 44px. - [ ] Body text ≥ 14px.
--fs-body: 15pxalready enforces this on most copy. List-row sub text uses 13px max — that's the floor. - [ ] One accent, used at most twice on the screen. Typically: one active tab + one CTA, OR one accent card + one tab. Never three.
- [ ] No external image URLs. Use the
.ph-imgplaceholder class. External CDN images break the OD preview iframe and look fake when they 404. - [ ] Tab bar matches the screen kind. Onboarding / detail / checkout: drop the
<nav class="tabbar">entirely. Feed / focus / profile: keep it. - [ ] Display headlines use `var(--font-display)` (serif). The seed binds this via
.h1,.h2,.header h1. Don't override headings to system-sans — it instantly looks like a stock template. - [ ] No emoji icons in the UI. SVG monoline only. Emoji in copy is fine ("9:41 ☀️ Tuesday" is not, but "Sunny day in Berlin" is).
- [ ] `data-od-id` on the device, content, header, and any major sections.
P1 — should pass
- [ ] One screen, one job. A profile screen does profile things. Don't graft a checkout form onto a feed.
- [ ] Caption above the device names the screen (e.g. "FILEBASE · INBOX"). The seed already has the slot — fill it.
- [ ] Status bar time is `9:41` (Apple convention) unless the brief asks otherwise.
- [ ] Mono font for numerics — counts, prices, durations, dates. The seed's
.numclass binds this. - [ ] Real, specific copy. "Mira Hassan · CTO" beats "User Name". "$1,920" beats "$X,XXX".
- [ ] First-screen content fits inside the 844px frame without requiring scroll for the primary action. If the CTA is below the fold, it's the wrong layout.
P2 — nice to have
- [ ] Subtle accent radial gradient on the page background (already in seed). Removing it makes the device feel pasted onto a flat sheet.
- [ ] Backdrop-blurred tab bar (already in seed via
backdrop-filter). - [ ] At most one image placeholder per screen. Two placeholders on a small canvas competes for attention.
- [ ] Subtle metallic side rails on the bezel (already in seed via
::before/::after).
Anti-fake-device checklist
If any of these are true, the screen looks like a card pretending to be a phone rather than a phone:
- The device's outer corners aren't visibly more rounded (~56px) than the inner screen (~44px).
- There's no Dynamic Island gap at the top centre.
- The status bar text is grey or low-opacity (it should be
var(--fg)at full strength). - The home indicator is missing.
- The bottom tab bar has no top border or no backdrop blur.
The seed prevents all of these — the most common regression is the agent rewriting the frame with border-radius: 24px and losing the depth.
Mobile app layouts
6 paste-ready screen archetypes. Drop into <main class="content"> of assets/template.html. Don't write screens from scratch — pick the closest archetype, paste, swap copy.
Pre-flight
1. Read `assets/template.html` at minimum through the <style> block — every class below is defined there. The Dynamic Island, status bar, home indicator, and tab bar are already drawn; do not re-implement them inline. 2. Pick exactly one archetype. A mobile screen does one job. Mixing "feed + checkout + profile" into one mock is the #1 reason mobile prototypes feel fake. 3. If the archetype implies a tab bar, keep it; otherwise delete the entire `<nav class="tabbar">` block. Onboarding, detail, and checkout screens generally don't show one.
Class inventory
padstackrowrow-betweengrid-2grid-3headergreetingh2h3metanumcardcard.accentcard.flatlist-rowavatartagpilltabbartabtab.activebtn-primarybtn-secondaryph-imgprogress
If you reach for a class not on this list, define it in the seed's <style> first.
---
Archetype A — Feed (home / for-you / inbox)
Top: greeting + title. Body: 4–6 list rows, hairline-separated. Tab bar: yes.
<div class="header" data-od-id="header">
<div>
<p class="greeting">Tuesday · April 22</p>
<h1>Inbox</h1>
</div>
<button class="icon-btn" aria-label="Compose">
<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
</button>
</div>
<section class="pad" data-od-id="filters" style="margin-bottom: 8px;">
<div class="row" style="overflow-x: auto; padding-bottom: 4px;">
<span class="pill">All · 14</span>
<span class="tag">Mentions</span>
<span class="tag">Following</span>
<span class="tag">Shared</span>
</div>
</section>
<section class="pad" data-od-id="feed">
<div class="list-row">
<div class="avatar"></div>
<div class="body">
<div class="title">Mira Hassan · Sync engine v3 review</div>
<div class="sub">"Merged the chunker — egress is down 38% on Northwind."</div>
</div>
<span class="meta">2m</span>
</div>
<div class="list-row">
<div class="avatar"></div>
<div class="body">
<div class="title">#engineering · 7 new replies</div>
<div class="sub">Latency spike between 03:40 and 04:10 — probably the cron.</div>
</div>
<span class="meta">14m</span>
</div>
<div class="list-row">
<div class="avatar"></div>
<div class="body">
<div class="title">Northwind Studios · Invoice paid</div>
<div class="sub">$2,184 · April · auto-receipt sent to billing@</div>
</div>
<span class="meta">1h</span>
</div>
<div class="list-row">
<div class="avatar"></div>
<div class="body">
<div class="title">Daniel Park · Re: Next Tuesday's review</div>
<div class="sub">"I'll have the Q2 numbers by Monday EOD."</div>
</div>
<span class="meta">3h</span>
</div>
</section>Archetype B — Detail (single item)
Hero image up top, eyebrow + title + meta, body text, primary action floating at the bottom. Tab bar: no.
<div class="ph-img wide" style="border-radius: 0; aspect-ratio: 4/3;" data-od-id="hero">[ Hero image ]</div>
<section class="pad" style="padding-top: 18px;" data-od-id="meta">
<span class="pill">Studio session</span>
<h1 class="h2" style="margin: 10px 0 6px;">Filebase v3 — what we shipped, what we cut.</h1>
<p class="meta">Mira Hassan · April 22 · 9 min read</p>
</section>
<section class="pad stack" style="margin-top: 18px; gap: 14px;" data-od-id="body">
<p>The biggest unlock in v3 was the new content-defined chunker. On Final Cut projects, post-edit re-uploads dropped 38× — from full multi-GB pushes to the few hundred KB that actually changed.</p>
<p>What we cut: per-folder compression. It looked great on benchmarks; on real footage it was slower than no compression at all because the chunker was already doing the dedup work.</p>
<p>Next quarter: dual-region replication on R2 + S3, rolling out to Enterprise first.</p>
</section>
<section class="pad" style="padding-top: 24px; padding-bottom: 8px;" data-od-id="cta">
<button class="btn-primary">Save to library</button>
</section>Archetype C — Onboarding (1 of N)
Illustration block + headline + subhead + paginator + primary CTA. Tab bar: no. Status bar still visible.
<section class="pad stack" style="height: 100%; padding-top: 24px; padding-bottom: 24px; gap: 24px;" data-od-id="onboarding">
<div class="ph-img square" style="aspect-ratio: 1/1; max-width: 240px; margin: 0 auto;">[ Illustration ]</div>
<div style="text-align: center;">
<p class="meta" style="margin: 0 0 6px;">STEP 2 OF 4</p>
<h1 style="font-family: var(--font-display); font-size: 26px; margin: 0 0 10px; letter-spacing: -0.02em; line-height: 1.15;">Sync only what changed.</h1>
<p style="margin: 0 auto; max-width: 26ch; color: var(--muted); font-size: 14px; line-height: 1.5;">No more 4 GB re-uploads when you fix one frame. We diff at the byte level so the network stays quiet.</p>
</div>
<!-- pagination dots -->
<div class="row" style="justify-content: center; gap: 6px;">
<span style="width: 6px; height: 6px; border-radius: 50%; background: var(--border);"></span>
<span style="width: 18px; height: 6px; border-radius: 999px; background: var(--accent);"></span>
<span style="width: 6px; height: 6px; border-radius: 50%; background: var(--border);"></span>
<span style="width: 6px; height: 6px; border-radius: 50%; background: var(--border);"></span>
</div>
<div class="stack" style="gap: 10px; margin-top: auto;">
<button class="btn-primary">Continue</button>
<button class="btn-secondary" style="border: 0; color: var(--muted);">Skip</button>
</div>
</section>Drop the <nav class="tabbar"> block from the seed for this archetype.Archetype D — Profile (someone's page)
Avatar + name + meta row; stat row; tabbed content underneath. Tab bar: yes (often the surrounding app's tabs).
<section class="pad" style="padding-top: 8px;" data-od-id="head">
<div class="row" style="gap: 16px;">
<div class="avatar" style="width: 64px; height: 64px;"></div>
<div>
<h1 class="h2" style="margin: 0;">Mira Hassan</h1>
<p class="meta" style="margin: 4px 0 0;">CTO · Northwind Studios · Joined 2024</p>
</div>
</div>
<div class="row" style="margin-top: 16px; gap: 8px;">
<button class="btn-secondary" style="flex: 1; min-height: 38px; font-size: 13px;">Message</button>
<button class="btn-secondary" style="flex: 1; min-height: 38px; font-size: 13px;">Follow</button>
</div>
</section>
<section class="pad" data-od-id="stats" style="margin-top: 18px;">
<div class="grid-3">
<div class="card flat" style="text-align: center;">
<div class="num" style="font-size: 22px; letter-spacing: -0.02em;">218</div>
<div class="meta">Posts</div>
</div>
<div class="card flat" style="text-align: center;">
<div class="num" style="font-size: 22px; letter-spacing: -0.02em;">3.1k</div>
<div class="meta">Followers</div>
</div>
<div class="card flat" style="text-align: center;">
<div class="num" style="font-size: 22px; letter-spacing: -0.02em;">142</div>
<div class="meta">Following</div>
</div>
</div>
</section>
<section class="pad" data-od-id="tabs" style="margin-top: 12px;">
<div class="row" style="border-bottom: 1px solid var(--border); gap: 24px;">
<span style="padding: 12px 0; border-bottom: 2px solid var(--accent); color: var(--fg); font-weight: 500; font-size: 14px;">Posts</span>
<span style="padding: 12px 0; color: var(--muted); font-size: 14px;">Replies</span>
<span style="padding: 12px 0; color: var(--muted); font-size: 14px;">Likes</span>
</div>
</section>
<section class="pad" data-od-id="post-list" style="margin-top: 4px;">
<div class="list-row" style="grid-template-columns: 1fr;">
<div class="body">
<div class="title">"Bandwidth pricing went up 4× — sync engine choice is no longer cosmetic."</div>
<div class="sub" style="margin-top: 6px;">2 days ago · 142 likes</div>
</div>
</div>
<div class="list-row" style="grid-template-columns: 1fr;">
<div class="body">
<div class="title">"Shipped v3 today. The team carried this one."</div>
<div class="sub" style="margin-top: 6px;">5 days ago · 88 likes</div>
</div>
</div>
</section>Archetype E — Checkout / form
Stacked card sections (item summary → details → totals), bottom-fixed CTA. Tab bar: no.
<section class="pad" style="padding-top: 12px;" data-od-id="title">
<h1 class="h2">Confirm order</h1>
</section>
<section class="pad" data-od-id="item">
<div class="card row" style="gap: 14px; align-items: flex-start;">
<div class="ph-img square" style="width: 64px; height: 64px; aspect-ratio: 1; border-radius: 10px;"></div>
<div style="flex: 1;">
<div class="h3">Filebase Team · annual</div>
<p class="meta" style="margin: 4px 0 0;">$4 / seat / month, billed yearly</p>
</div>
<span class="num">$1,920</span>
</div>
</section>
<section class="pad stack" data-od-id="details" style="margin-top: 14px; gap: 10px;">
<div class="card flat row-between">
<span>Seats</span>
<span class="num">40</span>
</div>
<div class="card flat row-between">
<span>Billing email</span>
<span class="meta">billing@northwind.studio</span>
</div>
<div class="card flat row-between">
<span>Payment</span>
<span class="meta">Visa · 4242</span>
</div>
</section>
<section class="pad" data-od-id="totals" style="margin-top: 14px;">
<div class="card row-between" style="border-top: 1px solid var(--fg); border-radius: 0; padding: 16px 0; background: transparent;">
<span style="font-weight: 600;">Total today</span>
<span class="num" style="font-size: 22px; letter-spacing: -0.01em;">$1,920</span>
</div>
</section>
<section class="pad" style="padding-top: 16px; padding-bottom: 12px;" data-od-id="cta">
<button class="btn-primary">Pay $1,920</button>
<p class="meta" style="text-align: center; margin: 12px 0 0;">By tapping Pay you agree to the terms.</p>
</section>Archetype F — Focus / hero card (timer, map, single tool)
A single accent-coloured hero card dominates; small supporting content underneath. Tab bar: yes.
<div class="header" data-od-id="header">
<div>
<p class="greeting">Tuesday · April 22</p>
<h1>Two pomodoros to lunch.</h1>
</div>
<button class="icon-btn" aria-label="Settings">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><circle cx="12" cy="3" r="0.5"/><circle cx="12" cy="21" r="0.5"/><circle cx="3" cy="12" r="0.5"/><circle cx="21" cy="12" r="0.5"/></svg>
</button>
</div>
<section class="pad" data-od-id="hero-card" style="margin-top: 4px;">
<div class="card accent" style="padding: 28px 24px; text-align: center;">
<p class="meta" style="margin: 0 0 6px; color: rgba(255,255,255,0.72);">FOCUS SESSION</p>
<div class="num" style="font-size: 64px; line-height: 1; letter-spacing: -0.03em; font-weight: 600; margin: 8px 0 18px;">15:42</div>
<div class="progress" style="margin-bottom: 18px;"><span style="width: 38%;"></span></div>
<div class="row" style="justify-content: center; gap: 8px;">
<button style="padding: 10px 22px; border: 1px solid rgba(255,255,255,0.4); background: rgba(255,255,255,0.12); color: #fff; border-radius: 999px; font: inherit; font-weight: 500;">Skip</button>
<button style="padding: 10px 22px; border: 0; background: #fff; color: var(--accent); border-radius: 999px; font: inherit; font-weight: 600;">Pause</button>
</div>
</div>
</section>
<section class="pad" data-od-id="stats-row" style="margin-top: 18px;">
<p class="meta" style="margin: 0 0 8px;">TODAY</p>
<div class="grid-3">
<div class="card"><div class="num" style="font-size: 22px;">3</div><div class="meta">Sessions</div></div>
<div class="card"><div class="num" style="font-size: 22px;">75m</div><div class="meta">Focused</div></div>
<div class="card"><div class="num" style="font-size: 22px;">2</div><div class="meta">Done</div></div>
</div>
</section>
<section class="pad" data-od-id="up-next" style="margin-top: 18px;">
<p class="meta" style="margin: 0 0 8px;">UP NEXT</p>
<div>
<div class="list-row" style="grid-template-columns: 22px 1fr auto;">
<span style="width: 18px; height: 18px; border-radius: 50%; background: var(--accent);"></span>
<div class="body">
<div class="title" style="text-decoration: line-through; color: var(--muted);">Review Q2 OKRs</div>
<div class="sub">25m · completed</div>
</div>
</div>
<div class="list-row" style="grid-template-columns: 22px 1fr auto;">
<span style="width: 18px; height: 18px; border-radius: 50%; border: 1.5px solid var(--border);"></span>
<div class="body">
<div class="title">Draft sync-engine post</div>
<div class="sub">2 sessions estimated</div>
</div>
</div>
</div>
</section>---
Choosing an archetype from a brief
| If the brief mentions… | Use |
|---|---|
| feed, inbox, timeline, list, messages | A — Feed |
| article, post, item, recipe, song, product | B — Detail |
| sign-up, welcome, intro, walkthrough | C — Onboarding |
| profile, account, user page, bio | D — Profile |
| checkout, payment, order, form, settings step | E — Checkout |
| timer, map, dashboard widget, single big number | F — Focus |
If two fit, pick the one that better matches the primary action the user takes on this screen.
Related skills
How it compares
Pick mobile-app over wireframe-sketch when the deliverable needs a polished phone-framed UI mockup rather than lo-fi graph-paper sketches.
FAQ
What device frame does mobile-app render?
mobile-app renders a pixel-accurate iPhone 15 Pro frame with Dynamic Island, status bar SVG icons, home indicator, and tab bar baked into assets/template.html so agents never re-implement device chrome inline.
How many screen layouts does mobile-app include?
mobile-app ships six screen archetypes in references/layouts.md—Feed, Detail, Onboarding, Profile, Checkout, and Focus—that agents paste into the template screen area after mapping DESIGN.md tokens.