Colors (React Native)
React Native uses the same 38 semantic color tokens as Web, but accessed via the theme object instead of CSS custom properties. All values come from @grundtone/core — the single source of truth for both platforms.
Naming Convention
Tokens use shade-based naming (primaryLight, primaryDark) instead of state-based (primaryHover, primaryActive). Components decide which shade to use for hover, active, tint, etc.
Setup
Wrap your app with GrundtoneThemeProvider and pass light/dark themes from createTheme():
import { GrundtoneThemeProvider } from '@grundtone/react-native';
import { createTheme } from '@grundtone/core';
const { light, dark } = createTheme({
light: {
primary: '#0059b3',
primaryLight: '#3381cc',
primaryDark: '#003a7a',
onPrimary: '#ffffff',
},
dark: {
primary: '#4dabf7',
primaryLight: '#74c0fc',
primaryDark: '#339af0',
onPrimary: '#121212',
},
});
export default function App() {
return (
<GrundtoneThemeProvider light={light} dark={dark}>
<RootNavigator />
</GrundtoneThemeProvider>
);
}The provider automatically follows the system appearance (light/dark) via React Native's Appearance API. Override with the defaultMode or mode prop.
Using Colors
Access colors via the useGrundtoneTheme() hook:
import { useGrundtoneTheme } from '@grundtone/react-native';
function Card({ title, children }) {
const { theme } = useGrundtoneTheme();
return (
<View
style={{
backgroundColor: theme.colors.surface,
borderColor: theme.colors.borderLight,
borderWidth: 1,
borderRadius: 8,
}}
>
<Text style={{ color: theme.colors.text }}>{title}</Text>
<Text style={{ color: theme.colors.textSecondary }}>{children}</Text>
</View>
);
}Brand
| Color | Token | Purpose |
|---|---|---|
#0059b3 | primary | Primary brand color |
#3381cc | primaryLight | Lighter shade (hover, tints) |
#003a7a | primaryDark | Darker shade (active, pressed) |
#ffffff | onPrimary | Text on primary background |
#6c757d | secondary | Secondary brand color |
#868e96 | secondaryLight | Lighter secondary shade |
#494f54 | secondaryDark | Darker secondary shade |
Status
| Color | Token | Purpose |
|---|---|---|
#198754 | success | Success state |
#d1e7dd | successLight | Success background tint |
#146c43 | successDark | Darker success shade |
#ffc107 | warning | Warning state |
#fff3cd | warningLight | Warning background tint |
#cc9a06 | warningDark | Darker warning shade |
#dc3545 | error | Error state |
#f8d7da | errorLight | Error background tint |
#b02a37 | errorDark | Darker error shade |
#0dcaf0 | info | Info state |
#cff4fc | infoLight | Info background tint |
#0aa2c0 | infoDark | Darker info shade |
Surface
| Color | Token | Purpose |
|---|---|---|
#ffffff | background | Page background |
#fafafa | backgroundAlt | Alternate sections, zebra rows |
#f8f9fa | surface | Cards, panels |
#f0f1f2 | surfaceAlt | Hover state, zebra |
#ffffff | surfaceRaised | Modals, FABs |
rgba(255,255,255,0.95) | surfaceOverlay | Semi-transparent overlay |
rgba(0,0,0,0.5) | modalBackdrop | Modal backdrop scrim |
// Modal backdrop example
function ModalBackdrop({ visible, children }) {
const { theme } = useGrundtoneTheme();
if (!visible) return null;
return (
<View style={[StyleSheet.absoluteFill, { backgroundColor: theme.colors.modalBackdrop }]}>
{children}
</View>
);
}Text
| Color | Token | Purpose |
|---|---|---|
#212529 | text | Primary text |
#6c757d | textSecondary | Secondary text |
#adb5bd | textTertiary | Tertiary / hint text |
#ffffff | textInverse | Text on dark backgrounds |
#a3a3a3 | textPlaceholder | Input placeholders |
#d4d4d4 | textDisabled | Disabled elements |
Border
| Color | Token | Purpose |
|---|---|---|
#dee2e6 | borderLight | Subtle dividers |
#ced4da | borderMedium | Default inputs |
#adb5bd | borderStrong | Focus, emphasized |
rgba(255,255,255,0.2) | borderInverse | On dark backgrounds |
Focus & Neutral
| Color | Token | Purpose |
|---|---|---|
#0059b3 | focus | Focus indicator |
rgba(0,89,179,0.25) | focusRing | Focus ring shadow |
#6c757d | neutral | Neutral UI elements |
Light vs Dark Comparison
Side-by-side comparison of all tokens across modes.
Brand
| Color | Token | Light | Dark |
|---|---|---|---|
#0059b3 | primary | #0059b3 | #4dabf7 |
#3381cc | primaryLight | #3381cc | #74c0fc |
#003a7a | primaryDark | #003a7a | #339af0 |
#ffffff | onPrimary | #ffffff | #121212 |
#6c757d | secondary | #6c757d | #adb5bd |
#868e96 | secondaryLight | #868e96 | #ced4da |
#494f54 | secondaryDark | #494f54 | #868e96 |
Status
| Color | Token | Light | Dark |
|---|---|---|---|
#198754 | success | #198754 | #51cf66 |
#d1e7dd | successLight | #d1e7dd | #1a3d20 |
#146c43 | successDark | #146c43 | #40c057 |
#ffc107 | warning | #ffc107 | #ffd43b |
#fff3cd | warningLight | #fff3cd | #3d3a1a |
#cc9a06 | warningDark | #cc9a06 | #fab005 |
#dc3545 | error | #dc3545 | #ff6b6b |
#f8d7da | errorLight | #f8d7da | #3d1a1c |
#b02a37 | errorDark | #b02a37 | #fa5252 |
#0dcaf0 | info | #0dcaf0 | #4dabf7 |
#cff4fc | infoLight | #cff4fc | #1a2e3d |
#0aa2c0 | infoDark | #0aa2c0 | #339af0 |
Surface
| Color | Token | Light | Dark |
|---|---|---|---|
#ffffff | background | #ffffff | #121212 |
#fafafa | backgroundAlt | #fafafa | #1a1a1a |
#f8f9fa | surface | #f8f9fa | #1e1e1e |
#f0f1f2 | surfaceAlt | #f0f1f2 | #252525 |
#ffffff | surfaceRaised | #ffffff | #2a2a2a |
rgba(255,255,255,0.95) | surfaceOverlay | rgba(255,255,255,0.95) | rgba(30,30,30,0.95) |
rgba(0,0,0,0.5) | modalBackdrop | rgba(0,0,0,0.5) | rgba(0,0,0,0.7) |
Text
| Color | Token | Light | Dark |
|---|---|---|---|
#212529 | text | #212529 | #ffffff |
#6c757d | textSecondary | #6c757d | #b0b0b0 |
#adb5bd | textTertiary | #adb5bd | #808080 |
#ffffff | textInverse | #ffffff | #121212 |
#a3a3a3 | textPlaceholder | #a3a3a3 | #666666 |
#d4d4d4 | textDisabled | #d4d4d4 | #4a4a4a |
Border
| Color | Token | Light | Dark |
|---|---|---|---|
#dee2e6 | borderLight | #dee2e6 | #404040 |
#ced4da | borderMedium | #ced4da | #505050 |
#adb5bd | borderStrong | #adb5bd | #606060 |
rgba(255,255,255,0.2) | borderInverse | rgba(255,255,255,0.2) | rgba(0,0,0,0.3) |
Dark Mode Control
The hook provides isDark and setMode for manual control:
const { theme, isDark, setMode } = useGrundtoneTheme();
// Toggle
setMode(isDark ? 'light' : 'dark');
// Use in styles
<View style={{ backgroundColor: theme.colors.background }}>
<Text style={{ color: theme.colors.text }}>
{isDark ? 'Dark mode' : 'Light mode'}
</Text>
</View>;Overriding Colors
Override only what you need in createTheme() — the rest uses defaults:
const { light, dark } = createTheme({
light: {
primary: '#e91e63',
primaryLight: '#f06292',
primaryDark: '#c2185b',
onPrimary: '#ffffff',
},
dark: {
primary: '#f48fb1',
primaryLight: '#f8bbd0',
primaryDark: '#ec407a',
onPrimary: '#121212',
},
});You can override any of the 38 tokens. Unspecified tokens keep their defaults.
