SsuperslateDocs

Web UI patterns

Use the shipped React components, state boundaries, accessibility behavior, and tests.

Web UI patterns

The web application uses CSS Modules and semantic tokens rather than a utility framework. Reuse the existing primitives when they fit; add product-specific composition in the owning module instead of turning components/ui into a second product domain.

Ownership

PathResponsibility
src/components/uireusable interaction and presentation primitives
src/components/layoutapplication shell, navigation, responsive layout, and error surfaces
src/modules/<domain>route pages and product-specific composition
src/servicestyped Hono calls and TanStack Query lifecycle
src/storedeliberate client-only Zustand state
src/styles/_variables.csssemantic color, spacing, typography, elevation, and motion tokens
src/configs/rpc-client.tscredentialed transport and stable application errors

TanStack Query owns remote data. Do not copy API results into Zustand. Components do not call raw fetch; service modules own query keys, mutations, invalidation, and error conversion.

Included primitives

The current inventory includes buttons, inputs, forms, checkboxes, switches, selects, selectors, tabs, menus, breadcrumbs, tooltips, modals, drawers, bottom sheets, cards, stat cards, tags, date and date-range pickers, tables with pagination, filters, search, infinite-scroll triggers, skeletons, spinners, empty states, toasts, avatars, identity cells, file icons, file cells, date cells, area charts, uploads, async images, keyboard hints, truncated text, the command palette, and a floating action button. The users module is the reference composition for a paginated data table (Table + createTableStore + useTableQueryParams against a typed list endpoint); the files module is the reference for table cell primitives (FileCell / FileIcon / DateCell) over an owner-scoped list; the dashboard module is the reference for stat cards and charts. Zag-backed popovers are composed inside the date controls and notification bell rather than exported as a standalone primitive.

FileIcon resolves its glyph and category tint through resolveFileType, which maps a MIME type onto a fixed category set and falls back to the filename extension. Extend that map when a new upload type ships; do not branch on MIME strings inside a page. Its category tints are the one place in the primitive library that sets colour outside the token palette, and each has a dark-mode value.

Zag.js-backed controls must retain their keyboard, focus, and ARIA behavior. A similar visual built from generic div elements is not an equivalent replacement. Keep semantic labels and error associations when composing form controls.

Responsive and application states

Every material screen should deliberately handle:

  • initial loading and mutation-pending states;
  • empty data and disabled-provider states;
  • recoverable and terminal errors;
  • unauthenticated and unauthorized behavior;
  • keyboard navigation, visible focus, and screen-reader labels;
  • narrow viewport layout and touch targets;
  • reduced motion and sufficient contrast;
  • long names, translated-like expansion, and unbroken user content.

The settings shell has distinct desktop and mobile layouts. Tables include mobile card rendering; test both representations when columns or filtering change.

Localization

The buyer SPA ships English, Spanish, and German through i18next and react-i18next. Locale resources live in apps/web/src/i18n/locales, and the language switcher is available on both authentication and authenticated application surfaces, with the same preference also exposed as a language field in /settings/profile. The selected locale is stored in app.locale; otherwise the browser language is used, with English as the fallback. The runtime updates the document lang attribute when the locale changes.

Use useTranslation from @/i18n in React components and the exported i18n instance in service callbacks that cannot use hooks. Use interpolation and plural keys for variable copy, and pass the resolved locale into Intl or date-fns formatting. Do not assemble translated sentences from fragments when word order can vary by language.

Add every new key to every locale file. The i18n test enforces key parity so a missing buyer-facing translation fails the web test suite. API-provided notification bodies and provider error messages remain server-owned content and are displayed verbatim.

The service worker fallback at apps/web/public/offline.html cannot import the React locale bundle, so it carries the minimal offline copy for each supported locale and reads the same app.locale preference. Update that static copy when adding a locale.

Testing

Vitest runs in a Node environment. Existing component contract tests use react-dom/server to prove important static zero-key and disabled-provider states. They do not prove focus, pointer, portal, measurement, animation, or browser navigation behavior.

Use the smallest useful layer:

vp run --filter @app/web test
pnpm test:e2e
vp run --filter @app/web build

Add a browser test when behavior depends on the DOM or user interaction. Keep static rendering tests for deterministic copy and availability contracts, and preserve the production bundle budget.

Branding

Change identity and tokens through Customize branding and design tokens. Do not introduce Tailwind into the buyer SPA, embed the product name into every component, or manually edit generated PWA assets.

On this page