Best Practices
Tips for organizing and maintaining typestyles in your project
Best Practices
These guidelines will help you write maintainable, scalable code with typestyles.
Naming conventions
Namespaces
Use kebab-case for namespaces. The namespace should describe the component or pattern:
// ✅ Good - descriptive and consistent
const button = styles.create('button', { ... });
const card = styles.create('card', { ... });
const navigationMenu = styles.create('navigation-menu', { ... });
// ❌ Avoid - generic or single-letter names
const b = styles.create('b', { ... });
const x = styles.create('x', { ... });
const component = styles.create('component', { ... });
Variants
Use camelCase for variant names. Be descriptive about the style purpose, not just the visual appearance:
const button = styles.create('button', {
// ✅ Good - describes the intent
base: { ... }, // The foundation styles
primary: { ... }, // The main action
secondary: { ... }, // Alternative action
danger: { ... }, // Destructive action
ghost: { ... }, // Subtle action
// Size variants
small: { ... },
medium: { ... },
large: { ... },
// State variants
disabled: { ... },
loading: { ... },
// ❌ Avoid - describes appearance only
blue: { ... },
big: { ... },
rounded: { ... },
});
Tokens
Group tokens by category. Use consistent naming within groups:
// ✅ Good - clear categories and consistent naming
export const color = tokens.create('color', {
// Semantic colors
primary: '#0066ff',
secondary: '#6b7280',
success: '#10b981',
warning: '#f59e0b',
danger: '#ef4444',
// Neutral scale
gray50: '#f9fafb',
gray100: '#f3f4f6',
gray200: '#e5e7eb',
// ...etc
// Text colors
text: '#111827',
textMuted: '#6b7280',
textInverse: '#ffffff',
// Surface colors
surface: '#ffffff',
surfaceRaised: '#f9fafb',
surfaceSunken: '#f3f4f6',
});
export const space = tokens.create('space', {
// Scale-based
0: '0',
1: '4px',
2: '8px',
3: '12px',
4: '16px',
5: '20px',
6: '24px',
8: '32px',
10: '40px',
12: '48px',
16: '64px',
20: '80px',
24: '96px',
// Semantic aliases (optional)
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
});
File organization
Monorepo/Package structure
src/
├── tokens/
│ ├── index.ts # Re-exports all tokens
│ ├── colors.ts # Color definitions
│ ├── spacing.ts # Spacing scale
│ ├── typography.ts # Font sizes, line heights
│ ├── radii.ts # Border radius
│ └── shadows.ts # Box shadows
├── components/
│ ├── Button/
│ │ ├── index.ts # Component export
│ │ ├── Button.tsx # Component implementation
│ │ └── Button.styles.ts # Style definitions
│ ├── Card/
│ │ ├── index.ts
│ │ ├── Card.tsx
│ │ └── Card.styles.ts
│ └── ...
├── layouts/
│ ├── Layout.styles.ts
│ └── ...
└── utils/
└── styles.ts # Shared utility styles
Token files
Keep tokens in dedicated files, grouped by category:
// tokens/colors.ts
import { tokens } from 'typestyles';
export const color = tokens.create('color', {
// Brand colors
brand50: '#eff6ff',
brand100: '#dbeafe',
brand500: '#3b82f6',
brand600: '#2563eb',
brand700: '#1d4ed8',
// Neutral scale
gray50: '#f9fafb',
gray100: '#f3f4f6',
// ... etc
});
// tokens/index.ts
export { color } from './colors';
export { space } from './spacing';
export { font } from './typography';
Component style files
Keep styles co-located with components or in a separate .styles.ts file:
Option 1: Same file (for simple components)
// Button.tsx
import { styles } from 'typestyles';
import { color, space } from '../tokens';
const button = styles.create('button', {
base: { ... },
primary: { ... },
});
export function Button({ variant, children }) {
return <button className={button('base', variant)}>{children}</button>;
}
Option 2: Separate file (for complex components)
// Button.styles.ts
import { styles } from 'typestyles';
import { color, space } from '../tokens';
export const button = styles.create('button', {
base: { ... },
primary: { ... },
secondary: { ... },
// ... many variants
});
// Button.tsx
import { button } from './Button.styles';
export function Button({ variant, children }) {
return <button className={button('base', variant)}>{children}</button>;
}
Token architecture
Semantic vs. literal tokens
Use literal tokens for the design system foundation, semantic tokens for application use:
// tokens/primitives.ts
// Raw values from your design system
export const primitives = {
colors: {
blue500: '#0066ff',
blue600: '#0052cc',
gray500: '#6b7280',
red500: '#ef4444',
},
space: {
1: '4px',
2: '8px',
3: '16px',
4: '24px',
},
};
// tokens/semantic.ts
// Semantic meanings mapped to primitives
export const color = tokens.create('color', {
primary: primitives.colors.blue500,
primaryHover: primitives.colors.blue600,
secondary: primitives.colors.gray500,
danger: primitives.colors.red500,
// ... etc
});
export const space = tokens.create('space', primitives.space);
Component-specific tokens
For complex components, you can scope tokens to just that component:
// components/Calendar/Calendar.tokens.ts
import { tokens } from 'typestyles';
export const calendar = tokens.create('calendar', {
daySize: '40px',
headerHeight: '48px',
gridGap: '4px',
});
// Usage in Calendar.styles.ts
import { calendar } from './Calendar.tokens';
const calendarStyles = styles.create('calendar', {
day: {
width: calendar.daySize,
height: calendar.daySize,
},
});
Token composition
Reference other tokens when defining new ones:
const space = tokens.create('space', {
1: '4px',
2: '8px',
3: '16px',
4: '24px',
5: '32px',
});
const size = tokens.create('size', {
// Reference space tokens for consistency
sm: space[1], // 4px
md: space[2], // 8px
lg: space[3], // 16px
icon: space[4], // 24px
});
Component patterns
Compound components
For related components (like Form + Input + Label), use consistent namespacing:
const form = styles.create('form', {
root: { ... },
field: { ... },
});
const input = styles.create('input', {
base: { ... },
error: { ... },
});
const label = styles.create('label', {
base: { ... },
required: { ... },
});
// Usage
<form className={form('root')}>
<div className={form('field')}>
<label className={label('base', 'required')}>Name</label>
<input className={input('base', error && 'error')} />
</div>
</form>
Variant composition
Compose variants from base to specific:
const button = styles.create('button', {
// Base styles every button has
base: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
border: 'none',
fontWeight: 500,
transition: 'all 150ms ease',
},
// Visual variants
primary: { backgroundColor: color.primary, color: '#fff' },
secondary: { backgroundColor: color.secondary, color: '#fff' },
ghost: { backgroundColor: 'transparent' },
// Size variants
small: { padding: '4px 8px', fontSize: '12px' },
medium: { padding: '8px 16px', fontSize: '14px' },
large: { padding: '12px 24px', fontSize: '16px' },
// State variants
disabled: { opacity: 0.5, cursor: 'not-allowed' },
loading: { cursor: 'wait' },
});
// Usage
button('base', 'primary', 'medium'); // Primary medium button
button('base', 'primary', 'medium', 'disabled'); // Disabled primary medium
button('base', 'ghost', 'small'); // Small ghost button
Layout components
Use layout components with flexible spacing:
const stack = styles.create('stack', {
base: {
display: 'flex',
flexDirection: 'column',
},
// Spacing variants
gap1: { gap: space[1] },
gap2: { gap: space[2] },
gap3: { gap: space[3] },
gap4: { gap: space[4] },
});
const row = styles.create('row', {
base: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
gap1: { gap: space[1] },
gap2: { gap: space[2] },
// ... etc
});
State management
Boolean variants
For simple on/off states:
const card = styles.create('card', {
base: { ... },
elevated: {
boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
},
interactive: {
cursor: 'pointer',
'&:hover': {
boxShadow: '0 8px 12px rgba(0,0,0,0.15)',
},
},
});
// Usage
card('base', isElevated && 'elevated', isInteractive && 'interactive');
Complex state
For more complex state combinations, consider using a helper:
function getButtonClasses({ variant, size, isDisabled, isLoading }: ButtonProps) {
return button(
'base',
variant, // 'primary' | 'secondary' | 'ghost'
size, // 'small' | 'medium' | 'large'
isDisabled && 'disabled',
isLoading && 'loading',
);
}
Theming patterns
Dark mode with createTheme
// tokens/index.ts
export const color = tokens.create('color', {
text: '#111827',
textMuted: '#6b7280',
surface: '#ffffff',
surfaceRaised: '#f9fafb',
});
export const darkTheme = tokens.createTheme('dark', {
color: {
text: '#e0e0e0',
textMuted: '#9ca3af',
surface: '#1a1a2e',
surfaceRaised: '#25253e',
},
});
// Apply theme to document body or specific container
document.body.classList.add(darkTheme);
Multiple themes
const brandLight = tokens.createTheme('brand-light', { ... });
const brandDark = tokens.createTheme('brand-dark', { ... });
const highContrast = tokens.createTheme('high-contrast', { ... });
Code style
Consistent formatting
// ✅ Good - consistent structure
const card = styles.create('card', {
base: {
padding: space[4],
borderRadius: '8px',
backgroundColor: color.surface,
},
elevated: {
boxShadow: shadow.md,
},
interactive: {
cursor: 'pointer',
transition: 'box-shadow 200ms ease',
'&:hover': {
boxShadow: shadow.lg,
},
},
});
// ❌ Avoid - inconsistent formatting
const card = styles.create('card', {
base: { padding: space[4], borderRadius: '8px', backgroundColor: color.surface },
elevated: { boxShadow: shadow.md },
});
Import organization
// 1. External imports
import React from 'react';
import { styles, tokens } from 'typestyles';
// 2. Internal absolute imports
import { color, space } from '@/tokens';
import { Button } from '@/components';
// 3. Internal relative imports
import { card } from './Card.styles';
import { useCardState } from './useCardState';
Performance tips
- Define styles at module level - Never create styles inside components
- Reuse token references - Define once, import many times
- Minimize variants - Don't create a variant for every possible state
- Use inline styles for dynamic values - Don't create styles for frequently changing values
- Lazy load component styles - Use dynamic imports for code splitting
Common mistakes to avoid
❌ Creating styles inside components
// Bad - creates new styles on every render
function Button({ children }) {
const button = styles.create('button', { base: { ... } }); // ❌
return <button className={button('base')}>{children}</button>;
}
// Good - define at module level
const button = styles.create('button', { base: { ... } });
function Button({ children }) {
return <button className={button('base')}>{children}</button>;
}
❌ Dynamic style values
// Bad - creates styles for every possible value
const button = styles.create('button', {
base: { width: props.width }, // ❌ Don't do this
});
// Good - use inline styles for dynamic values
const button = styles.create('button', {
base: { display: 'inline-block' },
});
function Button({ width, children }) {
return (
<button
className={button('base')}
style={{ width }} // ✅ Dynamic value here
>
{children}
</button>
);
}
❌ Duplicate namespaces
// File A
const button = styles.create('button', { ... });
// File B
const button = styles.create('button', { ... }); // ❌ Collision!
// Good - use descriptive names
const iconButton = styles.create('icon-button', { ... });
const textButton = styles.create('text-button', { ... });
Summary
- Use descriptive, consistent naming for namespaces and variants
- Organize tokens by category in dedicated files
- Define styles at module level, never in components
- Use semantic tokens for application code
- Keep variants focused on style purpose, not appearance
- Use inline styles for truly dynamic values
- Use the Vite plugin to catch duplicate namespaces