A lightweight, themeable design system for React applications with support for light and dark modes.
- 🎨 Primitive Color Scales: Structured color scales (0-900) for building consistent UI
- 🌓 Light & Dark Mode: Built-in support for light and dark themes
- 🧩 Semantic Color Tokens: Abstract color tokens (surface, text, etc.) mapped to primitives
- 📦 React Components: Themeable components using CSS variables
- 🔄 Theme Switching: Easy-to-use context API for theme control
- 🔤 Typography System: Complete typography system with responsive text scaling
- 📏 Spacing System: Consistent spacing scale with 4px increments
- 🔘 Border Radius System: Consistent border radius tokens (min 4px, default 12px)
- 📱 Responsive Design: Support for mobile, tablet, and desktop devices
- 📊 Layout Components: Flexible layout components with spacing props
- Install dependencies:
npm install- Start the development server:
npm startThe design system follows a token-based architecture with multiple layers that create a flexible and maintainable color system:
Base color scales (from 0-900) that form the foundation of the system. These are the raw hex values:
// Example of primitive colors
primitiveColors.gray[500] // '#645F7A'
primitiveColors.purple[600] // '#6837AB'Functional color tokens that map primitive colors to semantic names. These change based on theme:
// Light theme
lightThemeColors.brandPrimary // maps to primitiveColors.purple[600]
lightThemeColors.textPrimary // maps to primitiveColors.gray[900]
// Dark theme
darkThemeColors.brandPrimary // maps to primitiveColors.purple[400]
darkThemeColors.textPrimary // maps to primitiveColors.gray[100]The ThemeProvider component (/src/theme/ThemeProvider.tsx) dynamically generates CSS variables from semantic colors and injects them into the DOM:
:root {
--color-brand-primary: #6837AB;
--color-text-primary: #15101F;
}Components reference these CSS variables in their styles:
const Button = styled.button`
background-color: var(--color-brand-primary);
color: var(--color-text-inverse);
`;Changing your brand colors is now a simple process that requires changes in just one place:
If you want to add or modify a primitive color scale:
- Edit
/src/theme/colors/primitives.ts - Add or update color scales:
// Adding a new teal color
export const primitiveColors = {
// existing colors...
teal: {
50: '#E6FFFA',
100: '#B2F5EA',
// etc...
}
}If you want to use existing primitives for your brand colors:
- Edit
/src/theme/colors/semantic.ts - Update the semantic colors to reference different primitives:
// Light theme
export const lightThemeColors: ThemeColors = {
// Change brand from purple to teal
brandPrimary: primitiveColors.teal[600],
brandSecondary: primitiveColors.teal[700],
// Other color mappings...
}
// Dark theme
export const darkThemeColors: ThemeColors = {
// Change brand from purple to teal
brandPrimary: primitiveColors.teal[400],
brandSecondary: primitiveColors.teal[300],
// Other color mappings...
}After making these changes, your ThemeProvider will automatically:
- Read the updated semantic color values
- Generate new CSS variables with these values
- Inject them into the DOM
- All components using these variables will instantly update
No need to manually update multiple files or components - the changes propagate throughout your entire application automatically!
This token-based approach offers several key advantages:
-
Single Source of Truth: Color definitions exist in one place only, eliminating inconsistencies.
-
Separation of Concerns:
- Primitives (raw values) are separated from semantics (usage)
- Semantic names are separated from implementation (CSS variables)
-
Theme Support: Allows different themes to map the same semantic names to different colors.
-
Dynamic Updates: Changes to semantic colors are automatically reflected throughout the application.
-
Maintainability: Changing brand colors requires minimal effort - edit just one file instead of hunting through the codebase.
-
Consistency: All components automatically stay in sync when colors change.
-
Developer Experience: No need to remember hex codes or constantly reference design specs.
The design system includes a comprehensive typography system with the Inter font family:
Responsive font sizes that adjust based on device:
// Font sizes with responsive variations
fontSizes.md.mobile // 14px on mobile
fontSizes.md.tablet // 15px on tablet
fontSizes.md.desktop // 16px on desktopPredefined text styles for common use cases:
// Heading styles
textVariants.h1 // Title styling
textVariants.h2 // Section heading styling
// Body text styles
textVariants.bodyDefault // Regular paragraph styling
textVariants.bodySmall // Smaller text stylingReact components for all text elements:
// Example usage
<H1>Page Title</H1>
<Text>Regular paragraph text</Text>
<TextSmall>Smaller text</TextSmall>
<Link href="#">Click me</Link>A consistent spacing system with increments of 4px:
Numeric spacing tokens that follow a clear pattern:
// Spacing scale examples
spacing[1] // 4px
spacing[2] // 8px
spacing[4] // 16px
spacing[6] // 24px
spacing[8] // 32pxNamed spacing aliases for common usage patterns:
// Component spacing
spacingAliases.componentSM // 8px
spacingAliases.componentMD // 16px
// Layout spacing
spacingAliases.layoutMD // 32px
spacingAliases.layoutLG // 48pxComponent with built-in spacing props:
// Box component
<Box p={4} mb={6}>Content with padding and margin</Box>
// Flex component
<Flex gap={2} direction="column">
<Box>Item 1</Box>
<Box>Item 2</Box>
</Flex>
// Stack components
<VStack gap={4}>
<Box>Stacked item 1</Box>
<Box>Stacked item 2</Box>
</VStack>A consistent border radius system:
A set of border radius values, with a minimum of 4px and default of 12px:
// Border radius scale
borderRadius.none // '0px'
borderRadius.xs // '4px' (minimum size)
borderRadius.sm // '8px'
borderRadius.md // '12px' (default)
borderRadius.lg // '16px'
borderRadius.xl // '24px'
borderRadius.full // '9999px' (for pills, circles)Named border radius aliases for specific components:
// Component radii
borderRadiusAliases.button // 12px (md)
borderRadiusAliases.card // 12px (md)
borderRadiusAliases.badge // 8px (sm)
borderRadiusAliases.modal // 16px (lg)Components accept various border radius props:
// Full border radius
<Box rounded="md">Rounded box</Box>
<Button rounded="full">Pill button</Button>
// Individual corners
<Box roundedTop="md">Top corners rounded</Box>
<Box roundedBottomRight="lg">Only bottom-right corner rounded</Box>Wrap your application with the ThemeProvider:
import { ThemeProvider } from './theme';
function App() {
return (
<ThemeProvider>
<YourApp />
</ThemeProvider>
);
}Access theme values in your components:
import { useTheme } from './theme';
function MyComponent() {
const { colors, mode, toggleMode } = useTheme();
return (
<div>
<p>Current theme: {mode}</p>
<button onClick={toggleMode}>Toggle Theme</button>
</div>
);
}Components can leverage CSS variables defined by the theme:
.my-component {
background-color: var(--color-surface);
color: var(--color-text-primary);
border: 1px solid var(--color-border-default);
font-family: var(--font-family-primary);
font-size: var(--font-size-md);
padding: var(--spacing-4);
margin: var(--spacing-2);
border-radius: var(--border-radius-md);
}