know.2nth.ai Design components
design · Component Libraries · Skill Leaf

Build once,
compose everywhere.

A component library is the practical output of a design system. Atoms combine into molecules, molecules into organisms, organisms into pages. The craft is in the API: how the component exposes its props, handles composition, documents itself, and fails gracefully when used wrong.

Live Atomic design Storybook Composition Visual regression

Components are contracts, not templates.

A component is a reusable piece of UI with a defined API — its props, its slots, its events. Atomic design gives you a vocabulary for scale: atoms (button, input, badge), molecules (search bar = input + button), organisms (header = logo + nav + search bar), templates (page layout with placeholder content), and pages (templates filled with real data).

The value is compounding. Every time you use a component instead of writing one-off markup, you get consistency, accessibility, and testability for free. Every time you copy-paste a component and tweak it, you create a maintenance debt that compounds just as fast in the other direction.

Storybook, variants, and composition.

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

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

export const Primary: StoryObj = {
  args: { variant: 'primary', children: 'Deploy' },
}
export const Ghost: StoryObj = {
  args: { variant: 'ghost', children: 'Cancel' },
}
// Component with variant pattern via cva (class-variance-authority)
import { cva, type VariantProps } from 'class-variance-authority'

const buttonVariants = cva(
  // Base classes shared across all variants
  'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:opacity-50',
  {
    variants: {
      variant: {
        primary:   'bg-blue text-paper hover:bg-blue-glow',
        secondary: 'bg-navy text-paper border border-border hover:bg-bg-card',
        ghost:     'bg-transparent text-secondary hover:bg-bg-card',
      },
      size: {
        sm: 'h-8 px-3 text-xs',
        md: 'h-10 px-4 text-sm',
        lg: 'h-12 px-6 text-base',
      },
    },
    defaultVariants: { variant: 'primary', size: 'md' },
  }
)

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

export function Button({ variant, size, className, ...props }: ButtonProps) {
  return <button className={buttonVariants({ variant, size, className })} {...props} />
}
// Composition pattern — compound component for a Card
function Card({ children, className }) {
  return <div className={`bg-card border rounded-lg p-6 ${className}`}>{children}</div>
}

Card.Header = ({ children }) => <div className="mb-4">{children}</div>
Card.Title  = ({ children }) => <h3 className="text-lg font-bold">{children}</h3>
Card.Body   = ({ children }) => <div className="text-sm text-secondary">{children}</div>

// Usage — composable, readable, no prop drilling
<Card>
  <Card.Header>
    <Card.Title>Deployment status</Card.Title>
  </Card.Header>
  <Card.Body>All services healthy.</Card.Body>
</Card>

Component library pitfalls.

Too many props, not enough composition

A Button with 18 props is not a good component — it's a configuration file. Split it. Use compound components, render props, or slots. If the prop list is growing, the component is doing too much.

Storybook without visual regression is documentation, not testing

Storybook shows you what a component looks like right now. Visual regression (Chromatic, Percy, Playwright screenshots) shows you what changed. Without the diff, you won't catch the subtle breakages.

Building a library before you have patterns

Don't abstract on day one. Build three instances of a pattern in real features, then extract the component. Premature abstraction locks you into the wrong API before you know what the right one is.

Forgetting the "unstyled" escape hatch

Every styled component should accept a className prop and pass it through. If consumers can't override your styles, they'll work around your component instead of using it.

Build vs adopt.

Build your own library when

  • Your brand has specific visual requirements that no existing library matches.
  • You have a dedicated design systems team (or at least one person) to maintain it.
  • You need cross-platform consistency (web + native) from a single source of truth.
  • You're using shadcn/ui as a starting point — it's designed to be forked and owned.

Where component libraries link in the tree.

Go deeper.