Usage
Atoms, molecules, organisms — which layer owns your new UI, and the rules for moving between them.
The three layers
Every piece of UI in ePassLive belongs to exactly one layer. The decision is about who composes whom:
Atoms — primitives consumers compose
The smallest self-contained units: buttons, inputs, badges, avatars, tags. An atom owns one DOM element, its tokens and its a11y contract. It never fetches data and never knows about other atoms.
Examples: Button, Input, Badge, Avatar, Tag, Switch
Molecules — reusable UI composites
Compositions of atoms with a small amount of behaviour: a label + input + error (FormField), a search input + results list (SearchSelect), a trigger + listbox (Dropdown). Molecules are still domain-agnostic — they render props, not business entities.
Examples: FormField, SearchSelect, Dropdown, DateRangePicker
Organisms — feature sections
Feature-level sections composed from molecules and atoms: page headers, event cards, data tables, checkout summaries. Organisms may bind to domain models and Medusa SDK types — this is the first layer allowed to know what an “event” is.
Examples: Header, EventCard, CartSummary, StatsTable
The layer-split twin problem
Some patterns exist twice: a placeholder molecule documented in patterns.ts and a real organism that grew in the storefront. When you find a twin:
• The implemented code wins — the catalog entry must be corrected to describe what actually ships (or marked planned/deprecated).
• If both exist legitimately, the molecule stays domain-agnostic and the organism composes it — the organism never re-implements the molecule’s internals.
• Document the resolution in the ADR; the docs-consistency gate (pnpm ds:consistency) will keep flagging the pair until the catalog matches reality.
The promotion pattern
Growth follows one direction: atoms → molecules → organisms. A composite that appears in three or more organisms is promoted to a molecule; a molecule that has become domain-aware is really an organism and should move. Demotion (splitting a layer) requires an ADR.
Promotion is additive: the old layer keeps a deprecation note pointing at the new home for at least one minor version.
The dual-import rule
Import from the barrel only. Deep imports into packages/design-system/src/... are lint-banned (they break the public API contract and the Docker standalone build):
✓ Barrel import
import { Button, Card, Avatar } from '@epasslive/design-system'✗ Deep import (banned)
import { Button } from '@epasslive/design-system/dist/components/atoms/Button/Button'Worked example — a feature card
You need to display a producer with their avatar, name and stats. Three candidate implementations:
| Composition | Layer | Why |
|---|---|---|
Avatar + Card | atoms | If the consumer assembles the layout themselves each time, the atoms stay untouched — no new DS code needed. |
UserCard = Card + Avatar + Name | molecule | Once the same assembly repeats, promote it to a domain-agnostic molecule. It takes props (avatarSrc, name, meta) and renders atoms. |
ProfileHeader = UserCard + stats | organism | When domain data (revenue stats, settlement state) enters, it becomes an organism that fetches/maps and composes UserCard. |
Rule of thumb: atoms render props they can name in one word; molecules render props they can name in a sentence; organisms render domain models.