03 — Design Tokens
Status: 🟢 Deels geïmplementeerd — Brand tokens, semantic tokens, OKLCH shades, dark mode, WCAG contrast, W3C/Tailwind export, token versioning zijn gebouwd. Element tokens (§6
elements) en uitgebreide guardrail UI (§4 visuele feedback) zijn nog vision.Afhankelijkheden: 01 — Brand Identity (sfeer-presets, wizard data) · 02 — Brand JSON Spec (schema,
brand_profilestabel).Optionele lagen: 05 — Expansion System en 06 — Guild Taxonomy voegen later extra cascade-lagen toe (Style Packs, Vertical Packs). Dit document werkt volledig zonder die lagen.
Het design token systeem is de technische backbone van Beam’s visuele identiteit. Alles wat er visueel gebeurt — de editor, blocks, addons, AI-gegenereerde content, exports — leest uit één bron: het token schema.
Inhoudsopgave
Section titled “Inhoudsopgave”- Theming Cascade
- Drie-Laags Token Architectuur
- Sfeer-preset ↔ Token Schema Alignment
- Guardrail Systeem
- Semantic Context Laag
- Volledig Token Schema
- Publieke Token API
- Storage Structuur
- Export Formaten
- Technische Beslissingen
- Backlog
1. Theming Cascade
Section titled “1. Theming Cascade”Tokens cascaderen van laag naar hoog. Hogere lagen overschrijven lagere.
v1 — Kern (01/02/03)
Section titled “v1 — Kern (01/02/03)”Laag 3 │ User Overrides (styling builder: handmatige aanpassingen) │ Altijd de hoogste prioriteit — de gebruiker wint │Laag 2 │ Sfeer-preset │ "Warm & Aards", "Bold & Expressief", "Zakelijk & Betrouwbaar", etc. │ = het startpunt dat de styling builder aanbiedt │ Zie: 01 §7 Sfeer-presets │Laag 1 │ Beam Core Tokens │ Platform defaults — de basis die altijd geldt │────────┤ │Tokens │ W3C Design Token Schema (single source of truth) │ Alles hierboven resolved naar dit schemaCascade-regel: Laag 1 is read-only vanuit de styling builder. De builder bewerkt Laag 2 (sfeer-preset keuze) en Laag 3 (user overrides).
Toekomst — Met guilds en addons (05/06)
Section titled “Toekomst — Met guilds en addons (05/06)”Wanneer het guild- en addon-systeem gelanceerd worden, schuiven er twee lagen tussen:
Laag 5 │ User Overrides (zelfde als v1 Laag 3)Laag 4 │ Vertical Pack Styling (niche-specifieke overrides)Laag 3 │ Style Packs (guild-gebaseerd, vervangt sfeer-presets)Laag 2 │ Addon Global Styles (CSS variabelen, fonts van addons)Laag 1 │ Beam Core TokensDit is backward compatible: bestaande v1-sites behouden hun sfeer-preset als Laag 2, die later 1:1 mapt naar een guild Style Pack.
2. Drie-Laags Token Architectuur
Section titled “2. Drie-Laags Token Architectuur”Brand Tokens → Semantic Tokens → Element Tokens(ruwe waarden) (betekenis/rol) (toepassing)
#1B4D7A → primary color → button backgroundPlayfair Display → heading font → h1, h2, h36px → base radius → card cornerTokens werken altijd via refs — nooit hardcoded waarden. Wijzig je een brand color → alles downstream past zich automatisch aan via OKLCH shade recalculatie.
3. Sfeer-preset ↔ Token Schema Alignment
Section titled “3. Sfeer-preset ↔ Token Schema Alignment”De styling builder is het bewerkoppervlak, het token schema is de opslag, en de sfeer-preset is het startpunt. Dezelfde data, drie representaties.
Sfeer-presets: zie 01 — Brand Identity §7.
mood_presets tabel: zie 02 — Brand JSON Spec §5.
Sfeer-preset (mood_presets tabel) Token Schema (styling builder)──────────────────────────────── ────────────────────────────────colors.primary → tokens.colors.brand.primarycolors.secondary → tokens.colors.brand.secondarycolors.accent → tokens.colors.brand.accentfont_combination → tokens.typography.fonts.heading + bodyradius → tokens.shape.radius.presetspacing → tokens.shape.spacing.presetshadows → tokens.shape.shadow.presetFlow: Wizard kiest sfeer-preset → preset tokens worden geladen als startpunt → gebruiker past aan in de styling builder → resultaat opgeslagen als brand_profiles.design_tokens.
Toekomst: Style Packs (05/06)
Section titled “Toekomst: Style Packs (05/06)”Wanneer het guild-systeem gelanceerd wordt, worden sfeer-presets uitgebreid tot Style Packs met extra velden:
Style Pack (toekomst) Extra velden t.o.v. sfeer-preset───────────────────── ──────────────────────────────────componentStyles → elements.* (button, table, etc.)seasonal.overrides → meta.seasonal_overridesgilde → meta.source_gilde4. Guardrail Systeem
Section titled “4. Guardrail Systeem”“You cannot make it ugly” is geen aspiratie — het is een technische implementatie. Guardrails zijn altijd actief, niet optioneel.
Kleur Guardrails
Section titled “Kleur Guardrails”| Regel | Implementatie | Feedback |
|---|---|---|
| Max 3 brand colors | UI blokkeert 4e kleur | ”Drie kleuren zijn genoeg voor een sterk merk.” |
| WCAG AA contrast | Real-time check bij elke kleurwijziging | Contrast ratio badge: ✅ AA / ⚠️ Bijna / ❌ Onvoldoende |
| Harmonie check | Kleuren buiten OKLCH-gegenereerde schaal worden gewaarschuwd | ”Deze kleur botst met je palette. Probeer deze suggestie.” |
| Dark mode contrast | Automatisch gevalideerd bij generatie | Idem |
Typografie Guardrails
Section titled “Typografie Guardrails”| Regel | Implementatie | Feedback |
|---|---|---|
| Curated font-paren | Alleen vooraf goedgekeurde combinaties heading+body | Font picker toont compatibele matches |
| Geen extreme sizes | Min/max per element type (H1: 1.5rem–4rem) | Slider stopt bij grenzen |
| Line-height validatie | Automatisch berekend op basis van font size | Niet handmatig instelbaar tenzij “geavanceerd” |
| Mobile sizes | Automatisch afgeleid uit desktop (ratio) | Preview toont mobiel altijd mee |
Vorm Guardrails
Section titled “Vorm Guardrails”| Regel | Implementatie | Feedback |
|---|---|---|
| Consistente radius | Eén radius-preset voor alles (None/Small/Medium/Large/Pill) | Geen losse radius per element |
| Spacing systeem | Base unit × vermenigvuldigers, geen vrije waarden (Compact/Comfortable/Spacious) | Spacing slider met stappen |
| Shadow coherentie | Vier niveaus (None/Subtle/Medium/Prominent), niet vrij instelbaar | Shadow preset selector |
Guardrail Feedback — Nintendo Juice
Section titled “Guardrail Feedback — Nintendo Juice”Guardrails voelen niet als restrictie maar als hulp:
| Situatie | Visueel | Audio |
|---|---|---|
| Kleur voldoet aan WCAG AA | Subtiel groen vinkje | Zacht “ding” |
| Kleur faalt contrast | Kleur schudt licht + suggestie slide-in | Zacht “bonk” |
| Font-paar is harmonieus | Fonts schuiven soepel in positie | — |
| Radius verandert | Alle elementen animeren smooth mee (spring physics) | Subtiel “click” per stap |
| Kleurwijziging propageert | Ripple-animatie door alle gerelateerde tokens in sidebar | Toon (pitch volgt hue) |
5. Semantic Context Laag
Section titled “5. Semantic Context Laag”Tokens zijn contextbewust. Een primary kleur op een donkere achtergrond gebruikt automatisch een lichtere variant:
"primary": { "default": { "ref": "brand.primary" }, "on-dark": { "ref": "tokens.colors.generated.primary-300" }, "on-light": { "ref": "tokens.colors.generated.primary-700" }}Beam lost dit automatisch op op basis van de achtergrondkleur van de container — onzichtbaar voor de gebruiker, ingebakken in het schema.
6. Volledig Token Schema
Section titled “6. Volledig Token Schema”{ "brand": { "name": "Bouwbedrijf De Vries", "logo": { "url": "https://r2.beam.app/sites/abc123/brand/logo.svg", "favicon": null } },
"tokens": { "colors": { "brand": { "primary": "#1B4D7A", "secondary": "#F5F0EB", "accent": "#D4883E" }, "semantic": { "primary": { "default": { "ref": "brand.primary" }, "on-dark": { "ref": "tokens.colors.generated.primary-300" }, "on-light": { "ref": "tokens.colors.generated.primary-700" }, "dark-mode-override": null }, "background": { "value": "#FFFFFF", "dark-mode-override": "#0F172A" }, "surface": { "value": "#F8F9FA", "dark-mode-override": "#1E293B" }, "text": { "value": "#1A1A2E", "dark-mode-override": "#F1F5F9" }, "text-muted": { "value": "#6B7280", "dark-mode-override": "#94A3B8" }, "border": { "value": "#DEE2E6", "dark-mode-override": "#334155" }, "danger": { "value": "#C92A2A" }, "success": { "value": "#2F9E44" }, "warning": { "value": "#E67700" } }, "generated": { "primary-50": "auto (OKLCH)", "primary-100": "auto (OKLCH)", "primary-200": "auto (OKLCH)", "primary-300": "auto (OKLCH)", "primary-400": "auto (OKLCH)", "primary-500": "auto (OKLCH)", "primary-600": "auto (OKLCH)", "primary-700": "auto (OKLCH)", "primary-800": "auto (OKLCH)", "primary-900": "auto (OKLCH)" } },
"typography": { "fonts": { "heading": { "family": "Roboto Slab", "source": "self-hosted" }, "body": { "family": "Roboto", "source": "self-hosted" }, "mono": { "family": "JetBrains Mono", "source": "self-hosted" } }, "scale": { "h1": { "font": "heading", "size": { "desktop": "3rem", "mobile": "2.25rem" }, "weight": 700, "lineHeight": 1.2 }, "h2": { "font": "heading", "size": { "desktop": "2.25rem", "mobile": "1.875rem" }, "weight": 600, "lineHeight": 1.3 }, "h3": { "font": "heading", "size": { "desktop": "1.875rem", "mobile": "1.5rem" }, "weight": 600, "lineHeight": 1.3 }, "h4": { "font": "heading", "size": { "desktop": "1.5rem", "mobile": "1.25rem" }, "weight": 500, "lineHeight": 1.4 }, "h5": { "font": "heading", "size": { "desktop": "1.25rem", "mobile": "1.125rem" }, "weight": 500, "lineHeight": 1.4 }, "h6": { "font": "heading", "size": { "desktop": "1rem", "mobile": "1rem" }, "weight": 500, "lineHeight": 1.4 }, "body": { "font": "body", "size": { "desktop": "1rem", "mobile": "1rem" }, "weight": 400, "lineHeight": 1.6 }, "label": { "font": "body", "size": { "desktop": "0.875rem", "mobile": "0.875rem" }, "weight": 500, "lineHeight": 1.4 }, "caption": { "font": "body", "size": { "desktop": "0.75rem", "mobile": "0.75rem" }, "weight": 400, "lineHeight": 1.4 } } },
"shape": { "radius": { "preset": "none", "values": { "none": "0px", "small": "4px", "medium": "8px", "large": "16px", "pill": "9999px" } }, "spacing": { "preset": "compact", "base": "4px", "values": { "compact": "4px", "comfortable": "6px", "spacious": "8px" } }, "shadow": { "preset": "subtle", "values": { "none": "none", "subtle": "0 1px 2px rgba(0,0,0,0.05)", "medium": "0 4px 6px rgba(0,0,0,0.07)", "prominent": "0 10px 15px rgba(0,0,0,0.1)" } } } },
"elements": { "button": { "primary": { "bg": { "ref": "semantic.primary.default" }, "text": { "value": "#FFFFFF" }, "border": { "value": "transparent" }, "radius": { "ref": "shape.radius.preset" }, "paddingX": "1.25rem", "paddingY": "0.625rem", "font": { "ref": "typography.scale.label" }, "states": { "hover": { "bg": { "ref": "tokens.colors.generated.primary-700" } }, "focus": { "ring": { "ref": "semantic.primary.default" }, "ringOffset": "2px" }, "disabled": { "opacity": 0.5 }, "loading": { "opacity": 0.8 } } }, "secondary": { "bg": { "ref": "semantic.surface" }, "text": { "ref": "semantic.primary.default" }, "border": { "ref": "semantic.border" }, "radius": { "ref": "shape.radius.preset" }, "states": { "hover": { "bg": { "ref": "tokens.colors.generated.primary-50" } } } }, "ghost": { "bg": { "value": "transparent" }, "text": { "ref": "semantic.primary.default" } }, "destructive": { "bg": { "ref": "semantic.danger" }, "text": { "value": "#FFFFFF" } } }, "table": { "headerBg": { "ref": "semantic.surface" }, "borderColor": { "ref": "semantic.border" }, "stripeColor": { "ref": "tokens.colors.generated.primary-50" }, "cellPadding": "0.75rem 1rem" }, "list": { "markerColor": { "ref": "semantic.primary.default" }, "itemSpacing": "0.375rem", "indent": "1.5rem" }, "input": { "border": { "ref": "semantic.border" }, "radius": { "ref": "shape.radius.preset" }, "focusRing": { "ref": "semantic.primary.default" }, "bg": { "value": "#FFFFFF" } }, "badge": { "radius": { "ref": "shape.radius.pill" }, "font": { "ref": "typography.scale.caption" } }, "codexCard (toekomst, 05)": { "bg": { "ref": "semantic.surface" }, "border": { "ref": "semantic.border" }, "radius": { "ref": "shape.radius.lg" }, "titleFont": { "ref": "typography.scale.label" }, "rarityGlow": { "ref": "semantic.primary.default" } } },
"meta": { "version": "1.0", "onboarding_completed_at": "2026-03-20T12:00:00Z", "last_published_at": null, "mood_preset": "solide-strak", "font_combination": "system-solid", "token_format": "w3c-design-tokens-draft", "guardrails": { "contrast_aa_pass": true, "color_harmony_pass": true, "font_pair_validated": true, "last_checked_at": "2026-03-20T12:00:00Z" } }}7. Publieke Token API
Section titled “7. Publieke Token API”Elk Beam-merk heeft een read-only token endpoint. De pagebuilder, AI content generator, addon blocks en externe tools lezen hier live uit.
Volledige API specificatie: zie 02 — Brand JSON Spec §3.
| Endpoint | Beschrijving |
|---|---|
GET /api/sites/{site_id}/tokens | Volledig token schema in W3C formaat |
GET /api/sites/{site_id}/tokens/css | CSS variables, kant-en-klaar voor embedden in <head> |
Tokens zijn de bron — CSS, Tailwind en W3C JSON zijn afgeleiden.
8. Storage Structuur
Section titled “8. Storage Structuur”CF R2: beam-media/ sites/ {site_id}/ media/ ← afbeeldingen (onboarding + media library) fonts/ ← eigen WOFF2 uploads brand/ ← logo, faviconNoot over fonts: Curated self-hosted fonts (21 families, OFL-licensed): 19 variable fonts (single woff2, alle weights) + 3 static fallbacks (Poppins, Lato, Proza Libre). De API genereert @font-face CSS via generateFontFaceCss() uit @beam/shared — geen runtime externe font API calls. <link rel="preload"> elimineert FOUT. Eigen WOFF2 uploads worden opgeslagen in R2 (sites/{site_id}/fonts/).
9. Export Formaten
Section titled “9. Export Formaten”Alle exports zijn afgeleiden van het token schema.
1. CSS Variables
:root { --color-primary: #1B4D7A; --color-primary-50: /* OKLCH gegenereerd */; --font-heading: 'Roboto Slab', serif; --font-body: 'Roboto', sans-serif; --radius-base: 0px; --spacing-base: 4px;}2. Tailwind v4 (@theme)
@theme { --color-primary: #1B4D7A; --font-heading: 'Roboto Slab'; --font-body: 'Roboto'; --radius-base: 0px;}3. W3C Design Tokens JSON Primair formaat. Compatibel met Figma Tokens, Style Dictionary, Canva (via Figma bridge).
Toekomst (05/06): Wanneer het addon-systeem gelanceerd wordt, komt er een vierde export: het Style Pack formaat als distributieformaat voor addons.
10. Technische Beslissingen
Section titled “10. Technische Beslissingen”| Onderwerp | Beslissing |
|---|---|
| Color shade algoritme | OKLCH via culori library |
| Dark mode | Auto-generatie met handmatige override per token |
| Font bronnen | Self-hosted woff2 in public/fonts/brand/ (19 variable + 3 static, OFL) + eigen WOFF2 upload → R2 (zie 01 §4) |
| Tailwind export | v4 CSS-first via @theme |
| Token formaat | W3C Design Token spec als primair formaat |
| Publiceren | Draft staat met aparte publiceer stap |
| Versioning | Auto-save + named snapshots (zie 02 §5 token_versions) |
| Responsive typografie | Vaste sizes per breakpoint (desktop + mobiel apart, zie 01 §6) |
| Component states | Hover + focus handmatig, disabled + loading automatisch afgeleid |
| Scope | Per site — geen multi-site (agency feature: backlog B9) |
| Theming cascade | v1: 3 lagen (Core → Sfeer-preset → User). Toekomst: 5 lagen met guilds/addons (05/06) |
| Guardrails | Altijd actief, niet optioneel — kern-feature (zie 01 §8) |
| Radius presets | 5 vaste opties: None (0), Small (4), Medium (8), Large (16), Pill (9999) |
| Spacing presets | 3 opties: Compact (4px), Comfortable (6px), Spacious (8px) |
| Shadow presets | 4 opties: None, Subtle, Medium, Prominent |
11. Backlog
Section titled “11. Backlog”Hoog (logische volgende stap na MVP)
Section titled “Hoog (logische volgende stap na MVP)”| # | Feature | Omschrijving |
|---|---|---|
| B1 | Brand health score | Live indicator hoe consistent tokens worden toegepast. Hardcoded waarden buiten token systeem verlagen de score. |
| B2 | Token import | W3C JSON importeren vanuit Figma Tokens, Style Dictionary of andere Beam site |
| B3 | ”Gebruikt in” impact preview | Toon waar een token gebruikt wordt vóór je het wijzigt. “Deze kleur zit in 3 pagina’s, 2 buttons, 1 tabel.” |
Medium
Section titled “Medium”| # | Feature | Omschrijving |
|---|---|---|
| B4 | Brand lock / permissions | Agency vergrendelt primaire kleur + logo, klant past de rest aan. |
| B5 | Changelog / audit trail | Leesbare geschiedenis: “Angelo heeft op 14 feb de primary color gewijzigd.” |
| B6 | AI stijlgeneratie vanuit URL | Voer een inspiratie URL in → Beam extraheert stijl als token startpunt. |
| B7 | Animaties & transitions | Globale instelling: transition snelheid + easing preset. |
| B8 | Fluid typography | clamp() per breakpoint als alternatief voor vaste sizes. |
Later / Visie
Section titled “Later / Visie”| # | Feature | Omschrijving |
|---|---|---|
| B9 | Agency / multi-site templates | Gedeelde token templates over meerdere sites. |
| B10 | Real-time samenwerking | Meerdere gebruikers tegelijk in de styling builder. |
| B11 | Tokens overdraagbaar naar andere platforms | E-mail templates, Canva social media, pitch decks. |
| B12 | AI als merkbewaker | AI toetst elke nieuwe pagina aan de merkidentiteit. |
| B13 | Semantische betekenislaag | Tokens beschrijven niet alleen hoe iets eruitziet maar wat het betekent. “Blauw = vertrouwen.” |
Referenties
Section titled “Referenties”| Document | Relatie |
|---|---|
| 01 — Brand Identity | Sfeer-presets (§7), font-combinaties (§4), guardrails (§8), styling builder layout (§6) |
| 02 — Brand JSON Spec | Brand JSON schema (§1–2), API endpoints (§3), brand_profiles + token_versions tabellen (§5) |
| 05 — Expansion System | Toekomst: addon tokens, vertical packs |
| 06 — Guild Taxonomy | Toekomst: Style Packs, guild-gebaseerde presets |