Skip to main content

Design System & Guidelines

:::info Source Sourced from docs/08-frontend-design-guidelines.md in the documentation repo. :::

Companion: 09 Frontend Workflows · 10 Authoring Tool Spec · 11 Player Spec

1. Stack & App Topology

  • Web: Next.js (App Router), TypeScript strict, TailwindCSS, React Server Components where appropriate; PWA shell with custom service worker.
  • Mobile: Capacitor wrapping the same Next.js app for iOS/Android; native plugins for storage, camera, push, biometric.
  • Desktop: Electron wrapper for offline-heavy enterprise installations.
  • State: TanStack Query (server cache) + Zustand (client UI state). No Redux. Yjs for collaborative authoring.
  • Styling: Tailwind + CSS variables for tokens; CSS logical properties only (margin-inline-start not margin-left).
  • Forms: React Hook Form + Zod schemas (mirroring service DTOs).
  • Animation: Framer Motion (gated behind prefers-reduced-motion).
  • Charts: Visx for accessible, customizable charts; Recharts forbidden (a11y gaps).
  • Icons: Lucide; never raster icons.
  • i18n: ICU MessageFormat; runtime locale switching; per-locale dynamic imports.

2. Clean Architecture on the Frontend

/app # Next.js routes (presentation)
/components # presentational components (mostly client)
/components/server # server components
/hooks # application orchestration
/services # API clients + service-layer abstractions (ports)
/state # Zustand stores
/lib/domain # pure-TS domain primitives (Money, RRULE wrapper, ABAC eval, AI provenance)
/lib/adapters # IndexedDB (Dexie), Service Worker, AI client (cloud + local), Sync client
/types # shared TS types and DTO mirrors
/styles # Tailwind config, tokens, themes

Rules:

  • lib/domain has no React, no Next.js, no fetch — pure TS.
  • services/ defines ports; lib/adapters/ provides implementations bound at module init.
  • Components depend on hooks/ + services/; never call fetch directly.

3. Design Tokens

Tokens live in CSS variables under :root and .theme-* selectors, exported also as a Tailwind preset. Categories:

:root {
/* Color */
--color-bg: oklch(99% 0 0);
--color-surface: oklch(97% 0 0);
--color-text: oklch(20% 0 0);
--color-text-muted: oklch(40% 0.02 280);
--color-brand: oklch(62% 0.15 250);
--color-brand-fg: oklch(99% 0 0);
--color-success: oklch(60% 0.16 145);
--color-warning: oklch(72% 0.18 80);
--color-danger: oklch(58% 0.20 25);

/* Typography */
--font-sans: 'InterVariable', 'Noto Naskh Arabic', system-ui, sans-serif;
--font-mono: 'JetBrainsMono', monospace;
--text-base: clamp(0.95rem, 0.9rem + 0.2vw, 1.05rem);
--line-height-tight: 1.2;
--line-height-base: 1.55;

/* Space */
--space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem;
--space-6: 1.5rem; --space-8: 2rem; --space-12: 3rem;

/* Radii / shadows */
--radius-1: 4px; --radius-2: 8px; --radius-3: 12px;
--shadow-1: 0 1px 2px oklch(0% 0 0 / 0.04), 0 1px 4px oklch(0% 0 0 / 0.06);
--shadow-2: 0 4px 16px oklch(0% 0 0 / 0.08);

/* Motion */
--duration-fast: 120ms; --duration-base: 220ms; --duration-slow: 360ms;
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

.theme-dark { --color-bg: oklch(14% 0 0); --color-surface: oklch(18% 0 0); --color-text: oklch(96% 0 0); ... }
  • Tokens are direction-agnostic — never tied to LTR or LTR-only assumptions.
  • Component classes consume tokens; never raw hex inside components.

4. Component System

  • Primitives: Button, IconButton, Link, Input, Textarea, Select, Combobox, Checkbox, Radio, Switch, Slider, Toggle, Tabs, Accordion, Dialog, Drawer, Tooltip, Popover, Toast, Skeleton, Avatar, Badge, Tag, Chip, Card, Surface, Divider, Stack, Cluster, Grid.
  • Composite: DataTable (cursor-paginated + filter chips), CommandPalette, FilePicker, MediaPicker, Editor (Tiptap-based), QuizPreview, BlockCanvas, AssistantPanel.
  • Headless first: primitives wrap Radix UI for accessibility primitives where applicable; styling via Tailwind variants.
  • Compound components: Tabs.Root / Tabs.List / Tabs.Trigger / Tabs.Content style for shared state.

5. Direction (LTR + RTL)

  • <html dir> set by server based on locale.
  • All layout uses logical properties: padding-inline, margin-inline-start, inset-inline-end, text-align: start | end.
  • Icons that imply direction (chevrons, arrows) flipped via [dir='rtl'] .flip-on-rtl { transform: scaleX(-1); }.
  • All components have a Storybook RTL story; visual regression in both directions.

6. Accessibility (WCAG 2.2 AA)

  • Color contrast ≥ 4.5:1 for body, ≥ 3:1 for large text and UI.
  • Keyboard reachable everywhere; visible focus rings; tab order matches DOM.
  • Semantic HTML: <button>, <nav>, <main>, <section>, <dialog> (with polyfill).
  • ARIA used minimally — only when semantics aren't expressible natively.
  • Live regions for sync status, AI streaming messages, and validation errors.
  • Reduced motion: every animation gated on prefers-reduced-motion.
  • Captions/transcripts available on all media (auto-generated by AI then editable).
  • Forms: every input has a programmatic label; errors associated via aria-describedby.
  • Player: keyboard-controllable navigation; screen-reader announces lesson + block role; transcript drawer; closed captions toggle persists per user.
  • Skip-links on every page (Skip to main content).
  • Automated axe scan blocks PR; manual NVDA + VoiceOver pass per release.

7. Internationalization

  • ICU MessageFormat strings; pluralization + gender supported.
  • Date / number / currency formatting via Intl.
  • AI-generated locales accepted only after author review; provenance tag visible.
  • Fonts: variable Inter for Latin; Noto Naskh Arabic for ar-; Noto Sans Hebrew for he-; auto-fallback chain.

8. Offline UI Patterns

  • Sync Status Pill in app shell: states online, syncing, offline, sync_paused, conflicts_pending. Click to open Sync Center.
  • Sync Center drawer: list of pending mutations; conflict resolution UI; storage usage; pinned bundles.
  • Bundle Manager: library view of downloaded courses; storage size; pin / unpin; remove.
  • Offline Banner (non-blocking): "You're offline. Changes will sync when you're back online."
  • Optimistic UI: mutations apply locally immediately, mark with subtle pending indicator until applied.
  • Conflict UI (authoring): side-by-side diff + "Keep Yours / Keep Theirs / Merge" buttons.
  • Tamper warning: modal-blocking; explains the bundle is untrusted; offers re-download.

9. AI Affordances

  • AI Trigger is always an explicit user action — never automatic content insertion.
  • AI Provenance Badge on any AI-generated artifact; click reveals model, prompt, time, reviewer.
  • Streaming UX: chat messages stream token-by-token; tool calls render as inline cards ("Looked up lesson 'Photosynthesis'").
  • Cancel Anywhere: AI requests cancellable at all times with one click.
  • Refusal UX: safety refusals show a clear neutral message with rationale (no scolding); user can rephrase.
  • Cost Visibility: authoring + admin AI features show "AI usage: X / Y this month" in nav.
  • Local-vs-Cloud Indicator: when offline AI is used, badge shows "Local model"; cloud-refresh CTA appears when online.
  • Acceptance Pattern: AI suggestions are visually distinct (subtle dashed border) until accepted.

10. Page Architecture

  • Layouts: App shell (sidebar nav + topbar + sync status); minimal shell (auth, public verify); Player shell (immersive, distraction-minimized).
  • Loading: route-level Suspense + skeletons; never spinners on top of content.
  • Empty states: every list has an opinionated empty state with one clear next action.
  • Error states: problem+json codes mapped to user-friendly messages + recovery actions; never raw stack.
  • Notifications: toast for ephemeral; in-app inbox for persistent.

11. Performance Targets

MetricTarget
LCP (learner web)< 1s p75
INP< 200ms
CLS< 0.1
TBT< 200ms
First-load JS (learner)< 200 KB gzip
First-load JS (author)< 350 KB gzip
Bundle download (course median)< 30 MB compressed

Techniques: route-level code-split; dynamic import of heavy editors; image priority on hero; preload critical fonts only; avoid third-party scripts in the player; PWA pre-cache shell.

12. Testing

  • Component tests: Vitest + Testing Library; behavior over snapshot.
  • Integration: Playwright Component Tests for complex composites.
  • E2E: Playwright (per 09).
  • Visual regression: Chromatic / Playwright screenshots in LTR + RTL + dark.
  • Accessibility: axe-playwright; threshold zero new issues.
  • Offline: context.setOffline(true) Playwright flows; service worker tests.

13. Service Worker (PWA)

  • Strategy: app shell precache + runtime cache for catalog tiles + bundle adapter for PlayPackage Bundle storage (delegates to IndexedDB for binary chunks).
  • Versioning: cache name includes build hash; orphan caches purged on activate.
  • Update UX: "New version available" toast → reload.

14. State Management Conventions

  • Server data: TanStack Query — every query has a typed key; mutations integrate with sync outbox (a write goes to sync first, then optimistic update, then settled when push applied).
  • Client UI state: Zustand stores per surface (player store, author store, sync store).
  • No global Redux store. Local-first thinking applied to UI state too.

15. Why This Design

A consistent token system and component primitives let 19 backend services share one UX language. Direction-agnostic CSS makes RTL a non-event. Offline UI patterns are explicit so users always know whether their action will hit the network. AI affordances are designed so users always understand they're interacting with AI and can intervene — which is essential for trust in a learning product.