← Back to Skills

Design System

design

Build consistent, scalable component libraries following Atomic Design principles and modern design system practices.

Design System

Guidelines for building maintainable, scalable design systems and component libraries.

When to Activate

Atomic Design Principles

Structure components hierarchically

atoms/       → Button, Input, Icon, Text
molecules/   → SearchInput, FormField, Card
organisms/   → Header, ProductCard, CommentThread
templates/   → PageLayout, DashboardLayout
pages/       → HomePage, ProductPage

Atoms: Single-purpose primitives

// GOOD - focused, composable
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'ghost';
  size: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
  onClick?: () => void;
  disabled?: boolean;
}

// BAD - too many responsibilities
interface ButtonProps {
  text: string;
  icon?: string;
  iconPosition?: 'left' | 'right';
  loading?: boolean;
  loadingText?: string;
  tooltip?: string;
  // ... 20 more props
}

Molecules: Meaningful combinations

// GOOD - combines atoms with clear purpose
function SearchInput({ onSearch, placeholder }) {
  return (
    <div className="search-input">
      <Icon name="search" />
      <Input placeholder={placeholder} />
      <Button onClick={onSearch}>Search</Button>
    </div>
  );
}

Design Tokens

Use semantic naming

/* GOOD - semantic tokens */
:root {
  /* Primitives (don't use directly in components) */
  --color-blue-500: #3b82f6;
  --color-gray-100: #f3f4f6;
  
  /* Semantic (use these in components) */
  --color-primary: var(--color-blue-500);
  --color-background: var(--color-gray-100);
  --color-text-primary: var(--color-gray-900);
  --color-text-secondary: var(--color-gray-600);
  
  /* Component-specific */
  --button-bg: var(--color-primary);
  --button-text: white;
}

/* BAD - magic values */
.button {
  background: #3b82f6;
  color: white;
}

Define spacing scale

:root {
  --space-1: 0.25rem;  /* 4px */
  --space-2: 0.5rem;   /* 8px */
  --space-3: 0.75rem;  /* 12px */
  --space-4: 1rem;     /* 16px */
  --space-6: 1.5rem;   /* 24px */
  --space-8: 2rem;     /* 32px */
}

Component API Design

Prefer composition over configuration

// GOOD - composable
<Card>
  <Card.Header>
    <Card.Title>Product</Card.Title>
    <Card.Actions><Button>Edit</Button></Card.Actions>
  </Card.Header>
  <Card.Body>Content here</Card.Body>
</Card>

// BAD - configuration explosion
<Card
  title="Product"
  showActions
  actions={[{ label: 'Edit', onClick: handleEdit }]}
  headerVariant="large"
  bodyPadding="lg"
/>

Use consistent prop patterns

// Size: 'sm' | 'md' | 'lg'
// Variant: 'primary' | 'secondary' | 'ghost'
// State: disabled, loading, error

// GOOD - predictable API
<Button size="md" variant="primary" disabled />
<Input size="md" variant="primary" disabled />
<Select size="md" variant="primary" disabled />

Storybook Best Practices

Write comprehensive stories

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Atoms/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'ghost'],
    },
    size: {
      control: 'select', 
      options: ['sm', 'md', 'lg'],
    },
  },
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    variant: 'primary',
    children: 'Click me',
  },
};

export const AllVariants: Story = {
  render: () => (
    <div style=>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="ghost">Ghost</Button>
    </div>
  ),
};

Document with MDX

{/* Button.mdx */}
import { Canvas, Meta, Story } from '@storybook/blocks';
import * as ButtonStories from './Button.stories';

<Meta of={ButtonStories} />

# Button

Buttons trigger actions. Use primary for main actions, secondary for alternatives.

## Usage Guidelines

- One primary button per section
- Use loading state for async actions
- Disable when action unavailable

<Canvas of={ButtonStories.Primary} />

Accessibility

Always include ARIA attributes

// GOOD
<button
  aria-label="Close dialog"
  aria-pressed={isPressed}
  disabled={isDisabled}
>
  <Icon name="x" aria-hidden="true" />
</button>

// GOOD - form inputs
<label htmlFor="email">Email</label>
<input 
  id="email"
  type="email"
  aria-describedby="email-error"
  aria-invalid={hasError}
/>
{hasError && <span id="email-error">Invalid email</span>}

Ensure keyboard navigation

// GOOD - focusable, keyboard handlers
<div
  role="button"
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      handleClick();
    }
  }}
>
  Custom Button
</div>

File Structure

design-system/
├── tokens/
│   ├── colors.css
│   ├── spacing.css
│   └── typography.css
├── atoms/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.stories.tsx
│   │   ├── Button.test.tsx
│   │   └── index.ts
│   └── Input/
├── molecules/
├── organisms/
└── index.ts  # Public exports only