rn-theming
Skill4 starsChanged 5 months agoMIT-0View on GitHub
Theme token system: colors.ts and typography.ts structure, surface hierarchy, how to apply brand colors consistently
In this repository26 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- 10ft-uiSkill
- amazon-devices-vega-app-performanceSkill
- amazon-devices-vega-best-practicesSkill
- amazon-devices-vega-setup-sdkSkill
- android-tv-testingSkill
- changelog-static-siteSkill
- creative-tv-uiSkill
- eas-buildSkill
- expo-tv-configSkill
- firetv-leanbackSkill
- kmp-build-commandsSkill
- kmp-compose-tvSkill
- kmp-data-layerSkill
- kmp-navigationSkill
- kmp-template-anatomySkill
- kmp-themingSkill
- kmp-verify-patternsSkill
- metaSkill
- rn-manifest-wiringSkill
- rn-shared-ui-catalogSkill
- rn-spatial-navigationSkill
- rn-template-anatomySkill
- vega-sdkSkill
- video-playerSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 3 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 74
If brand.primary_color fails contrast against background_color at body sizes, do not silently use it for text.
- Distinctive: line 91
Font weight: prefer 400 body, 700 headings.
- Very specific: line 92
Custom font: if brand.font_family is non-system, register it via expo-font in the app's root layout.
- Distinctive: line 102
If you can't articulate why the override is needed, don't add one.
- Distinctive: line 111
If the brand kit only provides a logo, generate the rest:
- Very specific: line 118
Hardcoding colors in components: All colors flow through useTheme().
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Theming
Theme tokens live in
packages/shared-ui/theme/. A brand kit becomes a theme via mechanical token replacement, then a small set of judgment calls (contrast, accent placement, dark/light defaults).
Token structure
packages/shared-ui/theme/
├── tokens.ts # The token table — single source of truth
├── ThemeProvider.tsx # Context provider, consumed by all components
├── tokens.android.ts # Optional per-platform override
├── tokens.ios.ts # Optional per-platform override
└── tokens.kepler.ts # Optional per-platform override (Vega)tokens.ts exports a typed object roughly shaped like:
export const tokens = {
color: {
background: "#0B0B0F",
surface: "#15151C",
primary: "#E50914",
accent: "#FFC857",
text: "#FFFFFF",
textMuted: "#A0A0A8",
focusRing: "#FFFFFF",
},
type: {
family: "Inter",
size: { body: 24, h2: 36, h1: 56 },
weight: { regular: "400", bold: "700" },
},
radius: { sm: 4, md: 8, lg: 16 },
spacing: { 1: 4, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32 },
};Mapping brand.json → tokens
brand.json field | Goes into | Notes |
primary_color | color.primary | Used for CTAs, active states |
accent_color | color.accent | Used for focus rings, highlights |
background_color | color.background | App-wide background |
font_family | type.family | Must be loaded via expo-font if non-system |
logo_path | Not a token — copied to assets/logo.svg, referenced in Drawer | |
splash_path | Not a token — copied to apps/expo-multi-tv/assets/splash.png |
Decision: dark or light mode default?
TV is viewed in dim rooms much more often than phones. Default dark unless the brief says otherwise. Test:
brand_kit.background_colorlightness ≥ 0.85 → light mode is the intent.- Otherwise → dark.
The planner's theme.mode field overrides this if explicitly set.
Contrast — TV is not phone
TV viewers sit 8–10 ft away. Minimum acceptable contrast ratios are stricter than WCAG mobile baselines:
- Body text on background: ≥ 7:1 (AAA). Aim higher than you would for mobile.
- Focused vs unfocused tile border: clearly visible from 10 ft — usually means a bright color (white or a high-contrast accent) and a thickness of at least 4px.
- Subtitles / muted text: still ≥ 4.5:1. Don't go below.
If brand.primary_color fails contrast against background_color at body sizes, do not silently use it for text. Use it for CTAs and accents; use tokens.color.text for body.
Focus ring color — judgment call
The template uses white as a default focus ring. This works on dark backgrounds. On lighter brands:
- Background lightness < 0.3 → focus ring =
#FFFFFF. - Background lightness 0.3–0.7 → focus ring =
accent_colorif it contrasts ≥ 4.5:1, else darkenaccent_color. - Background lightness > 0.7 → focus ring = a dark neutral (e.g.
#111111).
The contrast check is non-negotiable. A focus ring you can't see from across the room is a broken app.
Typography
- Body minimum: 24pt. Anything smaller is unreadable from couch distance. The template's default
body: 24is the floor, not a suggestion. - H1: 48–64pt. H2: 32–40pt. Resist the urge to scale down.
- Line height: 1.4× minimum. Tight line-height hurts at distance.
- Font weight: prefer 400 body, 700 headings. Hairline / thin weights disappear on TVs.
- Custom font: if
brand.font_familyis non-system, register it viaexpo-fontin the app's root layout. Verify it loads on all targets — fonts that work on iOS sometimes don't ship to Vega.
Per-platform overrides
Reach for tokens.ios.ts etc. only when the platform genuinely needs different values. Examples that warrant an override:
- Vega's UI guidelines prescribe specific minimum tile sizes — override
spacingthere. - Apple TV system font preference —
type.familyoverride. - Fire TV banner-required color contrast — usually fine via default tokens; don't override unless tested.
If you can't articulate why the override is needed, don't add one.
Splash and icon assets
Generate (or accept user-provided):
- Icon: 1024×1024 PNG, opaque, no rounding (platforms round).
- Android TV banner: exactly 400×240 PNG. Required. Builds will silently use a generic icon if missing, and look unprofessional in the leanback launcher.
- Splash: 1920×1080 minimum, centered logo on the brand background color.
If the brand kit only provides a logo, generate the rest:
- Splash = logo centered on
background_color, with 30% margin. - Banner = logo + wordmark on
background_color, fitting 400×240. - Icon = logo on
primary_colorwith 15% padding.
Anti-patterns
- Hardcoding colors in components. All colors flow through
useTheme(). If you writecolor: "#fff"in a component, you'll see it in the next bug report. - Using
brand.accent_colorfor body text. Accents are accents. - Tiny text "to fit more on screen." Users will lean forward and squint, then close the app. 24pt or larger.
- Multiple custom fonts. One brand font + system fallback is enough. Two custom fonts doubles load time and font-loading failures.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

