Skip to content
Back to skills

Design Component

ASecurity

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.

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 8, 2026
ai-agentstypescriptgoreactnodeawsrefactoringgitapifrontenddocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add jeffreytse/grimoire-core --skill design-component --agent claude-code

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.

Security grade badge for Design Component
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jeffreytse-design-component/badge)](https://www.skillsdirectory.com/skills/jeffreytse-design-component)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…