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

  1. Define styles at module level - Never create styles inside components
  2. Reuse token references - Define once, import many times
  3. Minimize variants - Don't create a variant for every possible state
  4. Use inline styles for dynamic values - Don't create styles for frequently changing values
  5. 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