UI Architecture Contract (Admin UI)¶
Decision¶
Use both:
- Radix for interaction/accessibility primitives (focus, keyboard, layering, ARIA behavior).
- Tailwind + design tokens for layout and visual styling.
Do not choose Radix-only or Tailwind-only.
Layering Model¶
- Layer 0: Tokens
- Source:
@bast/shared-stylestokens. -
Colors/spacing/typography must come from design tokens.
-
Layer 1: Primitive wrappers
- Location:
apps/admin-ui/src/components/ui/* - Allowed to import
@radix-ui/react-*. -
Own low-level behavior + primitive wiring.
-
Layer 2: Shared semantic components
- Location:
packages/shared-styles/src/components/* - Examples:
NeuCard,NeuButton,NeuInput,NeuSelect,InfraCard. -
Preferred API for product features.
-
Layer 3: Feature screens
- Location:
apps/admin-ui/src/components/{operational,setup,customization,cat-builder,...} - Should consume Layer 2 first.
- Use Layer 1 only when Layer 2 has no equivalent.
Contract Rules¶
UI-001(blocking):@radix-ui/react-*imports are allowed only insrc/components/ui/*.UI-002(advisory, strict-ready): feature files importingsrc/components/ui/*are flagged for migration to shared semantic components.UI-003(blocking, existing): no native<select>in admin-ui (NeuSelectrequired).UI-004(blocking, existing): no raw Tailwind palette/shadow shortcuts that bypass Bast tokens.
Lint/Guard Enforcement¶
pnpm run test:ui-contract- Enforces
UI-001(error). - Reports
UI-002(warning by default). - Set
STRICT_UI_CONTRACT=1to fail onUI-002. pnpm run test:no-native-select- Enforces
UI-003. pnpm run test:bast-ux-guard- Enforces
UI-004. pnpm run test:bast-ux- Runs all guard checks together.
Migration Policy¶
- New feature UI should use
@bast/shared-styles/componentsfirst. - Existing
src/components/ui/*usage can remain during migration. - When creating a second usage of a local primitive pattern, promote it into
shared-stylesinstead of duplicating.
Practical Defaults¶
- Need popovers/dialog/select behavior: use Layer 1 wrapper (
src/components/ui/*) or promote a semantic wrapper to Layer 2. - Need screen-level controls and cards: use Layer 2 semantic components.
- Need one-off visual tweaks: use Tailwind classes with tokenized values.