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.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add jeffreytse/grimoire-core --skill design-component --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Design Component?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeffreytse-design-component)More formats (shields.io, HTML) on the badges page.
---
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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!