Skip to Content
Design

Design

Give your app its own theme, fonts, icon and splash screen with three commands, while the design contract in DESIGN.md keeps every screen coherent.

Use the design skill to make this app look like our brand. Read the design inputs in PRODUCT.md, ask me only for what is missing, then generate the theme, swap the fonts, build the icon and splash from the logo and rewrite DESIGN.md §1.

The boilerplate ships with the mono look. Components read their colours through semantic tokens, so a new theme re-skins all of them without touching a component.

Prerequisites

What the design skill asks

It reads the Design inputs section of PRODUCT.md first and asks, in one message, only for what is missing:

InputDefault
A logo: PNG, transparent background, at least 1024 pxNone; the boilerplate icon stays until one exists
Brand colours: the accent, and the canvas if it is not white, as hex or oklchThe mono preset
Fonts: a UI family and, optionally, a display family, both from Google FontsGeist and Instrument Serif
Two or three reference apps, or screenshotsNone
Three adjectives for how the app should feelcontent-first, quiet, precise

An SVG logo must be rasterised first; on macOS, qlmanage -t -s 1024 -o . logo.svg writes a PNG next to it. The skill writes the answers back into PRODUCT.md. With no logo, colours or references, it offers the three presets below and lets you pick one.

Theme

A theme is generated from a seed in src/theme/seeds/. The seed is the whole identity; surface steps, muted colours, status colours and dark mode are derived from it by fixed rules. Three presets ship:

SeedCharacterAccent
monoStark white canvas, near-black ink, hairline dividersInk itself
paperWarm cream canvas, espresso ink; editorial and unhurriedTerracotta
slateCool near-white canvas, navy ink; precise and technicalElectric blue

For your own colours, copy the closest preset and replace light.background, light.foreground and accent:

src/theme/seeds/acme.json
{ "name": "acme", "character": "One line on how it should feel.", "light": { "background": "#ffffff", "foreground": "#0d0d0d" }, "dark": { "background": "#0d0d0d", "foreground": "#fafafa" }, "accent": { "light": "oklch(0.55 0.2 250)", "dark": "oklch(0.72 0.15 250)" }, "tint": { "hue": 60, "chroma": 0.004 }, "status": { "danger": "#c0392b" } }

dark, tint and status are optional: dark defaults to the inverted neutrals, tint to none, and status to the boilerplate’s palette. "accent": "ink" makes the primary button the foreground colour, as in mono. Give dark mode a lighter, less saturated version of the light accent, because a saturated mid-tone on a dark canvas vibrates.

Generate and activate

yarn theme:generate --seed src/theme/seeds/acme.json --activate

This writes src/theme/themes/acme.css with the full HeroUI Native variable set for light and dark, swaps the import in src/global.css and APP_THEME in src/theme/index.ts, and prints the contrast ratios. A missed contrast floor exits 1: darken the light accent or lighten the dark one in the seed and run it again.

Restart Metro

Theme CSS is compiled at build time, so a running dev server keeps the old theme.

Verify

yarn check:theme

Activation is those two places only: the one theme @import in src/global.css and APP_THEME in src/theme/index.ts. A theme is Uniwind’s own light and dark variants, so the device’s colour scheme keeps driving it and nothing changes in metro.config.js.

Fonts

yarn theme:fonts --sans "Inter" --display "Fraunces"

Pass --sans, --display or both, with family names from Google Fonts. The command rewrites the useFonts call in src/app/_layout.tsx, the @theme font variables in src/global.css, and the family names in DESIGN.md and the conventions skill, then prints the yarn add and yarn remove commands for the @expo-google-fonts packages. Run those yourself; the script leaves the lockfile alone. The UI family needs Regular, Medium, SemiBold and Bold; the display family only Regular. Fonts render after a native rebuild.

Icon and splash

yarn theme:icon --logo path/to/logo.png

From one PNG logo and the active seed’s canvas colours it writes:

FileContent
src/assets/images/icon.png1024 × 1024, opaque: the logo on the light canvas
src/assets/images/adaptive-icon.pngThe transparent Android adaptive foreground, with the logo inside the safe zone
src/assets/images/splash-icon.pngThe transparent splash image that expo-splash-screen scales
src/assets/images/branding.jsonThe three paths and the light and dark canvas colours as hex, read by app.config.js

Pass --bleed for artwork that already fills the square, --logo-dark for a mark that needs an inverted version on the dark splash, and --scale to change the logo’s size on the canvas.

Keep icon.png opaque. Prebuild flattens an alpha channel for the build, but the App Store rejects a transparent icon uploaded to the listing. The submit skill’s preflight warns when it finds one.

DESIGN.md

DESIGN.md has two kinds of section. §1 is your app’s identity: canvas, ink, accent and fonts. The design skill rewrites it from the seed, so regenerate it rather than editing it by hand. §2 to §7 are the contract that keeps screens coherent whatever the identity: tokens, type scale, spacing, patterns, tablet layout, and the do and don’t list. They do not change per app. Read them before building UI, and point your agent at them too.

What check:theme enforces

yarn check:theme is part of yarn ci and fails on:

  • A theme file whose light and dark blocks define different variables, or that misses or adds a variable from HeroUI Native’s contract
  • A token pair below its WCAG contrast floor, in either mode
  • A theme file that differs from what its seed renders, or has no seed
  • src/global.css importing anything other than exactly one theme, or APP_THEME naming a different one
  • A font name in src/global.css that useFonts does not load, or a face whose package is not installed
  • A raw colour in app code outside src/theme/. scripts/theme-allowlist.json names the files allowed to paint outside the theme, each with a reason, and the list only shrinks

Files

      • index.ts - APP_THEME and setAppTheme()
      • branding.json - Icon, splash and canvas colours read by app.config.js
    • global.css - The theme import and the font variables
      • generate-theme.mjs
      • swap-fonts.mjs
      • app-icon.mjs
    • check-theme.mjs
    • theme-allowlist.json
  • DESIGN.md - §1 identity, §2 to §7 contract
  • .claude/skills/design/SKILL.md
Last updated on