Every new design system starts with a Notion doc listing thirty components the team plans to build. Three quarters later, the team has shipped twenty, the product is using eight, and morale is rough.
The right way to start is to count what your product actually uses, build the smallest superset that covers it, and add components only when a real product surface needs one. Aspirational components rot.
The ten that matter for SaaS
Button, Input (text, number, search), Select (single, multi), Card, Modal/Dialog, Toast, Tabs, Table, Badge, Tooltip. With these ten, you can build 80% of any internal-tool or admin product.
Add Form (validation patterns), Pagination, EmptyState, and Skeleton when you hit them. Don't pre-build them.
The ten that matter for consumer / commerce
Button, Input, Select, Card (especially product card), Image (with aspect-ratio + lazy load), Carousel, BottomSheet (mobile), Filter, Search, Toast. Add ListingCard, DateRangePicker, and Gallery when the surfaces demand them.
Airbnb DLS and Pinterest Gestalt are the canonical references for consumer pattern depth.
API design: copy from people who got it right
shadcn/ui composes via primitives — its Dialog is a Trigger, Content, Header, Footer. Material UI uses prop-driven configuration. Polaris splits structural props from visual props. Each pattern is a tradeoff between flexibility and constraint.
Pick one. Mixing patterns within the same system is the worst outcome — engineers can't predict which API style applies and reach for the docs every time.
Controlled vs uncontrolled
Default to controlled for inputs that participate in form state. Default to uncontrolled (with refs) for self-managed widgets like Tooltip or Popover. Document which is which. Inconsistency here causes the bugs nobody ever finds the root cause for.
- 01Start with ten components, not thirty. Add only when product surfaces force it.
- 02SaaS ten and consumer ten overlap on input primitives and diverge on patterns above them.
- 03Pick one API style — composable primitives (shadcn) or prop-driven (Material) — and don't mix.
- 04Document controlled vs uncontrolled per component. The bugs caused by ambiguity are silent.
shadcn/ui
The composable-primitives pattern done well — copy-paste, fully owned, Radix-powered.
Polaris (Shopify)
The right reference for form-heavy admin patterns — IndexTable, FormLayout, Banner.
Component Registry
Drop-in shadcn-spec components (button, input, card, utils) — install with `npx shadcn@latest add`.