import { Meta, Canvas } from '@storybook/addon-docs/blocks'; import * as ButtonStories from '../app/shared/ui/button/button.stories'; import * as FormFieldStories from '../app/shared/ui/form-field/form-field.stories'; import * as PageShellStories from '../app/shared/layout/page-shell/page-shell.stories'; # Atomic design Every screen in this app is built from a small set of layers, each composed **only from the layer below it**. Read a screen top-down and you always land on the same handful of atoms — that is the whole point: fewer things to understand, nothing bespoke per page.
{[ ['Templates', 'shared/layout', 'shell, page-shell, wizard-shell — the page skeleton', '#1e3a5f'], ['Organisms', 'shared/ui/upload/document-upload …', 'self-contained sections that own a bit of behaviour', '#2a5a8a'], ['Molecules', 'shared/ui/form-field, async …', 'a label + control + error, grouped', '#3f7cb5'], ['Atoms', 'shared/ui/button, text-input …', 'thin wrappers over Utrecht/RHC CSS classes', '#6aa6d8'], ].map(([name, where, why, bg], i) => (
{name} — {why}
{where}
))}
## The rule, enforced **Each layer only uses layers below it, and dependencies point inward.** This is not a convention you have to remember — `eslint.config.mjs` fails the build if `domain/` imports Angular, or if a context imports "upward". See [the FP-in-the-UI primer](?path=/docs/foundations-fp-in-the-ui--docs) for how the same discipline shapes state and effects. ## A composition chain, live Here is one real chain from atom → molecule → template. Each is a published Storybook story below; click through to the sidebar entries to explore every variant. ### Atom — `button` A thin wrapper: we own a typed `variant` input, the RHC CSS owns the pixels. ### Molecule — `form-field` Label + control + error text, grouped so the error is announced via `role="alert"`. It composes atoms; it adds no new visual primitives of its own. ### Organism — `document-upload` `shared/ui/upload/document-upload` composes molecules (a file input, status banner, progress bar, chips) into a section that owns real upload behaviour. It has no story yet (Track A backlog); read it at `src/app/shared/ui/upload/document-upload/`. ### Template — `page-shell` The page skeleton — title, optional back-link, content slot. Pages drop composed organisms into it; the template never knows what they are. ## Why bother A new page should be **composition of existing blocks**. Adding a new building block is the exception, not the reflex — if you reach for one, that is a signal to check whether an existing atom/molecule already covers it. Fewer primitives → less to test, less to learn, one place to fix a bug.