Browse pages
TypeScript Web Components
This page states how the TypeScript web stacks realize the frontend component principles (Frontend components).
Intent#
This page states how the TypeScript web stacks realize the frontend component principles (Frontend components). It states the registry source, the closed element list, and the visual file patterns. It also states the raw HTML sinks and the route suite checks the React and Svelte stacks share.
React#
The React stack uses these bindings:
- A class or style binding spells
classNameorstyle. - A visual file ends with
*.visual.tsx. - The raw HTML sink is
dangerouslySetInnerHTML. - The route error surface is the route error component.
- Component authoring follows
react/components.md.
Svelte#
The Svelte stack uses these bindings:
- A class or style binding spells
class,class:,style, orstyle:. - A visual file ends with
*.visual.svelte. - The raw HTML sink is
{@html}. - The route error surface is the route error page.
- Component authoring follows
svelte/components.md.
Standards#
Keep primitives and composites in one shared package#
ruleguidancelitespec/frontend/web/components#keep-primitives-and-composites-in-one-shared-packageA workspace MUST hold its registry primitives and its composites in one shared UI package, with no copy in an application.
Why
A primitive copied into two applications diverges on the first upstream update. The divergence is invisible because both copies compile. This realizes litespec/frontend/components#keep-shared-ui-in-one-package.
Example
The primitives sit in one folder that the registry CLI writes. No one edits that folder by hand. The composites sit beside them and import them.
Checka reviewer answers
Does one shared package hold the registry source and the composites, with no copy in an application?
Marked guidance: a reviewer answers this question.
Use no raw element in page code#
ruleguidancelitespec/frontend/web/components#use-no-raw-element-in-page-codeA component under a route or module folder MUST NOT declare a layout or interactive element, and each refusal names the composite to use.
Why
A raw element carries no focus management, no state, and no accessibility wiring. The reader of the lint message chooses between two answers. The message gives one. This realizes litespec/frontend/components#use-composites-for-layout-and-interaction-in-page-code.
Example
The refused elements and their replacements are one closed list.
| Refused | Use instead |
|---|---|
div, span | Stack, Inline, Columns, Section, or a floorplan slot |
button | Button from the shared package |
a | TextLink or ButtonLink |
input, select, textarea | a field from the shared form library |
form | the shared form component |
table | DataTable |
img | the shared image component, which litespec/frontend/components#use-a-composite-for-a-content-image owns |
Text elements stay available, because a sentence is not a component decision. p, h2, h3, em, strong, ul, ol, and li carry prose. The floorplan owns the h1, so it is not in the list. Any other element follows the same rule: page code declares no other element, and the author adds a composite or a visual file instead.
Checka reviewer answers
Does page code declare a refused element, and does each refusal name its composite?
Marked guidance: a reviewer answers this question.
Place a drawing no composite makes in a visual file#
ruleguidancelitespec/frontend/web/components#place-a-drawing-no-composite-makes-in-a-visual-fileA visual file MAY write class names, and uses semantic theme tokens only.
Why
A stock-location map, a packing-slip preview, and a public frame have no composite. Refusing them pushes the drawing into an arbitrary value that no token governs. The file name declares this exception. The React and Svelte sections name the file pattern. This realizes litespec/frontend/components#place-a-drawing-no-composite-makes-in-an-exception-file.
Checka reviewer answers
Does every visual file use semantic tokens only, and does any other file write class names?
Marked guidance: a reviewer answers this question.
Meet accessibility requirements#
rulecheckablelitespec/frontend/web/components#meet-accessibility-requirementsThe route suite MUST check keyboard reach, visible focus, target size, and status announcements on every route.
Why
The principle's guarantees hold only when every route proves them. This realizes litespec/frontend/components#support-keyboard-focus-labels-and-announcements.
Checkchecked by a tool
tests/routes/every-route.spec.ts presses Tab and asserts every focused element shows an indicator.
Marked checkable: the tool named here checks it.
Protect rich content boundaries#
ruleguidancelitespec/frontend/web/components#protect-rich-content-boundariesA component MUST NOT pass untrusted content to a raw HTML sink.
Why
The React and Svelte sections name the sink for each stack. Stored rich content requires a decision record naming the sanitizer, the allowed elements and attributes, the link policy, and the test cases. This realizes litespec/frontend/components#sanitize-untrusted-rich-content.
Checka reviewer answers
Does any component pass untrusted content to a raw HTML sink without its recorded decision?
Marked guidance: a reviewer answers this question.
Conventions#
Give card and section titles heading semantics#
conventioncheckablelitespec/frontend/web/components#give-card-and-section-titles-heading-semanticsRender a visible card, panel, or section title as a heading element at its correct level.
ReplacementA consumer replaces this default by recording an exception with its reason and review date.
Why
Some primitive sources default a title to a non-heading element, which removes it from the outline and the browser test that finds it. The route suite asserts each visible section title resolves as a heading. This realizes litespec/frontend/components#expose-card-and-section-titles-as-headings.
Checkchecked by a tool
tests/routes/every-route.spec.ts asserts each visible section title resolves as a heading.
Marked checkable: the tool named here checks it.
Compose class names in the shared package#
conventionguidancelitespec/frontend/web/components#compose-class-names-in-the-shared-packageCompose class names through the one merge helper the shared package exports, and keep every merge or variant helper import inside it.
ReplacementA consumer replaces this default by recording an exception with its reason and review date.
Why
A second merging helper produces a different conflict resolution for the same class pair. Page code has no reason to hold one.
Checka reviewer answers
Does every class merge go through the shared package helper?
Marked guidance: a reviewer answers this question.
Verification#
| ID | Mark | Evidence |
|---|---|---|
| litespec/frontend/web/components#keep-primitives-and-composites-in-one-shared-package | guidance | Does one shared package hold the registry source and the composites, with no copy in an application? |
| litespec/frontend/web/components#use-no-raw-element-in-page-code | guidance | Does page code declare a refused element, and does each refusal name its composite? |
| litespec/frontend/web/components#place-a-drawing-no-composite-makes-in-a-visual-file | guidance | Does every visual file use semantic tokens only, and does any other file write class names? |
| litespec/frontend/web/components#meet-accessibility-requirements | checkable | tests/routes/every-route.spec.ts presses Tab and asserts every focused element shows an indicator. |
| litespec/frontend/web/components#protect-rich-content-boundaries | guidance | Does any component pass untrusted content to a raw HTML sink without its recorded decision? |
| litespec/frontend/web/components#give-card-and-section-titles-heading-semantics | checkable | tests/routes/every-route.spec.ts asserts each visible section title resolves as a heading. |
| litespec/frontend/web/components#compose-class-names-in-the-shared-package | guidance | Does every class merge go through the shared package helper? |