Entity model
The eight entity kinds that make up the uidex registry and how each is discovered.
uidex discovers most entities by convention. You only annotate to override a convention, to opt out, or to declare something that has no conventional location (a widget, acceptance criteria, a primitive description).
Module-scoped metadata is authored as a single named export:
export const uidex = { ... } as const satisfies Uidex.<Kind>DOM-scoped metadata (elements, regions, widget roots) stays on data-uidex*
attributes. Legacy JSDoc tags (@uidex page|feature|widget, @acceptance,
@uidex:not-flow) are no longer recognised — see the
migration section in uidex scan.
The eight kinds
| Kind | Source | How discovered |
|---|---|---|
element | Interactive DOM element | data-uidex="id" (always manual) |
region | Structural area | data-uidex-region="id" OR HTML5 landmark (<header>/<nav>/<main>/<aside>/<footer>/role="region") |
widget | Composite behavioural unit (video player, date picker, autocomplete) | data-uidex-widget="id" on the DOM root AND export const uidex = { widget: "id", ... } on the component module |
primitive | Reusable presentational component definition | Any file under src/components/ui/** (auto-promoted); data-uidex-primitive="name" opts in a non-conventional location |
page | Route-rendered top-level | Auto-derived from your framework's route detection (Next.js / Vite / TanStack Router); export const uidex = { page: "id", ... } overrides |
feature | Cross-cutting capability | Any directory under src/features/*; export const uidex = { feature: "id", ... } overrides |
flow | End-to-end user journey | Top-level test.describe in e2e/** (auto-promoted); { tag: "@uidex:flow" } adds richer metadata; opt out with export const uidex = { notFlow: true } |
route | URL → page mapping | Auto-derived from your framework's router |
Convention reference
| Entity | Convention | Default | Disable | Override |
|---|---|---|---|---|
route | Framework route detection | Next.js App/Pages, Vite + React Router, TanStack Router | n/a | (per-framework config) |
page | One per detected route | conventions.pages = "auto" | conventions.pages = false | export const uidex = { page: "id", ... } in the page module |
feature | Directory under the features glob | conventions.features = "src/features/*" | conventions.features = false | export const uidex = { feature: "id", ... } in a module inside the directory |
widget | (No convention — always explicit) | — | — | data-uidex-widget="<id>" on the DOM root AND export const uidex = { widget: "<id>", ... } |
region | HTML5 landmarks and role="region" | conventions.regions = "landmarks" | conventions.regions = false | data-uidex-region="<id>" |
element | (No convention — always annotated) | — | — | data-uidex="<id>" |
primitive | File under a primitives glob | conventions.primitives = ["src/ui/**", "src/components/ui/**"] | conventions.primitives = false | data-uidex-primitive="<name>", optionally with export const uidex = { primitive: ... } |
flow | Top-level test.describe in the flows glob (tag @uidex:flow adds richer metadata) | conventions.flows = ["e2e/**/*.spec.ts"] | conventions.flows = false | export const uidex = { notFlow: true } in the spec file to opt out |
Region id derivation
When the scanner auto-promotes a landmark to a region, the id is derived (first match wins):
- The element's
idattribute - Its
aria-label(kebab-cased) kebab(tagName + index-within-file)— e.g.header-1,nav-2
A data-uidex-region="my-id" on the same element replaces the derived id.
Primitive name derivation
Files under a primitives glob are registered with name = kebab(basename(file)):
src/ui/Button.tsx→buttonsrc/ui/TextField.tsx→text-field
data-uidex-primitive="my-button" on the component's root element replaces the
basename-derived name.
Override precedence
For any single entity, precedence is:
configuration override > export const uidex > DOM attribute > convention
- Setting
conventions.primitives = falsedisables filesystem discovery globally. Annotated primitives still register. - An
export const uidexvalue wins over any convention-derived id on the same module. page: false/feature: false/primitive: falsein anexport const uidexopts a convention-matching module out of registration.
Element annotation is intentional
The scanner does not auto-promote DOM elements. element registration
requires an explicit data-uidex attribute. uidex scan --audit may flag
interactive elements lacking an annotation.
Marker MD files are obsolete
UIDEX_PAGE.md and UIDEX_FEATURE.md are not read. uidex scan --audit
emits a migration warning when these files are present.