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.
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
- A running app: Quickstart or Manual setup
- The Design inputs section of the product brief, or the answers to the questions below
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:
| Input | Default |
|---|---|
| A logo: PNG, transparent background, at least 1024 px | None; the boilerplate icon stays until one exists |
| Brand colours: the accent, and the canvas if it is not white, as hex or oklch | The mono preset |
| Fonts: a UI family and, optionally, a display family, both from Google Fonts | Geist and Instrument Serif |
| Two or three reference apps, or screenshots | None |
| Three adjectives for how the app should feel | content-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:
| Seed | Character | Accent |
|---|---|---|
mono | Stark white canvas, near-black ink, hairline dividers | Ink itself |
paper | Warm cream canvas, espresso ink; editorial and unhurried | Terracotta |
slate | Cool near-white canvas, navy ink; precise and technical | Electric blue |
For your own colours, copy the closest preset and replace light.background,
light.foreground and accent:
{
"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 --activateThis 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:themeActivation 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.pngFrom one PNG logo and the active seed’s canvas colours it writes:
| File | Content |
|---|---|
src/assets/images/icon.png | 1024 × 1024, opaque: the logo on the light canvas |
src/assets/images/adaptive-icon.png | The transparent Android adaptive foreground, with the logo inside the safe zone |
src/assets/images/splash-icon.png | The transparent splash image that expo-splash-screen scales |
src/assets/images/branding.json | The 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.cssimporting anything other than exactly one theme, orAPP_THEMEnaming a different one- A font name in
src/global.cssthatuseFontsdoes not load, or a face whose package is not installed - A raw colour in app code outside
src/theme/.scripts/theme-allowlist.jsonnames 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