Browse pages
Frontend Components
Every screen in a LiteSpec frontend draws with components from one shared UI package: small primitives such as inputs and badges, and larger composites such as sections and tables built from them. This page states what that package holds, how page code, the components under each route or module folder, composes its parts, and where a drawing that no composite makes belongs. Frontend developers and coding agents apply it whenever they build or change a screen, so the product keeps one visual language and a restyle stays a change in one place.
Stack: any Layer: frontend-ui
Concepts#
Primitives, Composites, and Page Code#
A primitive is a small generic control from the pinned baseline, such as an input, a badge, or a card. A composite is a shared component that fixes the layout, behavior, and accessibility for one job, such as a status badge or a data table, built from primitives. Page code is any component under a route or module folder: it composes composites inside one floorplan and declares no layout or styling of its own. Page code lives under the route and module folders that Frontend Structure defines, and the floorplan is the page component that fixes the page layout (Controlled UI Governance).
For example, the Badge primitive draws a label in any color, while the StatusBadge composite maps a business state to its tone and draws that. An expense page imports StatusBadge, so every color it draws is one the product agreed to.
The Shared UI Package#
The shared UI package holds every primitive and every composite in one place, behind documented entry points that applications import. The boundary is enforced rather than described: a page that reaches past the entry points, declares its own styling, or draws with raw elements fails lint, and each refusal names the composite to use instead. The author therefore gets the replacement in the same message as the refusal and does not have to search the package.
Drawings with No Composite#
Some drawings have no composite: a budget treemap, a receipt-scan preview, or a public marketing frame. They live in exception files whose names declare the exception, and they draw with semantic theme tokens, the named values the shared token definition holds. The file name keeps the exception visible, and the tokens keep it inside the product palette.
Standards#
Keep Shared UI in One Package#
ruleruleguidancelitespec/frontend/components#keep-shared-ui-in-one-packageA workspace MUST hold its shared primitives and its composites in one shared UI package, with no copy in an application.
Applies toartifact: any
Why
When two applications each hold a copy of one primitive, the first upstream update changes one copy and misses the other. Both copies still compile, so nothing reports the split, and the two applications drift into two visual languages. One package gives every application the same component, so one update reaches every screen.
Example
The primitives sit in one folder beside the composites, and the composites import them.
Checka reviewer answers
Reviewed forWhich shared primitives or composites live outside the shared package? Look in the workspace packages and the application sources.
Marked guidance: a reviewer answers this question.
Compose Composites in Page Code#
rulerulecheckablelitespec/frontend/components#compose-composites-in-page-codeA component under a route or module folder MUST import only the shared package's documented entry points.
Applies toartifact: component
Why
A composite fixes its layout and exposes only the inputs its job needs, so every page that uses it draws the same shape. If page code could reach past the composite to the primitives, one page would tune spacing or order for itself, and the next page would copy that tuning instead of the composite. The product would then hold as many layouts as pages.
Example
An expense section in the expenses app draws its facts, its state, and its commands through shared composites, and declares no element, class, or style binding.
EntitySection "title"
StatusBadge "state"
DetailActions "commands"The section imports only the shared package entry point, so the layout it draws is the one the shared package owns for every other entity page.
Checkchecked by a tool
Checked bylitespec model check reports [litespec/frontend/components#compose-composites-in-page-code] with the file and the refused import. Run it at the consumer root.
Marked checkable: the tool named here checks it.
Write No Class Name in Page Code#
ruleruleguidancelitespec/frontend/components#write-no-class-name-in-page-codeA component under a route or module folder MUST NOT declare a class or style binding.
Applies toartifact: component
Why
The floorplan and the display composites own every spacing, size, and placement choice on a page. A class or style binding in route code is a second layout that answers to no composite, and the next route copies it because it looks like the local way to draw. Two sources of layout then disagree on the first redesign, so keeping classes out of page code leaves the floorplan as the one place a visual change lands.
Each stack page names the class and style bindings this rule covers for its stack.
Checka reviewer answers
Reviewed forWhich class or style bindings appear in route and module sources? Look in route and module sources; each stack page names the bindings its lint refuses.
Marked guidance: a reviewer answers this question.
Use Composites for Layout and Interaction in Page Code#
ruleruleguidancelitespec/frontend/components#use-composites-for-layout-and-interaction-in-page-codeA component under a route or module folder MUST NOT declare a layout or interactive element.
Applies toartifact: component
Why
A raw element is only markup: it carries no focus handling, no state behavior, and no accessibility wiring, so each page that declares one rebuilds behavior the composite already carries, usually worse. The lint refusal names the composite to use instead, so the author gets the replacement in the same message as the refusal and does not have to search the shared package.
Checka reviewer answers
Reviewed forWhich layout or interactive elements appear in route and module sources? Look in route and module sources; each stack page names the refused elements and the composite each refusal names.
Marked guidance: a reviewer answers this question.
Prefer a Composite over a Primitive#
ruleruleguidancelitespec/frontend/components#prefer-a-composite-over-a-primitivePage code MUST import a composite wherever the shared package provides one.
Applies toartifact: component
Why
The composite holds the product's decision for one job: which layout, which behavior, which tone. The bare primitive holds none of those, so a page that imports it makes each decision again for itself, and two pages answer the same question two ways. Refusing the replaced primitive in page code sends every page to the decision already made.
Example
Card is Section, Table is DataTable, Badge is StatusBadge, and Input and Field are the form fields.
Checka reviewer answers
Reviewed forWhich page sources import a primitive where the shared package provides a composite? Look in route and module imports.
Marked guidance: a reviewer answers this question.
Place a Drawing No Composite Makes in an Exception File#
ruleruleguidancelitespec/frontend/components#place-a-drawing-no-composite-makes-in-an-exception-fileAn exception file MAY declare custom styling only with semantic theme tokens.
Applies toartifact: component
Why
Some drawings have no composite: a budget treemap, a receipt-scan preview, or a public marketing frame. Refusing them entirely would push the drawing into arbitrary values that no token governs, which is harder to find and restyle than a declared exception. The exception file keeps the drawing visible: its name says this page draws outside the composites, and semantic tokens keep it inside the product palette. Each stack page names the file pattern that marks an exception for its stack.
Checka reviewer answers
Reviewed forWhich custom styling sits outside the files whose names mark drawings outside the composites, or uses values other than semantic theme tokens? Look in the exception files each stack page names.
Marked guidance: a reviewer answers this question.
Add a Missing Composite to the Shared Package#
ruleruleguidancelitespec/frontend/components#add-a-missing-composite-to-the-shared-packageA layout, a control, or a surface a second page needs MUST be added to the shared UI package, not written inside an application.
Applies toartifact: component
Why
The second page that needs a layout, control, or surface proves the piece is shared, not page-specific. Written inside one application, it is reviewed against one use, styled for one context, and invisible to the floorplans that would carry it, so the third page writes it a third time or copies the first copy. Adding it to the shared package puts it where every page finds it and where its design is judged against every use.
Checka reviewer answers
Reviewed forWhich layout, control, or surface drawn by two pages is missing from the shared package exports? Look in the shared package exports and the drawing pages.
Marked guidance: a reviewer answers this question.
Support Keyboard, Focus, Labels, and Announcements#
ruleruleguidancelitespec/frontend/components#support-keyboard-focus-labels-and-announcementsInteractive UI MUST support keyboard operation, visible focus, semantic elements, programmatic labels, and asynchronous status announcements.
Applies toartifact: component, route
Why
Keyboard operation, visible focus, semantic elements, programmatic labels, and status announcements come free with platform controls and all break at once in a custom control. A mouse-only control locks out keyboard readers, a missing label leaves screen-reader users guessing, and a silent update leaves anyone who does not see the screen change unaware of it. Starting from a platform control keeps all five properties, and the composite keeps them for every page that uses it.
The five properties above are the ones a component can satisfy on its own. WCAG 2.2 adds criteria that only a flow or a page can satisfy, so each route answers them where the criterion applies:
| Criterion | Level | What it means for the UI |
|---|---|---|
| 2.4.11 Focus Not Obscured (Minimum) | AA | A focused control stays at least partly visible under a sticky header, bar, or panel. |
| 2.4.12 Focus Not Obscured (Enhanced) | AAA | A focused control stays fully visible. |
| 2.4.13 Focus Appearance | AAA | The focus indicator meets the stated minimum area and contrast. |
| 2.5.7 Dragging Movements | AA | Every drag action has a single-pointer alternative. |
| 2.5.8 Target Size (Minimum) | AA | A pointer target measures at least 24 by 24 pixels, or meets the stated spacing. |
| 3.2.6 Consistent Help | A | A help mechanism sits in the same relative order on every page that has one. |
| 3.3.7 Redundant Entry | A | Information already given in the same process is not asked for again. |
| 3.3.8 Accessible Authentication (Minimum) | AA | Sign-in offers a path with no cognitive function test. |
| 3.3.9 Accessible Authentication (Enhanced) | AAA | That path asks for no object recognition or personal content test either. |
The level column marks which target each criterion belongs to: AA for every project, AAA for a project that targets AAA.
Checka reviewer answers
Reviewed forWhich interactive UI lacks keyboard operation, visible focus, semantic elements, programmatic labels, or status announcements? Look in the route suite output.
Marked guidance: a reviewer answers this question.
Render Data States#
ruleruleguidancelitespec/frontend/components#render-data-statesA data component MUST render loading, empty, error, forbidden, and ready.
Applies toartifact: component
Why
A component that renders only its ready state leaves the reader with a blank region while data loads, after it fails, or when there is nothing to show. Each missing state is a screen with no design and no test visiting it. Naming every arrival state up front gives each one a designed drawing.
A route adds not-found under litespec/frontend/rendering#represent-route-states, because a missing target resolves at the route rather than inside a component. Each state name is a term in the project glossary.
Checka reviewer answers
Reviewed forWhich data components miss a loading, empty, error, forbidden, or ready state, or name a state outside the glossary? Look in the shared package components and the glossary.
Marked guidance: a reviewer answers this question.
Render Control States#
ruleruleguidancelitespec/frontend/components#render-control-statesA control MUST render disabled and pending so that a second submission stays impossible while one is in flight and recovery stays usable.
Applies toartifact: component
Why
A control with no disabled state invites a second submission while the first is still in flight, and a control with no pending state shows no sign that anything is happening. Blocking the duplicate submission and keeping recovery usable give the reader one safe path: wait, or recover. State names are glossary terms, as under litespec/frontend/components#render-data-states.
Checka reviewer answers
Reviewed forWhich controls miss a disabled or pending state, allow a second submission while one is in flight, or leave recovery unusable? Look in the shared package components.
Marked guidance: a reviewer answers this question.
Keep Component Inputs Narrow#
ruleruleguidancelitespec/frontend/components#keep-component-inputs-narrowA composite MUST receive the values and callbacks it needs, not a client, a store, or an aggregate.
Applies toartifact: component
Why
The shared package serves applications it was not written against. A composite that takes a client, a store, or an aggregate takes one application's data model as its input, so it fits no application with a different model, and a change to that model breaks the shared package. Values and callbacks keep the composite free of any application's shape: the page translates its model into plain inputs, and the composite renders what it is given.
Checka reviewer answers
Reviewed forWhich composite inputs name a client, a store, or an aggregate instead of values and callbacks? Look in the shared package component signatures.
Marked guidance: a reviewer answers this question.
Use Declared Visual Variants#
ruleruleguidancelitespec/frontend/components#use-declared-visual-variantsA repeated component variant MUST use theme tokens through the shared package's variant helper rather than repeated literal values.
Applies toartifact: component
Why
When each composite writes its own pixel values, colors, or conditional branches for one variant, the copies start equal and drift with every later edit, because nothing points the author at the other copies. The variant helper holds each variant once, in theme tokens, so a change to the variant reaches every composite that uses it and every value stays inside the theme.
Checka reviewer answers
Reviewed forWhich repeated variants bypass the shared variant helper or carry literal values in composites? Look in the shared package components.
Marked guidance: a reviewer answers this question.
Sanitize Untrusted Rich Content#
ruleruleguidancelitespec/frontend/components#sanitize-untrusted-rich-contentA component MUST NOT render untrusted rich content without its recorded sanitization decision.
Applies toartifact: component
Why
Rich content written by one user renders in another user's browser, so unsanitized markup is a script injection path: whoever writes the content runs code as every reader. The recorded decision names the sanitizer, the allowed elements and attributes, the link policy, and the test cases, so the boundary is explicit and reviewable instead of a different ad-hoc escape on every page.
Checka reviewer answers
Reviewed forWhich untrusted rich content renders without a recorded decision naming the sanitizer, the allowed elements and attributes, the link policy, and the test cases? Look in the decision records and the rendering components.
Marked guidance: a reviewer answers this question.
Use a Composite for a Content Image#
ruleruleguidancelitespec/frontend/components#use-a-composite-for-a-content-imageAn informative or decorative image MUST render through the shared image component, which owns its shape, its sizing, and its fallback.
Applies toartifact: component
Why
An informative image needs meaningful alternative text, and a decorative image needs empty alternative text so assistive tools skip it. A source and label written directly in a page bypass both rules: the author tracks which kind each image is and writes the matching text, on every page. The shared image component owns the shape, the sizing, the fallback, and the text policy, so each image gets the handling its kind needs. The raw-element rule refuses unwrapped images in page code under litespec/frontend/components#use-composites-for-layout-and-interaction-in-page-code.
Checka reviewer answers
Reviewed forWhich content images render outside the shared image component or against the text policy for their kind? Look in page sources for unwrapped images.
Marked guidance: a reviewer answers this question.
Conventions#
Name Components for Their Role#
conventiondefaultguidancelitespec/frontend/components#name-components-for-their-roleName a component for the role it plays, such as ExpenseLinesSection or ReplayDeliveryDialog.
ReplacementA consumer can replace this default with an explicit local convention.
Applies toartifact: component
Why
A name such as ExpenseComponent or GenericModal describes the file type, so it fits any component and tells a searcher nothing. A name that states the role, such as ExpenseLinesSection or ReplayDeliveryDialog, tells the next author what the component does before they open it, and keeps two components for two jobs from sharing one vague name.
Checka reviewer answers
Reviewed forWhich component names hide the role they play? Look in the shared package and module sources.
Marked guidance: a reviewer answers this question.
Expose Card and Section Titles as Headings#
conventiondefaultguidancelitespec/frontend/components#expose-card-and-section-titles-as-headingsRender a visible card, panel, or section title as a heading at its correct level.
ReplacementA consumer can replace this default with an explicit local convention.
Applies toartifact: component
Why
Some primitive sources render a title prop as a plain text element. That removes the title from the page outline, so screen-reader users lose it as a navigation point and the route suite's heading check misses it. Rendering the title as a heading at its correct level keeps the outline and the test in agreement.
Checka reviewer answers
Reviewed forWhich visible card, panel, or section titles render as non-heading elements? Look in the route suite output, which resolves each as a heading.
Marked guidance: a reviewer answers this question.
Keep Domain Values Typed Until Display#
conventiondefaultguidancelitespec/frontend/components#keep-domain-values-typed-until-displayKeep branded and generated identifier types until the presentation boundary converts them.
ReplacementA consumer can replace this default with an explicit local convention.
Applies toartifact: component
Why
Dates, money, and status values carry meaning a display string loses: the instant, the amount, the code. Converting early spreads formatting choices across every component that touches the value, and each one picks its own locale and zone. Keeping the typed value until the presentation boundary converts it in one place, with explicit locale and zone behavior.
Checka reviewer answers
Reviewed forWhich display strings are produced before the presentation boundary, or without an explicit locale and zone? Look in the presentation boundary and the components feeding it.
Marked guidance: a reviewer answers this question.
Keep Error Boundaries Scoped#
conventiondefaultguidancelitespec/frontend/components#keep-error-boundaries-scopedUse the route error surface for route failure, and a component boundary only where recovery keeps the page usable.
ReplacementA consumer can replace this default with an explicit local convention.
Applies toartifact: component, route
Why
An error boundary that catches a failure with no recovery of its own hides the error and leaves the surrounding page broken but silent. The route error surface owns route failure, with a designed recovery; a component boundary earns its place only where the rest of the page stays usable without the failed part. Each stack page names the error surface for its stack.
Checka reviewer answers
Reviewed forWhich route failures render outside the route error surface, and which component boundaries leave their surroundings unusable? Look in the route error surfaces and the boundary components.
Marked guidance: a reviewer answers this question.
Reference Example#
This informative example demonstrates litespec/frontend/components#compose-composites-in-page-code and litespec/frontend/components#use-composites-for-layout-and-interaction-in-page-code.
An expense section renders its facts, its commands, and its states by composing display composites and shared form fields. It declares no element and no styling, so the layout it draws is the one every other entity page draws.