Brand Identity & Design Token Systeem
Overzicht
Section titled “Overzicht”Het Brand Identity systeem in Beam stelt gebruikers in staat de visuele identiteit van hun site te definiëren via een Styling Builder op /brand. Het systeem genereert automatisch een compleet design token pakket (CSS custom properties, semantic tokens, shade scales, dark mode) op basis van de keuzes van de gebruiker.
Architectuur:
┌──────────────────────────────────────────────────────────┐│ Dashboard (/brand) ││ ┌─────────┬──────────┬───────────┬──────────┬──────────┐ ││ │ Presets │ Kleuren │Typografie │Elementen │ Export │ ││ └────┬────┴────┬─────┴─────┬────┴────┬─────┴────┬─────┘ ││ │ │ │ │ │ ││ └─────────┴───────────┴─────────┘ │ ││ │ │ ││ DesignTokens │ ││ │ │ ││ PUT /brand/:siteId │ ││ │ │ │├─────────────────────┼────────────────────────────┼────────┤│ API (Hono Worker) │ │ ││ ▼ │ ││ brand_profiles │ ││ (design_tokens JSONB) │ ││ │ │ ││ ┌──────┴──────┐ │ ││ ▼ ▼ │ ││ auto-snapshot GET /tokens/css ◄──────┘ ││ (trigger) GET /tokens (W3C) ││ │ GET /tokens/tailwind ││ ▼ ││ token_versions │└──────────────────────────────────────────────────────────┘ │ ▼┌──────────────────────────────────────────────────────────┐│ Public Site (Astro SSR) ││ <link href="/brand/{siteId}/tokens/css" rel="stylesheet">││ → Alle brand tokens beschikbaar als CSS custom properties│└──────────────────────────────────────────────────────────┘Design Tokens Structuur
Section titled “Design Tokens Structuur”Alle design tokens worden opgeslagen als één design_tokens JSONB veld in brand_profiles. De TypeScript interface:
interface DesignTokens { colors: BrandColors // 7 kleuren fontCombination: FontCombinationId // 1 van 12 font-paren typography: TypographyTokens // Fijnafstelling radius: RadiusPreset // Hoekafronding shadow: ShadowPreset // Schaduw gradients?: BrandGradient[] // Optionele gradients}BrandColors (7 velden)
Section titled “BrandColors (7 velden)”| Veld | Doel | CSS Variabele |
|---|---|---|
primary | Hoofdkleur — knoppen, links, accenten | --brand-primary |
secondary | Secundaire kleur — headers, hover states | --brand-secondary |
accent | Accentkleur — highlights, badges | --brand-accent |
neutral | Neutrale kleur — borders, muted tekst, achtergronden | --brand-neutral |
surface | Achtergrondkleur — pagina achtergrond, kaarten | --brand-surface |
textDark | Donkere tekstkleur | --brand-text-dark |
textLight | Lichte tekstkleur | --brand-text-light |
Elke kleur is een hex waarde (#RRGGBB). Validatie via Zod: z.string().regex(/^#[0-9a-fA-F]{6}$/).
TypographyTokens
Section titled “TypographyTokens”| Veld | Bereik | Standaard | Doel |
|---|---|---|---|
headingWeight | 100–900 | 700 | Font weight voor headings |
headingLetterSpacing | -0.05em tot 0.05em | -0.02em | Letter-spacing headings |
headingLineHeight | 0.8–1.5 | 1.2 | Line-height headings |
bodyWeight | 300–700 | 400 | Font weight voor body tekst |
bodyLineHeight | 1.2–2.0 | 1.75 | Line-height body tekst |
scale | compact/default/dramatic | default | Type scale voor heading sizes |
Type Scales
Section titled “Type Scales”| Scale | h1 mobile/desktop | h2 | h3 | h4 |
|---|---|---|---|---|
| compact | 1.75rem/2.25rem | 1.5rem/1.875rem | 1.25rem/1.5rem | 1.125rem/1.25rem |
| default | 2rem/2.5rem | 1.75rem/2rem | 1.5rem/1.75rem | 1.25rem/1.5rem |
| dramatic | 2.5rem/3.5rem | 2rem/2.5rem | 1.5rem/1.875rem | 1.25rem/1.5rem |
Gegenereerde Tokens
Section titled “Gegenereerde Tokens”OKLCH Shade Scale
Section titled “OKLCH Shade Scale”Per brand kleur (primary, secondary, accent) worden 10 shades gegenereerd via de OKLCH kleurruimte:
--brand-primary-50 (lightest)--brand-primary-100--brand-primary-200--brand-primary-300--brand-primary-400--brand-primary-500 (≈ oorspronkelijke kleur)--brand-primary-600--brand-primary-700--brand-primary-800--brand-primary-900 (darkest)Functie: generateShadeScale(hex) in packages/shared/src/color-utils.ts.
Semantic Tokens
Section titled “Semantic Tokens”Automatisch afgeleid van de brand kleuren:
| CSS Variabele | Bron | Doel |
|---|---|---|
--brand-surface | colors.surface | Pagina achtergrond |
--brand-surface-alt | primary-50 shade | Alternatieve achtergrond |
--brand-border | neutral-300 of fallback | Standaard borders |
--brand-border-light | neutral-200 of fallback | Subtiele borders |
--brand-text-primary | colors.textDark | Primaire tekst |
--brand-text-muted | neutral-500 of fallback | Grijze/gedempte tekst |
--brand-on-primary | Contrast berekening | Tekst op primary achtergrond |
--brand-on-secondary | Contrast berekening | Tekst op secondary achtergrond |
--brand-on-accent | Contrast berekening | Tekst op accent achtergrond |
--brand-danger | Vast: #DC2626 | Foutmeldingen |
--brand-success | Vast: #059669 | Succesmeldingen |
--brand-warning | Vast: #D97706 | Waarschuwingen |
--brand-info | Vast: #2563EB | Informatie |
Functie: generateSemanticTokens(colors) in packages/shared/src/color-utils.ts.
Dark Mode Tokens
Section titled “Dark Mode Tokens”Automatisch berekend via OKLCH:
@media (prefers-color-scheme: dark) { :root { --brand-surface: /* donkere variant */ --brand-surface-alt: /* donkere variant */ --brand-border: /* donkere variant */ --brand-border-light: /* donkere variant */ --brand-text-primary: /* lichte variant */ --brand-text-muted: /* gedempte lichte variant */ }}Functie: generateDarkModeTokens(colors) in packages/shared/src/color-utils.ts.
Contrast Variabelen
Section titled “Contrast Variabelen”Per brand kleur wordt een contrast-kleur berekend voor tekst op gekleurde achtergronden:
--brand-primary-contrast: #111827 of #ffffff--brand-secondary-contrast: #111827 of #ffffff--brand-accent-contrast: #111827 of #ffffffDashboard UI
Section titled “Dashboard UI”Tabs (WAI-ARIA)
Section titled “Tabs (WAI-ARIA)”De Styling Builder heeft 5 tabs met volledig WAI-ARIA tabs pattern:
| Tab | Component | Props |
|---|---|---|
| Presets | PresetPicker | selectedPreset, onSelectPreset |
| Kleuren | ColorsTab | colors, onColorsChange |
| Typografie | TypographyTab | selectedFont, typography, onFontChange, onTypographyChange |
| Elementen | ElementsTab | radius, shadow, onRadiusChange, onShadowChange |
| Export | ExportTab | siteId |
Keyboard navigatie: ArrowLeft/ArrowRight navigeert tussen tabs, Home/End gaat naar eerste/laatste tab.
Kleuren Tab
Section titled “Kleuren Tab”- Preset paletten: 6 sfeer-presets als klikbare kaarten met 5-kleur preview
- Vrije kleurkiezer: 7
ColorInputcomponenten met:- Native
<input type="color">picker - Hex text input met live validatie
- Inline WCAG contrast badge per kleur (ratio + pass/fail)
- Native
- WCAG contrast checking: Client-side via
checkWcagContrast()— geen API call nodig. Badge toont groen (AA/AAA) of oranje (fail) met ratio. Bij fail wordt een suggestie voor een accessible alternatief berekend viasuggestAccessibleColor() - Kleurvarianten preview: OKLCH shade scale (50-900) per brand kleur als interactieve strip. Hover toont shade nummer + hex. Click kopieert hex naar clipboard. CSS var naam beschikbaar in tooltip
Typografie Tab
Section titled “Typografie Tab”- Font-paren: 12 combinaties als klikbare kaarten met heading + body font preview
- Fijnafstelling: 5 slider controls (heading weight, letter-spacing, line-height, body weight, body line-height)
- Type scale: 3 opties (compact/standaard/dramatisch) als button group
Export Tab
Section titled “Export Tab”3 export formaten:
| Formaat | Endpoint | Content-Type |
|---|---|---|
| CSS Custom Properties | GET /brand/:siteId/tokens/css | text/css |
| W3C Design Tokens JSON | GET /brand/:siteId/tokens | application/json |
| Tailwind v4 @theme | GET /brand/:siteId/tokens/tailwind | text/css |
Elke kaart heeft een “Kopieer” (clipboard) en “Open” (nieuw tabblad) knop.
Versiegeschiedenis
Section titled “Versiegeschiedenis”Modal (VersionHistoryModal) met:
- Snapshot opslaan: Label input + opslaan knop. Max 100 karakters
- Versie-lijst: Chronologisch met relatieve timestamps, auto/handmatig label
- Herstellen: Per versie een restore knop die de tokens terugzet en de pagina ververst
Automatische snapshots worden aangemaakt door een PostgreSQL trigger bij elke update op brand_profiles. Max 50 auto-snapshots per site (FIFO cleanup).
API Endpoints
Section titled “API Endpoints”Authenticated (Bearer token)
Section titled “Authenticated (Bearer token)”| Method | Path | Beschrijving |
|---|---|---|
| GET | /brand/:siteId | Brand profile ophalen (auto-create als niet bestaat) |
| PUT | /brand/:siteId | Design tokens updaten |
| GET | /brand/:siteId/versions | Versiegeschiedenis ophalen (limit 50) |
| POST | /brand/:siteId/versions | Named snapshot aanmaken |
| POST | /brand/:siteId/versions/:id/restore | Snapshot herstellen (met audit log) |
Public (geen auth, edge-cached)
Section titled “Public (geen auth, edge-cached)”| Method | Path | Beschrijving |
|---|---|---|
| GET | /brand/:siteId/tokens/css | CSS custom properties stylesheet |
| GET | /brand/:siteId/tokens | W3C Design Tokens JSON |
| GET | /brand/:siteId/tokens/tailwind | Tailwind v4 @theme CSS |
| GET | /brand/:siteId/contrast | WCAG contrast rapport |
Caching: CSS endpoint gebruikt CF edge cache (caches.default) met max-age=60, s-maxage=300. Cache wordt automatisch gepurged bij brand profile updates via purgeCache().
Database
Section titled “Database”brand_profiles
Section titled “brand_profiles”| Kolom | Type | Beschrijving |
|---|---|---|
id | UUID | PK |
site_id | UUID | FK → sites, unique |
mood_preset | text | Geselecteerde sfeer-preset |
design_tokens | JSONB | Volledige DesignTokens object |
onboarding_data | JSONB | Onboarding metadata |
created_at | timestamptz | |
updated_at | timestamptz |
token_versions
Section titled “token_versions”| Kolom | Type | Beschrijving |
|---|---|---|
id | UUID | PK |
site_id | UUID | FK → sites |
design_tokens | JSONB | Snapshot van tokens |
label | text | Optioneel label (handmatige snapshots) |
auto | boolean | true = auto-snapshot door trigger |
created_by | UUID | Gebruiker die snapshot maakte |
created_at | timestamptz |
RLS: Team members (via get_team_owner_id()) hebben volledige toegang. INSERT enforceert created_by = auth.uid() of NULL.
Realtime Sync
Section titled “Realtime Sync”Brand tokens worden real-time gesynchroniseerd via twee mechanismen:
- BroadcastChannel — Cross-tab sync binnen dezelfde browser sessie
- Supabase Realtime — Multi-user sync bij gelijktijdig bewerken
Hook: useBrandRealtimeSync() in apps/dashboard/src/lib/hooks/use-brand-realtime.ts. Past CSS variabelen direct toe op document.documentElement via applyBrandCssVars().
Public Site Integratie
Section titled “Public Site Integratie”De publieke site (Astro SSR) laadt brand tokens als stylesheet:
<link rel="stylesheet" href="https://api.builtwithbeam.com/brand/{siteId}/tokens/css" />Dit levert een compleet CSS bestand met:
- Self-hosted
@font-faceregels (variable fonts + static fallbacks, geen externe CDN) :rootCSS custom properties (alle brand + semantic + shade tokens)- Typography regels (heading/body font-family, weight, line-height, letter-spacing)
- Heading sizes per type scale (mobile-first + desktop breakpoint)
@media (prefers-color-scheme: dark)overrides
Shared Package Functies
Section titled “Shared Package Functies”Alle design token logica zit in packages/shared/src/:
| Functie | Bestand | Beschrijving |
|---|---|---|
getDefaultDesignTokens() | mood-presets.ts | Standaard tokens (eerste preset) |
getMoodPreset(id) | mood-presets.ts | Preset ophalen op ID |
generateShadeScale(hex) | color-utils.ts | OKLCH 50-900 shade scale |
generateSemanticTokens(colors) | color-utils.ts | Afgeleide semantic tokens |
generateDarkModeTokens(colors) | color-utils.ts | Dark mode overrides |
checkWcagContrast(fg, bg) | color-utils.ts | WCAG AA/AAA check + ratio |
suggestAccessibleColor(fg, bg) | color-utils.ts | Accessible alternatief zoeken |
getContrastRatio(fg, bg) | color-utils.ts | Raw contrast ratio |
isLightColor(hex) | color-utils.ts | Licht/donker detectie |
generateFontFaceCss(family) | font-manifest.ts | Self-hosted @font-face CSS genereren (variable of static) |
getFontPreloadPaths(family) | font-manifest.ts | Font preload paden ophalen |
getFontCombination(id) | mood-presets.ts | Font-paar ophalen |
Backwards Compatibiliteit
Section titled “Backwards Compatibiliteit”Bestaande database records kunnen de neutral en surface velden missen (toegevoegd in maart 2026). De mergeTokensWithDefaults() helper in BrandPage.tsx vult ontbrekende velden aan met defaults:
function mergeTokensWithDefaults(dbTokens: Partial<DesignTokens>): DesignTokens { const defaults = getDefaultDesignTokens() return { colors: { ...defaults.colors, ...dbTokens.colors, neutral: dbTokens.colors?.neutral ?? defaults.colors.neutral, surface: dbTokens.colors?.surface ?? defaults.colors.surface, }, // ... overige velden met defaults }}De API Zod schema’s markeren neutral en surface als optioneel om backwards compatibiliteit te garanderen.
Security
Section titled “Security”- Zod validatie op alle write-inputs (kleuren, typography, gradients)
- Defense-in-depth: DB-data wordt opnieuw gevalideerd met Zod vóór CSS generatie
- Font selectie via enum: Geen vrije font-invoer, alleen 12 voorgedefinieerde combinaties
safeFontName(): Regex strip van\'"(){};/<>karakters voor CSS embedding- Gradient whitelist:
SAFE_GRADIENT_REaccepteert alleenlinear-gradient/radial-gradientmet hex kleuren - RLS: Alle queries via user-scoped Supabase client, admin client alleen voor publieke read-only endpoints
- Rate limiting: 60 req/min per IP op publieke endpoints
- Edge cache purge: Automatisch bij brand profile updates
Zie Security Standards voor de volledige checklist.