design-tokens
Use when writing or editing any StyleSheet in kasane — adding a component, restyling a screen, picking a colour, choosing spacing, or when asked to make the UI consistent, fix the palette, or clean up
SKILL.md.claude/skills/design-tokens/SKILL.md
GitHubDesign Tokens
src/theme.ts is the single source of colour, spacing, and type. Components read from it. No component introduces a hex literal.
The tokens were extracted from the 283 hex literals that were previously inlined across app/ and src/components/; the values are unchanged, so replacing a literal with its token is always a pure refactor with no visual diff.
Quick reference
import {
COLOR,
SPACE,
TEXT,
FONT,
ARC_COLORS,
MOVIE_COLOR,
arcColors,
pressFeedback,
NARROW_WIDTH,
} from "@/theme";
| Need | Token |
|---|---|
Screen background, Stack contentStyle |
COLOR.background |
| Card, pill, chip | COLOR.surface |
| Block that must separate from a card | COLOR.surfaceRaised |
| Notice banner background | COLOR.surfaceNotice |
| Divider, input border | COLOR.border |
| Box behind a loading cover | COLOR.coverPlaceholder |
| Heading, body copy | COLOR.textPrimary |
| Supporting copy | COLOR.textSecondary |
| Label, metadata, uppercase eyebrow | COLOR.textMuted |
placeholderTextColor, lowest emphasis |
COLOR.textFaint |
| Links, active state, brand rule | COLOR.accent |
| "Arcs behind", "auto-estimated", "no mapping" | COLOR.notice |
| Error text | COLOR.danger |
| Arc segment fill and text | arcColors(segment) |
| Film segment | MOVIE_COLOR |
Any gap or padding |
SPACE.xxs…SPACE.pageEndTall |
| Clearance above the footer | FOOTER_OFFSET |
| Eyebrow, button, chip, section/page titles | ...TEXT.<preset>, plus a color |
Press and hover feedback on a Pressable |
(state) => [styles.x, pressFeedback(state)] |
| Phone-width cut-off | NARROW_WIDTH, or useIsNarrow() |
SPACE runs xxs 2 · xs 4 · sm 6 · md 8 · mdl 10 · lg 12 · lgx 14 · xl 16 · xxl 20 · xxxl 24 · page 32 · pageEnd 40 · pageEndTall 48. mdl and lgx are the in-between steps existing controls rely on; prefer the main steps for new UI.
ARC_COLORS is shared so the same arc is the same colour in the rail and the pie, and arcColors is the one place that picks a segment's slot. Do not reorder it and do not fork a second copy into a component.
Layout rules
- No margins. Space with
gapandpaddingon the container. This is a hard repo rule, not a preference. - Lay grids out with
flexDirection: "row",flexWrap: "wrap",gap, and tile widths fromgridLayout/tileWidthForinsrc/data/gridLayout.ts. React Native's style types have nodisplay: "grid", so using it takes a cast — and it does nothing on a native tablet. Measure the container withuseLayoutWidthwhen the tiles must fill it exactly. - Reach for a token even when the value looks incidental.
gap: 6becomesSPACE.sm.
Adding a token
A new colour needs a role, not a shade. COLOR.warningBannerBorder is a role; COLOR.yellow2 is not. If no existing role fits, add one to src/theme.ts with a comment saying where it applies, then use it. Two roles may legitimately share a value: COLOR.danger and ARC_COLORS[0] are both #ff7c5c and are unrelated, which is exactly why they are separate names.
Migrating existing styles
When you touch a StyleSheet.create block for any reason, convert the literals in that block to tokens. Do not open a separate sweep across all 21 files; convert opportunistically so every diff stays reviewable and visually verifiable.
Find remaining literals:
grep -rn "#[0-9a-fA-F]\{3,8\}" --include="*.tsx" app src
Common mistakes
| Mistake | Why it hurts |
|---|---|
color: "#f5f5f5" in a new component |
The next palette change misses it. Use COLOR.textPrimary. |
| Naming a token by its colour | COLOR.violet cannot be re-themed. Name the role. |
Copying ARC_COLORS into a component |
The rail and the pie would drift and the same arc would render two colours. |
Adding margin "just this once" |
Banned. Use container gap/padding. |
| Replacing every literal in one giant commit | Unreviewable, and a single wrong swap is a silent visual regression. Convert per touched block. |