From mobile-store-assets
Evidence-based visual rules for App Store and Google Play screenshots, feature graphics, and other store imagery. Covers composition, colour, contrast, and text readability — grounded in WCAG 2.2 web standards and recent ASO conversion data. Consult this skill whenever authoring or rendering store assets (used by /store-spec and /store-assets) so every layout, palette, and caption clears a minimum readability bar before it reaches a real store listing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/mobile-store-assets:creative-design-principlesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill is the **design brain**: how a store screenshot or feature
This skill is the design brain: how a store screenshot or feature graphic must look to convert. The other skills handle adjacent concerns:
aso-toolkit — what the copy saysios-store-requirements / android-store-requirements — pixel sizes, policy limitsstore-assets — rendering pipeline that enforces these rules at output timeEvery rule below is paired with the data that justifies it. When two guidelines conflict, prefer the one with the strongest measured CTR / conversion impact.
Numbers below are reproducible benchmarks from ASO tooling vendors and public case studies. Treat them as ranges, not guarantees.
| Lever | Reported lift | Source |
|---|---|---|
| Optimised screenshots vs. raw app screens | +20% to +35% install rate | AppTweak / SplitMetrics 2024–25 benchmarks |
| Average winning screenshot A/B test | +10% to +25% conversion | Sensor Tower, ASOMobile 2025 |
| Icon A/B winners (Google Play) | median +8% to +12% CVR (top decile +28%) | SplitMetrics |
| Custom Product Pages (iOS PPO) | up to +8.6% | Apple, Adapty 2026 benchmarks |
| High-contrast text vs. low-contrast | ~89% improvement in readability scores | Industry readability studies cited by Moburst, AppScreenshotStudio |
| Consistent palette across creatives | +80% brand recognition, +23% retention | Color Research & Application |
| Visitors who never scroll past slot 3 | ~90% | StoreMaven, SplitMetrics |
Two structural truths underneath those numbers:
Map slot → job. The renderer's story-arc.ts encodes this; visual
reference for every layout is in design/layouts-phone-portrait-shots.html
(plus the three sibling files for phone-landscape / tablet-portrait /
tablet-landscape).
| Slot | Job | Caption framing | Layout family (portrait) |
|---|---|---|---|
| 1 | Value — what is this | Benefit headline (not feature) | hero-type, headline-masthead |
| 2 | Flow — how it works | Verb-led ("Track in seconds") | caption-band, editorial-card |
| 3 | Trust — why believe you | Proof / reassurance | feature-stack, vertical-bento (stat tiles) |
| 4+ | Features / depth | One feature per screen | tilted-card, dual-phone, editorial-card, vertical-bento |
| Final | CTA | "Get started free" | hero-type (CTA variant) |
Recommended 10-slot sequence for the default ABCD matrix:
hero-type → caption-band → feature-stack → editorial-card → vertical-bento → dual-phone → tilted-card → caption-band → feature-stack → hero-type(CTA).
Older / superseded layout names (text-hero, caption-below, uppercase-top,
logo-tilted, aurora-hero, bento, editorial-split, highlight-pill,
side-list, card-tilted, dual-phone-overlap) are automatically
remapped to the canonical names above by LEGACY_LAYOUT_ALIASES in
src/index.ts. Existing spec.yaml files keep working without edits.
Users scan left-to-right, top-to-bottom, with these anchors in order of attention: faces → bright accent colour → text → UI. Place the single most important word inside the top-left third of the canvas.
If a headline needs a comma or an "and", it is two screenshots. Cap at
5–7 words. The renderer's text-fit step will shrink overflowing copy,
but shrunk copy fails the thumbnail test.
Reserve at least 15–20% padding from each edge for portrait phone canvases. Crowded edges read as chaos at thumbnail size.
These web standards apply directly to store creatives because users read
them on the same displays. The repo's src/colors.ts already implements
WCAG 2.x relative luminance and contrast ratio.
| Where | Element | WCAG | Minimum | Recommended |
|---|---|---|---|---|
| Caption body / subhead | Normal text < 18 pt (< 14 pt bold) | 1.4.3 (AA) | 4.5 : 1 | 7 : 1 (AAA) |
| Headline / pull-quote | Large text ≥ 18 pt (≥ 14 pt bold) | 1.4.3 (AA) | 3 : 1 | 4.5 : 1 (AAA) |
| Accent shapes, pills, buttons | Non-text graphic | 1.4.11 | 3 : 1 | 4.5 : 1 |
| Logo / decorative | Inactive UI | n/a | exempt | – |
pickTextColor() in src/colors.ts already picks the higher-contrast
choice between white and a soft black #1A1A1A. The soft black is
deliberate: pure #000 against pure #FFF is 21 : 1 but causes halation
on bright displays — WCAG-compliant and slightly less fatiguing.
ensureAccentContrast() boosts an accent until it clears 4.5 : 1 against
the gradient midpoint. Use it for any text-bearing accent — pills,
chip labels, badge numbers.
The single most common failure: white text on a mid-luminance background (light teal, lavender, beige, mid-gradient pinks). The contrast crashes into the 2 : 1–3 : 1 range and the type vanishes at thumbnail size.
Rule: derive text colour from background luminance at the text's
position, not from the brand. For gradient backgrounds, sample the
midpoint (averageHex in src/colors.ts).
extractTheme().These mappings are heuristics from neuromarketing reviews — they bias where to start, not where to land. Always A/B test.
| Category | Lean toward | Avoid |
|---|---|---|
| Finance, health, productivity | Blue, deep green, navy (trust) | Red dominance (loss aversion) |
| Games, social, dating | Magenta, orange, vivid gradients | Greyscale (reads as utility) |
| Wellness, meditation | Soft greens, lavender, off-white | Saturated red, neon |
| Commerce, deals, food | Orange, red, amber (urgency, appetite) | Pale blue (cold) |
| Children / education | High-chroma primaries | Murky earth tones |
Saturation note: +10–15% saturation above the in-app brand colour
typically renders better in thumbnails because the screen-vs-store
luminance gap eats vibrance. The renderer's saturate() helper handles
this.
Sizes below are for the 6.9" iPhone source (1290 × 2796). Scale proportionally to the canvas's shorter side for other dimensions — landscape canvases use their height, not width.
Floors are derived from WCAG body-text conventions translated to the source-canvas resolution. A 1290-px source renders on a ≈ 430 logical-pt phone display at 3× retina, so 1 CSS px ≈ 3 source px. The web body minimum of 16 CSS px maps to a 48 source-px floor for any text the user has to read (subhead, caption, body). Larger UI ink (badges, stat labels) sits one tier below at 14 CSS px / 42 source px — the Apple HIG Callout / Material body2 floor. Anything smaller is unreadable both at native phone scale and at thumbnail.
| Role | Minimum (source px @ 1290) | CSS-px equivalent | Typical | Notes |
|---|---|---|---|---|
| Hero headline | 80 px | ≈ 27 CSS px (> WCAG large-text 24) | 96–120 px | One line, max 5–7 words |
| Subhead | 48 px | = 16 CSS px (WCAG body) | 56–72 px | One line, max ~10 words |
| Caption / body | 48 px | = 16 CSS px (WCAG body) | 56–64 px | Avoid wrapping > 2 lines |
| UI labels inside device frame | 42 px | ≈ 14 CSS px (HIG Callout / md body2) | 48–56 px | Only if narratively necessary |
| Badges / stat captions | 42 px | ≈ 14 CSS px | 48–56 px | Pair with high-contrast chip |
Universal absolute floor: 42 source px (= 14 CSS px). Any text
smaller than this is unreadable on App Store / Play Store screenshots at
native size and unreadable at thumbnail. Enforced by
src/contrast-gate.ts — failures surface in manifest.json and
review.html.
Render the screenshot, scale to 25% (≈ the thumbnail size shown in search), and read every text element at arm's length. If anything is illegible, increase its size or remove it. This single test eliminates ~80% of low-converting creatives in practice.
Adopt the WCAG text-spacing minimums even though they were written for the web — they make rendered captions resilient to localisation:
Reserve +30% horizontal room for languages that expand from English
(German, Finnish, Russian). The text-fit rule should shrink rather
than wrap when the expansion exceeds the canvas; never let a German
headline drop to two lines while the English version stays on one — the
layout reads as broken.
| Style | When it works | Risk |
|---|---|---|
| Solid brand colour | Utility apps, fintech, productivity | Boring at scale; avoid for slot 1 |
| Linear gradient | 90% of high-converting modern apps | Mid-luminance trap (§2.2) |
| Radial / spotlight | Hero shots, single subject | Focal point must align with caption |
| Photo / lifestyle | Categories where context sells (travel, fitness, food) | Text contrast collapses unless a scrim is used |
| Pattern / motif | Games, kids, creative tools | Easy to over-clutter; mute to 10–15% opacity behind text |
If the background behind text varies in luminance (any gradient, photo, or pattern), drop a scrim behind the text: a soft gradient overlay sized to the text bounding box, opacity 30–60%, colour chosen to push the local luminance to the safe end for the text colour.
The renderer's feature-graphic pipeline already uses a scrim — extend the same pattern to any layout that picks a photographic background.
The device frame itself needs ≥ 3 : 1 against the background or it disappears at thumbnail size. For very dark backgrounds, switch to a silver frame; for very light, a graphite frame. The renderer's frame module should pick this automatically.
The renderer should refuse to ship any image that fails these machine-checkable gates:
| Gate | Threshold | Source helper |
|---|---|---|
| Text-vs-bg contrast (each text layer) | ≥ 4.5 : 1 normal, ≥ 3 : 1 large | contrastRatio() |
| Accent-vs-bg contrast (text-bearing) | ≥ 4.5 : 1 | ensureAccentContrast() |
| Device-frame-vs-bg contrast | ≥ 3 : 1 | – (add to renderer) |
| Headline character count | ≤ 38 chars (≈ 5–7 words) | text-fit |
| Headline after shrink | font size ≥ 80 source px at 6.9" (≈ 27 CSS px) | text-fit |
| Any text after shrink | font size ≥ 42 source px at 6.9" (≈ 14 CSS px) | contrast-gate.ts |
| Bounding-box hits canvas edge | ≥ 15% padding | layout module |
Any failed gate is a build-time warning, not a silent override.
#000 text on pure white #FFF — passes contrast,
fails comfort; use #1A1A1A on #FAFAFA| Rule | Code | Notes |
|---|---|---|
| WCAG luminance / contrast ratio | src/colors.ts relativeLuminance, contrastRatio | WCAG 2.x formula |
| Adaptive text colour | src/colors.ts pickTextColor, pickTextColorsForGradient | Soft black #1A1A1A over pure black |
| Accent contrast boost | src/colors.ts ensureAccentContrast | Adjusts L until ≥ 4.5 : 1 |
| Palette extraction | src/colors.ts extractTheme | Saturation-weighted dominant colour |
| Saturation / hue tweaks | src/colors.ts saturate, shiftHue, lighten | Use sparingly per §2.3 |
| Layout / slot story arc | src/story-arc.ts | Slot → valid layout family |
| Matrix / palette per set | src/matrix.ts, src/themes.ts | Eight backgrounds across ABCD |
| Text fit / wrap | layout modules + text-fit | Hard floor on minimum font size |
| Container-first composition | src/containers.ts | planContainers + paintContainers |
| Auto-remediation retry loop | src/index.ts:renderLayoutWithRetry | Up to 2 retries with escalating contrast targets |
| Legacy-name aliases | src/index.ts:LEGACY_LAYOUT_ALIASES | Older spec.yaml names auto-route to current layouts |
When this skill conflicts with what the code currently does, fix the code — these rules carry the conversion data; the renderer is the mechanism.
The readability strategy: every text run is painted on a deterministic container whose alpha is solved for the contrast target against the actual sampled background pixels. Backgrounds — especially AI-generated ones — cannot be trusted to honour a text-safe zone on their own.
| Style | Use | Typical alpha | Visual |
|---|---|---|---|
solid-panel | Hero headlines, value slot, masthead | 0.74 – 0.84 | Rounded rect with soft drop shadow |
soft-band | Captions, banners, full-width strips | 0.45 – 0.60 | Full-width band, feathered top edge |
chip | Badges, feature claims, eyebrow tags | 0.92 – 0.98 (near-opaque) | Pill shape, brand or accent fill |
Pick once per layout based on visual hierarchy; alpha is recomputed at render time so the same style works across any sampled bg.
Two adjacent text zones whose padded bboxes overlap or sit within
0.6 × line-height of each other are merged into one container.
A headline directly above a subhead becomes a single panel, not two
stacked rectangles that read as a UI bug.
After the first render, the gate samples actual pixels under every declared zone. Any failure raises that container's target ratio one step (e.g. 4.5 → 5.5) and re-paints. Up to 2 retries before the CLI exits non-zero. This guarantees AA by construction — no manual tuning of scrim strength per asset.
Every text run is fit-tested against
container.width − 2 × padding. Lines that don't fit at the declared
font size are first shrunk (down to the floor in §3.1) and then
wrapped. Production output never overruns its container.
Lookup table — use this to pre-trim copy before fit-test (saves a retry pass). Char widths assume Arial at the noted weight; bold/condensed adds ~15 %. Always test for the longest line; wrap if a single token can't fit.
| Container width (px) | Font size / weight | Approx px/char | Max chars/line |
|---|---|---|---|
| 100 (small stat tile) | 13 regular | 6.5 | ~14 |
| 100 (small stat tile) | 44 weight-900 (stat) | 26 | ~3–4 (e.g. 100%) |
| 100 (small stat tile) | 64 weight-900 (stat) | 32 | ~2 (e.g. 3s, 4) |
| 192 (phone feature row) | 12 regular | 6.5 | ~28 |
| 200 (phone editorial body) | 12 regular | 6.5 | ~30 |
| 280 (phone caption band) | 26 weight-800 (headline) | 14 | ~19 |
| 360 (tablet panel headline) | 34 weight-900 | 17 | ~20 |
| 400 (tablet feature row) | 13 regular | 7 | ~55 |
When a stat tile holds a multi-character number (e.g. 100%, 4.9★),
drop the stat font by ~20 pt from the 1–2-char default (64 → 44).
That's the TL-04 lesson: 100% at font 64 = 128 px and overflows a 100 px
safe area; at font 44 it fits with margin.
See design/layouts-*.html for the 32 layouts (8 per device class).
For routine decisions (which layout for which slot, container style for
which zone) — consult §11 below, not the HTML. The HTML is for
visual reference only.
This is the canonical reference. Don't load the design HTML for routine decisions — every layout's slot job, container plan, and composition intent is captured here.
Source-screenshot orientation drives everything. Portrait source → portrait canvas + portrait device + portrait content. Landscape source → landscape canvas + landscape device + landscape content. No rotation, no letterboxing. The plugin picks the matching layout file based on what orientation the developer's app screens were captured in.
Four parallel layout files map 1:1 onto this rule:
| Source orientation | Device class | File |
|---|---|---|
| Portrait | Phone | design/layouts-phone-portrait-shots.html |
| Landscape | Phone | design/layouts-phone-landscape-shots.html |
| Portrait | Tablet | design/layouts-tablet-portrait-shots.html |
| Landscape | Tablet | design/layouts-tablet-landscape-shots.html |
Every file ships 8 layouts in this vocabulary. Names vary slightly per file but the intent is shared across all four.
| # | Canonical intent | Job | Container plan |
|---|---|---|---|
| 1 | Hero Type — icon + benefit headline + chips, no device | Value / CTA | merged solid-panel + chips |
| 2 | Caption — device hero + caption band (top or bottom) | Flow | soft-band + merged title |
| 3 | Masthead — solid headline panel + eyebrow chip + device | Value alt / Trust | solid-panel + chip eyebrow |
| 4 | Editorial / Multi-pane — device + magazine-style text composition | Flow / Feature | solid-panel panel + accent rule |
| 5 | Bento — grid of cards (headline strip + device + stat tiles) | Trust / Feature | multiple solid-panel tiles |
| 6 | Feature Stack / Side List — numbered rows (with or without device) | Trust | 4 × solid-panel rows + footer chip |
| 7 | Dual Device — two devices (light + dark, or A/B), tilted | Feature | solid-panel top + device frames |
| 8 | Tilted Card — device in floating tilted card | Feature (premium) | tilted card + solid-panel text |
The same 8-position vocabulary, instantiated per device class. Use this to pick a layout without loading the HTML.
| # | Phone portrait | Phone landscape | Tablet portrait | Tablet landscape |
|---|---|---|---|---|
| 1 | #1 Hero Type | LL-05 Text Hero · LL-01 Hero | TP-01 Hero Type | TL-05 Text Hero · TL-01 Hero Side |
| 2 | #2 Caption Band | LL-02 Caption | TP-02 Caption Band | TL-02 Caption Top |
| 3 | #3 Headline Masthead | (uses LL-01 instead) | TP-06 Headline Masthead | TL-06 Headline Masthead |
| 4 | #4 Editorial Card | LL-03 Triple Phone | TP-03 Editorial Split | TL-03 Multi-pane |
| 5 | #5 Vertical Bento | LL-04 Stat Strip | TP-04 Bento | TL-08 Bento Horizontal · TL-04 Stat Strip |
| 6 | #6 Feature Stack | LL-08 Side List | TP-05 Feature Stack | (use TL-03 multi-pane) |
| 7 | #7 Dual Phone | LL-06 Dual Phone | TP-07 Dual Tablet | TL-07 Dual Tablet |
| 8 | #8 Tilted Card | LL-07 Tilted Card | TP-08 Tilted Card | (use TL-01 instead) |
Notes:
| Class | Source canvas | Mockup viewBox |
|---|---|---|
| Phone portrait | 1290 × 2796 | 320 × 640 |
| Phone landscape | 2796 × 1290 | 640 × 320 |
| Tablet portrait | 2048 × 2732 | 480 × 640 |
| Tablet landscape | 2732 × 2048 | 640 × 480 |
Recommended 10-slot sequence per device class. iOS shows the first 3 inline in search; treat slots 1–3 as the conversion-critical sentence (Value → Flow → Trust). Slots 4–8 carry feature depth; slot 10 closes with a CTA. Landscape sets cap at 3 slots per iOS rules.
Phone portrait (10 slots): Hero Type → Caption Band → Feature Stack → Editorial Card → Vertical Bento → Dual Phone → Tilted Card → Caption Band → Headline Masthead → Hero Type (CTA).
Phone landscape (3 slots): Hero (LL-01) → Caption (LL-02) → Stat Strip (LL-04).
Tablet portrait (10 slots): TP-01 → TP-02 → TP-05 → TP-03 → TP-04 → TP-02 → TP-03 → TP-04 → TP-05 → TP-01 (CTA).
Tablet landscape (3 slots): TL-01 → TL-02 or TL-03 → TL-04.
1. What orientation are the source screenshots?
→ portrait → use portrait file
→ landscape → use landscape file
2. Phone or tablet?
→ pick the matching of the 4 files
3. Which slot?
→ §11.5 above gives the recommended layout
4. Need a visual reference?
→ only then, open design/layouts-<class>-<orient>-shots.html
This flow saves loading ~600 lines of HTML for routine work. Open the HTML only when you need to see the actual composition (e.g., debugging a visual issue or designing a new variant).
Provides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub dbroadhurst/mobile-store-assets --plugin mobile-store-assets