Home / Guides / Compose a pattern

How to compose a new pattern

Patterns are whole-surface assemblies that the system guarantees. They cost more than blocks — promote one only when the same composition shows up twice.

Guide Two-pages rule State machine + URL

When to make a pattern

The "two pages or more" rule — and a short list of exceptions.

A pattern is more than a composition of blocks. It carries behavior the system promises across all uses: a state machine, keyboard contract, URL convention, and a responsive plan. That promise is expensive to maintain, so we wait for proof.

Promote to a pattern when:

  • The same composition has been built on two or more pages — once is a coincidence, twice is a pattern.
  • The behavior is non-trivial: focus management, URL hash, optimistic update, undo, conflict resolution.
  • Multiple teams are about to build the same thing independently this quarter.

Don't promote to a pattern when:

  • It's a single block with new content (it's still that block — see Block vs Pattern vs Page).
  • The shape is right but the behavior varies per page — that's a page composition.
  • It only lives on a marketing surface — patterns are for product chrome.

What's in a pattern

A pattern doc declares four things. If any are missing the pattern is incomplete.

1 — Block composition

The exact list of blocks and components used, in render order. Each item links to its parts doc. A pattern with one block is probably just that block — re-read block vs pattern.

2 — State machine

The named states the pattern can be in and the transitions between them. Most patterns have between three and seven states. For Data table: empty · loading · loaded · selecting · bulk-action · error. Draw the machine before writing the markup.

3 — URL convention

What in the pattern is reflected in the URL? Hash for ephemeral state (open tab, selected row). Query string for filter state that should be shareable. Path for navigation. A pattern with no URL story breaks browser back, deep links, and bookmarking.

4 — Responsive plan

One sentence per breakpoint describing how the pattern degrades. Use the breakpoint conventions from Mobile adaptation. A pattern with no mobile plan is a desktop-only feature — document that explicitly.

Worked example — Detail + tabs

A multi-aspect record page, used by Customer detail, Invoice detail, Product detail. Three pages → pattern.

Block composition

The pattern is the assembly, in order, of:

  • Page header · compact variant, breadcrumb + back arrow + 1–2 actions.
  • Detail hero · key identifying facts and the primary action ("Issue invoice", "Refund order").
  • Segmented control · the tab strip — 3 to 6 tabs.
  • Tab body · a single visible region that swaps content based on the active tab. Each tab's content is composed from blocks like any other page (see Compose a page).

State machine

// 5 states, 6 transitions
loading        → loaded        // data arrived
loading        → error         // fetch failed
error          → loading       // retry pressed
loaded         → tab-switching // tab clicked, new tab loading
tab-switching  → loaded        // new tab data arrived
loaded         → stale         // websocket says record changed

The pattern guarantees: the hero never blanks during a tab switch (only the body region replaces). The stale state shows a non-blocking "Refresh" control — never a blocking dialog.

URL convention

SegmentHoldsExample
PathRecord identity/customers/cus_8a32
HashActive tab#activity
QueryTab-scoped filters?from=2026-04-01

The browser back button moves between tabs within a record. Closing the tab and re-opening the URL lands you exactly where you were.

Responsive plan

BreakpointBehavior
≥ 1100pxHero left, summary rail right, tabs span both columns.
900–1099pxSummary rail collapses below the tab body.
720–899pxHero stacks vertically. Tabs remain a segmented control.
< 720pxSegmented control becomes a select. Hero shows only title + primary action; the rest collapses into the first tab.

Document what your pattern guarantees

A pattern is a contract. Write the contract down so consumers know what they get for free.

Every pattern doc should list, near the top, the explicit guarantees. For Detail + tabs:

  • Tab switch never refetches the hero data.
  • Active tab is reflected in the URL hash and restored on reload.
  • Keyboard: / on the tab strip moves focus; Enter activates; tab strip is in the page tab order between hero and body.
  • Stale-data refresh is non-blocking — never interrupts an in-progress edit.
  • At <720px the segmented control swaps to a select; no horizontal scroll.

If you can't keep one of these promises, omit it from the contract — or fix it before promoting.

Naming and scope

A pattern's filename and label shape the way teams talk about it for years.

  • File prefix: x- (e.g. x-detail-tabs.html). Blocks are b-, components are p-, pages are pg-.
  • Label: short noun phrase that names the user-visible shape, not the internals. "Detail tabs" — not "TabbedRecordContainer".
  • Scope: one pattern does one thing. A "Detail + tabs + activity feed + comments" pattern is three patterns: Detail + tabs, Activity feed, Comment thread. Compose, don't combine.
  • Sidebar group: drop the entry into the Patterns group in system-shell.js next to its siblings.

Do & don't

Do

Wait for the second use case before promoting. Premature patterns get retrofitted painfully when the third use case disagrees.

Don't

Promote a single-page composition to a pattern "just in case." The block list and a worked example on the page are enough.

Do

Draw the state machine on paper before writing markup. It will catch missing states.

Don't

Ship a pattern without a URL convention. Browser back, deep linking, and bookmarking are not optional in an ERP.

Do

Make the pattern doc list explicit guarantees. Consumers cite them in PR reviews.

Don't

Combine two patterns into one. "Detail + tabs + activity" is three patterns composed by a page.

What's next

If you found yourself wanting to compose a whole new module (multiple pages, sidebar entry, IA decisions), read Adding a new feature module.