Skip to content

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

  1. Layer 0: Tokens
  2. Source: @bast/shared-styles tokens.
  3. Colors/spacing/typography must come from design tokens.

  4. Layer 1: Primitive wrappers

  5. Location: apps/admin-ui/src/components/ui/*
  6. Allowed to import @radix-ui/react-*.
  7. Own low-level behavior + primitive wiring.

  8. Layer 2: Shared semantic components

  9. Location: packages/shared-styles/src/components/*
  10. Examples: NeuCard, NeuButton, NeuInput, NeuSelect, InfraCard.
  11. Preferred API for product features.

  12. Layer 3: Feature screens

  13. Location: apps/admin-ui/src/components/{operational,setup,customization,cat-builder,...}
  14. Should consume Layer 2 first.
  15. Use Layer 1 only when Layer 2 has no equivalent.

Contract Rules

  • UI-001 (blocking): @radix-ui/react-* imports are allowed only in src/components/ui/*.
  • UI-002 (advisory, strict-ready): feature files importing src/components/ui/* are flagged for migration to shared semantic components.
  • UI-003 (blocking, existing): no native <select> in admin-ui (NeuSelect required).
  • 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=1 to fail on UI-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/components first.
  • Existing src/components/ui/* usage can remain during migration.
  • When creating a second usage of a local primitive pattern, promote it into shared-styles instead 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.