Why We Needed a Design System
Spindare hit 100 components before we had any formal system. Everything worked, but opening a new screen and trying to match the color of a button from a screen built two months ago meant digging through files, copy-pasting hex values, and hoping nothing drifted.
By 200 components, the drift was visible. Slightly different border radii, subtly inconsistent spacing between interactive elements, three different shades of "gray" that were all meant to be the same thing. On a phone screen, these details matter.
At 300 components across three developers (me, my uncle, and Daniel), a design system wasn't optional. It was either invest a week in building one or spend months fixing visual inconsistencies one by one.
The Token Layer
We started at the bottom: design tokens. Every visual constant goes here before it goes anywhere else.
// tokens/colors.ts
export const colors = {
background: {
primary: '#0A0A0F',
secondary: '#13131A',
tertiary: '#1C1C26',
elevated: '#222230',
},
accent: {
primary: '#F5A623',
soft: 'rgba(245, 166, 35, 0.15)',
border: 'rgba(245, 166, 35, 0.3)',
},
text: {
primary: '#FFFFFF',
secondary: 'rgba(255,255,255,0.65)',
tertiary: 'rgba(255,255,255,0.35)',
muted: 'rgba(255,255,255,0.2)',
},
// ...
} as const;// tokens/spacing.ts
export const spacing = {
xs: 4, sm: 8, md: 12, lg: 16,
xl: 24, xxl: 32, xxxl: 48,
} as const;
export const radius = {
sm: 6, md: 10, lg: 16, xl: 24, full: 9999,
} as const;The as const is important — it gives TypeScript literal types so you get autocomplete and type errors if you use a token that doesn't exist.
The Component Layer
On top of tokens, we built a set of primitive components that all other components compose from. The most important ones:
CODE_START<Box />CODE_END — basically a View but it accepts spacing/color tokens as props:
<Box px="lg" py="md" bg="secondary" radius="md">
{children}
</Box>CODE_START<Text />CODE_END — a typed text component with preset variants:
<Text variant="heading" size="xl">Post title</Text>
<Text variant="body" color="secondary">Post content</Text>
<Text variant="label" color="muted">3 min ago</Text>CODE_START<Pressable />CODE_END — wraps React Native's Pressable with standard feedback styles, haptics, and disabled state handling baked in.
Every other component in the app is built from these three primitives. When we want to update how disabled states look globally, we change it in <Pressable /> and it propagates everywhere.
Keeping It Consistent: The Rules
Rules that have saved us the most time:
1. No raw hex values outside of tokens. If you need a color that's not in the token file, add it there first. Never write #F5A623 in a component file.
2. No CODE_STARTStyleSheet.createCODE_END for one-off styles. If a style is used once and can be expressed with tokens via Box/Text, do that. StyleSheet.create is for reusable, named styles only.
3. Every new component gets a Storybook story. We use Storybook for React Native to preview components in isolation. This caught probably 30% of design bugs before they ever hit a real screen.
4. Spacing only from the scale. No marginTop: 13. If 12 or 16 doesn't work, the layout needs to change, not the spacing value.
What Saved Us the Most Time
Honestly: the Text component variants. Typography inconsistency was our biggest problem before the system. Having a fixed set of variants (heading, subheading, body, label, caption, mono) and enforcing that everything uses them meant we stopped having "close enough" font sizes scattered through the codebase.
The second biggest win: the token file as source of truth for dark mode. Because everything references tokens, adding a second theme was just adding a second set of token values and a context switch. No hunting through components for hardcoded colors.
Where We'd Do It Differently
Start earlier. We built the design system at component 200. Starting at component 1 would have saved a lot of retroactive work converting old components to use tokens. Even a minimal token file and two primitive components from day one is infinitely better than adding them later.