Frontends & Theming
Two separate renderers — Expo/React Native and React DOM — share one design token pipeline. DTCG tokens compiled by Terrazzo generate Tailwind v4 CSS for web and typed TypeScript themes for native. DaisyUI semantic vocabulary across both platforms. Per-server runtime branding at zero re-renders.
Token Pipeline
A single DTCG token source compiles through Terrazzo to produce platform-appropriate outputs. Web receives CSS variables; native receives typed TypeScript. Neither platform is a second-class citizen — both outputs derive from the same source JSON.
DaisyUI Semantic Vocabulary
DaisyUI semantic token names provide a shared naming contract across both platforms. The *-content pairing makes arbitrary user/server themes safe for contrast — any theme that passes validation is guaranteed to have readable foreground on every background.
| Token Group | Tokens | Purpose |
|---|---|---|
| Base surfaces | base-100 base-200 base-300 base-content |
Page backgrounds and primary text |
| Primary action | primary primary-content |
Main CTA, key interactive elements |
| Secondary | secondary secondary-content |
Supporting actions, secondary buttons |
| Accent | accent accent-content |
Highlights, badges, per-server brand color slot |
| Neutral | neutral neutral-content |
Borders, dividers, muted text surfaces |
| Semantic state | info success warning error (+ -content each) |
Status indicators, toasts, callouts |
| Radius | radius-field radius-box radius-selector |
Consistent border radius by component type |
*-content is the foreground-on-color pairing that makes arbitrary user/server themes safe for contrast. Server-supplied brand colors are validated for contrast server-side before being accepted into the theme system.
Web: Component Architecture
| Layer | Technology | Notes |
|---|---|---|
| Token source | @app/design-tokens (DTCG) |
Single source of truth for all design decisions |
| CSS output | Tailwind v4 @theme + [data-theme] CSS |
Per-theme variable scopes; no class-name conflicts |
| Component primitives | Base UI 1.0 | Headless, accessible; no styles of its own |
| Component library | Shadcn-style owned components | Copy-pasted, themed with DaisyUI vocabulary |
| Theme switching | document.documentElement.dataset.theme = name |
Instant, no React re-render |
Native: Component Architecture
| Layer | Technology | Notes |
|---|---|---|
| Token source | @app/design-tokens (themes.ts) |
Same DTCG source, typed TypeScript output |
| Styling | Unistyles 3 | Zero re-render runtime theme switching |
| Theme switching | UnistylesRuntime.setTheme(name) |
No React re-render triggered |
| Per-server branding | UnistylesRuntime.updateTheme('serverOverlay', ...) |
Scoped via ScopedTheme component |
| Component primitives | @rn-primitives / react-native-reusables | Accessible React Native behavior layer |
Runtime Theme Switching
Web
document.documentElement.dataset.theme = name. Per-server branding via <div data-theme={serverThemeId}> or inline CSS variable overrides on a wrapper. Instant, no re-render beyond style recalc.
Native
UnistylesRuntime.setTheme(name). Per-server branding via updateTheme mutation — no React re-render. Server brand colors arrive as semantic-token JSON; validated for contrast server-side before accepting.
Neither platform requires a React tree re-render for theme switching. Web uses CSS variable reassignment. Native uses Unistyles' native runtime. Per-server branding is scoped to a subtree without propagating to global state.
Monorepo Shape
packages/design-tokens/ tokens/*.json (DTCG) + terrazzo.config
→ dist/web/themes.css
→ dist/native/themes.ts
→ dist/contract.d.ts
packages/domain/ shared pure-TS business logic
apps/web/ Vite + TanStack Router + Tailwind v4
apps/native/ Expo + Unistyles 3
What Is NOT Shared
React DOM components, React Native components, platform-specific gestures, scroll behaviors, and keyboard handling are strictly not shared between platforms.
The shared boundary is explicit and narrow:
- Design tokens — DTCG source JSON, compiled outputs, contract types
- Semantic vocabulary types —
ButtonVariant,Size, DaisyUI token names - Domain logic — pure TypeScript in
packages/domain - AppRuntime commands — shared command/event definitions