Skip to main content

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:

Worked example — a feature card
CompositionLayerWhy
Avatar + CardatomsIf the consumer assembles the layout themselves each time, the atoms stay untouched — no new DS code needed.
UserCard = Card + Avatar + NamemoleculeOnce the same assembly repeats, promote it to a domain-agnostic molecule. It takes props (avatarSrc, name, meta) and renders atoms.
ProfileHeader = UserCard + statsorganismWhen 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.

Usage — ePassLive Design System