Use when creating a new UI component, refactoring an existing one into something reusable, or reviewing a component design for composability, accessibility, and maintainability before implementation.
Installs into .claude/skills of the current project.
Are you the author of Design Component?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/jeffreytse-design-component)
---
name: design-component
description: Use when creating a new UI component, refactoring an existing one into something reusable, or reviewing a component design for composability, accessibility, and maintainability before implementation.
source: Atomic Design methodology (Brad Frost, 2013), Airbnb React component guidelines, Meta (React team) component design principles, Storybook component-driven development documentation
tags: [component-design, react, ui, atomic-design, accessibility, props-contract, composability, frontend]
verified: true
---
# Design Component
Design a reusable UI component with a clear single responsibility, stable props contract, and built-in accessibility before writing implementation code.
## Why This Is Best Practice
**Adopted by:** Airbnb (open-sourced their component library `react-dates` with props-contract-first design, documented in their React styleguide), Meta (React team's own components follow single-responsibility and composition patterns documented in react.dev), Shopify (Polaris design system, 300+ components, each with documented props contract and accessibility spec), BBC (GEL design system mandates WCAG 2.1 AA on every component)
**Impact:** Component-driven development with Storybook reduces UI bug rate by 40% and speeds up cross-team reuse by 3× (Storybook 2023 user survey, n=2,000+ teams). Airbnb's investment in component design standards reduced frontend rework from 35% of frontend engineer time to 8% over 18 months (Airbnb Engineering Blog, 2019). WCAG 2.1 AA compliance at component level prevents 15–20% of accessibility lawsuits — US ADA digital accessibility cases grew 300% from 2017 to 2022 (UsableNet 2022 report).
**Why best:** Designing the props contract before implementation prevents interface churn — once consumers depend on props, renaming costs N call sites. Atomic Design's bottom-up composition (atoms → molecules → organisms) creates reusable primitives rather than one-off page components. Alternative (build page-specific components first, extract later) produces duplicated components that diverge and become unmaintainable.
Sources: Brad Frost "Atomic Design" (2013), Airbnb Engineering Blog (2019), Storybook User Survey (2023), UsableNet ADA Report (2022), react.dev component guidelines
## Steps
### 1. Define single responsibility
Write one sentence: "This component renders [what] and handles [what interaction]."
If you use "and" more than once, it's two components. Split it.
Good: "A `Button` renders a clickable element with a label and calls `onClick` when activated."
Bad: "A `UserCard` renders user info, fetches user data, handles follow/unfollow, and shows a notification on success."
The UserCard example should be: `UserCard` (display) + `useFollowUser` (data/logic) + `Toast` (notification).
### 2. Determine the Atomic Design level
| Level | Definition | Examples |
|---|---|---|
| Atom | Single HTML element or primitive | `Button`, `Input`, `Avatar`, `Badge` |
| Molecule | 2–3 atoms with one function | `SearchBar` (Input + Button), `FormField` (Label + Input + Error) |
| Organism | Complex, domain-specific section | `UserCard`, `NavigationBar`, `ProductGrid` |
| Template | Page layout, no real data | `DashboardLayout`, `AuthLayout` |
| Page | Template + real data | `DashboardPage`, `LoginPage` |
Prefer building atoms and molecules. Organisms and templates are composed from them.
### 3. Design the props contract
List every prop the component needs. For each:
```typescript
interface ButtonProps {
// Content
children: React.ReactNode; // required: button label or content
// Behavior
onClick?: () => void; // optional: action on click
type?: 'button' | 'submit' | 'reset'; // optional: form behavior, default 'button'
disabled?: boolean; // optional: disables interaction
// Appearance
variant?: 'primary' | 'secondary' | 'ghost'; // optional: visual style, default 'primary'
size?: 'sm' | 'md' | 'lg'; // optional: size scale, default 'md'
// Extensibility
className?: string; // optional: consumer style override
'aria-label'?: string; // optional: accessible name when no visible label
}
```
Rules for props:
- Required props = must always be provided. Use sparingly — more required = harder to use.
- Provide sensible defaults for optional props.
- Use string unions over boolean props when there are more than two states: `variant='primary'` not `isPrimary + isSecondary + isGhost`.
- Expose `className` or `style` for consumer overrides — don't lock in every style detail.
- Pass through native HTML attributes (`...rest`) for native elements.
### 4. Design for composition, not configuration
Prefer slot-based composition over a proliferating prop API.
**Configuration hell (avoid):**
```tsx
<Card
title="Hello"
subtitle="World"
image="/photo.jpg"
imagePosition="top"
showFooter
footerText="Read more"
footerUrl="/post"
/>
```
**Composition (prefer):**
```tsx
<Card>
<Card.Image src="/photo.jpg" />
<Card.Body>
<Card.Title>Hello</Card.Title>
<Card.Subtitle>World</Card.Subtitle>
</Card.Body>
<Card.Footer>
<a href="/post">Read more</a>
</Card.Footer>
</Card>
```
Composition lets consumers change layout, omit sections, and add content without new props.
### 5. Add accessibility requirements
Every component must meet WCAG 2.1 AA. Define requirements before implementation:
- **Keyboard**: can the user interact with Tab, Enter, Space, Escape, arrow keys as appropriate?
- **Screen reader**: does the element have an accessible name? (`aria-label`, `aria-labelledby`, or visible text)
- **Focus management**: does focus move correctly on open/close of modals, dropdowns?
- **Color contrast**: 4.5:1 ratio for text, 3:1 for large text and UI components.
- **Motion**: does it respect `prefers-reduced-motion`?
Write the a11y requirements as acceptance criteria before implementing:
```
- Tab focuses the button
- Enter and Space trigger onClick
- disabled button is not focusable (tabIndex=-1) and announces "dimmed" to screen readers
- aria-label is set when children is an icon only
```
### 6. Write a Storybook story (or equivalent)
Before implementation, write the component's stories as a usage spec:
```tsx
// Button.stories.tsx
export const Primary: Story = { args: { children: 'Save', variant: 'primary' } };
export const Disabled: Story = { args: { children: 'Save', disabled: true } };
export const IconOnly: Story = { args: { children: <SaveIcon />, 'aria-label': 'Save' } };
export const Loading: Story = { args: { children: 'Save', disabled: true, /* loading state */ } };
```
If you can't write a story for an edge case, the props contract doesn't cover it yet. Fix the contract.
### 7. Implement and document
Implement against the props contract and stories. Include JSDoc on the component:
```tsx
/**
* Primary action button. Use for the most important action on a screen.
* Use `variant="secondary"` for less prominent actions.
*
* @example
* <Button onClick={handleSave}>Save</Button>
* <Button variant="secondary" onClick={handleCancel}>Cancel</Button>
*/
export const Button = ({ children, variant = 'primary', ... }: ButtonProps) => { ... }
```
## Rules
- One component, one responsibility. "And" in the purpose statement = split the component.
- Never fetch data inside a display component — use a container/hook pattern.
- Always type props with TypeScript interfaces — no `any`, no untyped `props`.
- Always expose `className` and HTML passthrough (`...rest`) on wrapper elements.
- Every interactive component must be keyboard-accessible before shipping.
- Composition over configuration: max 5–6 props before using slot children.
- Write Storybook stories (or equivalent) for every variant and edge case.
## Examples
**Props contract for a reusable `TextInput`:**
```typescript
interface TextInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string; // required: visible label text
error?: string; // optional: error message shown below input
hint?: string; // optional: helper text shown below label
}
```
Extending `React.InputHTMLAttributes` passes through all native input attributes (placeholder, maxLength, autoFocus, etc.) without listing each one.
## Common Mistakes
- **God component**: one component renders an entire page section with its own data fetching and state — untestable, unreusable, unmaintainable.
- **Prop explosion**: 15+ props to configure every visual variant — use composition instead.
- **Hardcoded styles with no override**: wraps everything in a `div` with inline styles, no `className` exposed — forces consumers to override with `!important`.
- **Missing accessibility**: ships without keyboard support or aria attributes — fails WCAG; attracts ADA lawsuits.
- **No stories / visual tests**: visual regressions ship undetected; new engineers can't discover usage.
- **Skipping TypeScript interfaces**: props become implicit contracts; refactors break consumers silently.
- **Boolean prop proliferation**: `isLarge`, `isPrimary`, `isDanger` instead of `size='lg'` and `variant='danger'` — combinatorial explosion.