Pass/fail rules for how primitives assemble into a screen.
- Every screen has one identifiable purpose.
- Every screen has one primary action, expressed as one
defaultButton. - The primary action is visible in the initial viewport, unscrolled.
- Every screen has one title, naming the user task, never the implementation
(e.g.
Calendar, notSync engine). - A screen has at most three hierarchy levels: title, section, content.
- Decorative hero sections, slogans, and filler content are prohibited.
- A screen must remain understandable with all icons removed.
- A new layout pattern is not introduced when an existing pattern already fits.
- A section is typography and space by default. A Card or SurfacePanel exists to group related content that benefits from visual containment — metrics, summaries, grouped settings — never to indicate arbitrary state through a background or border.
- Dialog, Sheet, Card, and SurfacePanel are the only components allowed a
visible container (background separation, radius, elevation). Nothing nests
inside itself: no sheet inside a sheet, no dialog inside a dialog.
10a.
--control-borderis used on interactive fields and controls;--border-defaultis used on cards and containers. Adividerline between rows is a named exception — used only when adjacent rows would be genuinely ambiguous without it. "It looked bare" is not a reason to add a border. - One semantic group gets exactly one Section — never a Section inside a Section.
- Most UI uses
--shadow-none. Elevated surfaces (Card, Sheet, Dialog, DropdownMenu) may use shadows from the scale to separate from the background. Pick the lowest shadow that achieves the separation. - If two rows on one screen carry a container, they share the same treatment — never a mix of bordered and unbordered rows on one list.
- Gradients and purely decorative shadows are prohibited. A shadow exists
to separate a surface, not to decorate it. Borders use
--border-default. - FilterChip and Badge are restricted to compact status, filters, and tags — they are not a substitute for a button or a card.
- Radius is chosen from the defined scale. A component uses the radius its
contract specifies — a button does not pick
--radius-xljust to stand out. Nothing is ever "a bit more rounded" than the scale provides.
- Mobile content uses 16px as the horizontal gutter.
- All spacing comes from the spacing scale. The 4px unit is for internal control alignment, never a group gap.
- Touch targets are at least 44×44pt.
- Text inputs and primary buttons use their defined size contracts.
- Content never touches the screen edge.
- Spacing, oversized type tokens, or oversized surfaces are not used to create drama — pick the token the content's actual hierarchy calls for.
- Use sentence case everywhere copy appears.
- Uppercase is reserved for
SectionIntroeyebrow text and reference tags. All-caps UI copy, slogans, and feature taglines are otherwise prohibited. - Metaphors for ordinary features are prohibited.
- Do not use "lens," "hub," "workspace," "journey," "magic," or "intelligence" unless the word names a concrete user concept the feature actually implements.
- Titles describe the task:
Calendar,Archived chats,Account. - Button labels use a verb and are two words or fewer whenever possible.
- Labels describe the resulting action, not the component (
Delete chat, notDelete button). - Placeholder text describes the expected input, not a hint of tone.
- A title is never repeated in a subtitle beneath it.
- An action is not explained when its label already makes it obvious.
- Error copy states the problem and the recovery action in one sentence.
- Empty states state what is absent and what to do next.
- Loading states use a Spinner or Skeleton, never words.
Loading…,Saving…,Asking…, or equivalent loading copy is prohibited on-screen; that meaning lives in the Spinner's accessible label.- A control preserves its committed dimensions while loading.
- Duplicate interaction is disabled while an action is loading.
- Every Spinner has an accessible label.
- Skeletons are used for content loading; Spinners are used for action loading. Not interchangeable.
- Every async action defines success, empty, error, and retry states.
- Every feature defines initial, loading, success, empty, error, permission-denied, unavailable, and offline states before it ships.
- Visible controls always represent the current state — no stale control left over from a previous state.
- Setup controls (auth, permissions, configuration) do not appear on the task surface; they live in their own flow.
- Debug controls do not appear in production UI.
- A manual status check is not shown when status can load automatically.
- A List row is either
navigationaloractionable, never ambiguously both. - If a row has an inline action, the row itself is not tappable.
- Every async action prevents duplicate submission.
- Destructive actions require explicit confirmation via
Dialogwithalertenabled. - Navigation is always reversible with the platform back gesture.
- A Dialog is used for confirmation only, never for content that deserves its own screen.
- A screen is used for substantial content, never for a single confirmation.
- Icon-only actions require an accessible label.
- Personal data sources are named precisely (e.g. "your iOS Calendar," not "your data").
- Privacy copy describes actual behavior, not a generic assurance.
Good: Calendar data is processed on this device.
Bad: Your data is always safe.
- Source metadata is shown only when it helps a user verify a result.
- Raw implementation details are not shown by default.
- Personal data is not persisted solely to reproduce UI.
- The app never implies it can do something the underlying integration cannot do.
- Uncertainty in an answer is expressed in the copy, not hidden behind confident styling.
--primaryand--destructivecommunicate action and status.--successand--warningcommunicate state. Chart colors are reserved for data visualization. Everything else renders in--text-primary,--text-secondary, or--tertiaryas its hierarchy calls for.- Icons communicate state or action. Decorative icons are prohibited.
- Animation communicates a state change or spatial relationship, using a duration token from Foundations.
- Every animation resolves under reduced motion. No element may be invisible, displaced, or cut off in its static resting state.
- An existing token is used before a new one is proposed.
- An existing
@ponti-studios/uicomponent is used before a new one is proposed. - A new component requires a behavior none of the existing primitives can express — documented in the PR description.
- Route files compose primitives; they do not invent design systems.
- Hardcoded colors, radii, spacing values, font sizes, or durations in screen code are prohibited — every value must resolve to a token from Foundations.
- Every interactive element has an accessible name.
- Every interactive element has a 44×44pt touch target.
- Body text meets WCAG AA contrast against
--backgroundat every text level used for reading copy. - Focus, pressed, disabled, loading, and error states are each distinguishable by more than color alone — pair with a fill, ring, or icon change.
- Color is never the sole indicator of state (pair with icon, text, or shape).
- Dynamic content has an accessibility announcement or a stable reading order.
- The screen remains usable at the largest supported dynamic type size.
- Horizontal scrolling is never required to discover a primary action.
- A user-visible change is unverified until it is observed on its target device or browser in every changed state: idle, active/focused or loading, cancellation/error where applicable, and return/recovery.
- Before adding controls to a constrained region, the complete composition must be verified at the smallest supported viewport or container. Each control must remain visible, reachable, and legible; overlap, clipping, and undiscoverable actions fail review.
- An interactive control passes only when automation or equivalent direct observation proves both its activation and its resulting state. Rendering, an accessible label, or a successful type check alone does not prove the interaction works.
- App-owned controls require deterministic identifiers or another documented, reliable observation path. A platform primitive that cannot supply one is an implementation constraint to resolve or report before completion.