UI Kernel: Design Tokens as Symfony infrastructure

작성자

카테고리:

← 피드로
DEV Community · Wolf · 2026-07-04 개발(SW)

For years, Symfony teams solved UI scaling one of two ways.

Either you adopted a JavaScript build pipeline — Webpack Encore, Vite, npm scripts — and a utility CSS framework like Tailwind.

Or you stayed “PHP-first” and watched every project grow its own app.css, its own spacing scale, its own dark-mode hacks, and its own Bootstrap overrides.

Both paths work. Neither scales cleanly across agencies, products, and long-lived admin UIs.

UI Kernel (symfinity/ui-kernel, current line 0.3.x) is Symfinity’s answer on the Symfony side: treat design tokens as infrastructure — resolved in PHP, emitted as CSS custom properties, consumed by Twig and component CSS — without making Node the gatekeeper of your theme.

This article explains the CSS story first, then where Symfony fits in — including the 0.3.x theme engine (runtime switching, overlays, and inheritance).

The problem tokens solve

Hard-coded hex values in templates do not scale.

When primary blue lives in forty places, rebranding is a grep exercise. When dark mode is bolted on with @media (prefers-color-scheme: dark) and ad hoc overrides, contrast breaks in corners nobody tested. When spacing is “whatever looked fine in Figma,” components drift apart.

Design tokens are the fix: named, semantic variables (--color-text, --space-md, --radius-lg) that components reference instead of raw values.

That idea is not new. What changed recently is how we author and validate those tokens in CSS — especially for colour.

Why OKLCH matters for palettes

Classic spaces — RGB, HSL — are easy to compute but bad at matching human perception.

A “50% lightness” yellow and a “50% lightness” blue do not look equally bright. Darkening with hsl() or Sass darken() shifts hue and brightness in unpredictable ways. Building a ten-step brand ramp that stays accessible was always guesswork.

OKLCH (lightness, chroma, hue in the Oklab model) is perceptually uniform: equal steps on the L axis feel like equal brightness changes across hues. That makes ramps, states (hover, active), and dark-mode pairs far more predictable.

In modern CSS you can write tokens directly:

:root {
  --text: oklch(0.30 0.03 260);
  --surface: oklch(0.97 0.01 260);
  --accent: oklch(0.60 0.14 255);
}

Enter fullscreen mode Exit fullscreen mode

Progressive enhancement still matters — ship sRGB fallbacks, layer OKLCH behind @supports:

:root {
  --text: #222326;
  --accent: #005fcc;
}

@supports (color: oklch(0.5 0.1 0)) {
  :root {
    --text: oklch(0.30 0.03 260);
    --accent: oklch(0.60 0.14 255);
  }
}

Enter fullscreen mode Exit fullscreen mode

For state colours, color-mix(in oklch, …) keeps blends on the perceptual axis instead of muddy RGB averages.

Contrast: WCAG today, APCA tomorrow

Accessibility checks still anchor on WCAG 2.x contrast ratios (4.5:1 body text, 3:1 large text/UI). OKLCH helps because L tracks perceived lightness — pairs with sufficient L separation tend to pass, which is why token-driven systems can enforce contrast at generation time instead of in every component file.

APCA (Advanced Perceptual Contrast Algorithm) is the direction of travel for WCAG 3. Good practice in 2026: design with APCA awareness, audit with WCAG for compliance paperwork. They usually agree; where they diverge, APCA often reflects real-world legibility better (thin type, dark UI, wide gamut).

Tools worth bookmarking

These are the references I reach for when building or reviewing token palettes — independent of Symfony:

Further reading (CSS and design systems)

These DEV Community articles cover the CSS side better than any Symfony doc should duplicate:

Symfony-adjacent (same author, typography chain):

From CSS files to Symfony infrastructure

Node-centric pipelines (Style Dictionary, Tailwind config, theme JSON → build step) fit design agencies. They hurt Symfony teams who want Flex install, YAML config, and AssetMapper without a second repository for tokens.

UI Kernel inverts the default:

  1. Tokens live as data — W3C DTCG-shaped theme layers (colour, spacing, radius, motion, typography references).
  2. PHP resolves and validates — lineage (Balanced, Semantic, Utility), light/dark variants, OKLCH ramp math, contrast-aware semantic colours.
  3. CSS is emitted server-side — custom properties on [data-theme="…"], wrapped in cascade layers (kernel.tokens, kernel.profile) for predictable stacking with ux-blocks-* tiers.
  4. Twig wires the page — boot script, layer order, and theme CSS in the layout; no Encore entry for “theme variables.”

Minimal layout (order matters — boot script first avoids a flash of the wrong theme):

{# templates/base.html.twig #}
<head>
    {{ ui_kernel_theme_boot_script() }}
    {{ ui_kernel_layer_order()|raw }}
    {{ ui_kernel_css()|raw }}
</head>

Enter fullscreen mode Exit fullscreen mode

Or use the bundle partial, which emits the same three calls:

{% include '@UiKernel/_head.html.twig' %}

Enter fullscreen mode Exit fullscreen mode

Configuration stays in familiar Symfony paths:

# config/packages/symfinity_ui_kernel.yaml
symfinity_ui_kernel:
    default_theme: semantic
    default_variant: semantic
    schema_version: '2.0'

Enter fullscreen mode Exit fullscreen mode

Install is Composer + Flex — same story as any other bundle:

composer require symfinity/ui-kernel

Enter fullscreen mode Exit fullscreen mode

What you do not need for theming: Tailwind as the token source of truth, a Sass pipeline for variables, or Encore/Vite solely to compile :root { --… }.

What you still use Symfony for: AssetMapper for component JS/CSS paths, Stimulus for micro-interactions, UX Twig Components for leaves — AssetMapper is plumbing, not your design system.

Runtime theme engine (0.3.x)

Static token emission was the 0.2.x story. 0.3.x adds a theme engine on top — still server-resolved, still no Node build step.

Runtime switching

Users pick light, dark, or auto without redeploying CSS. The boot script sets data-theme before first paint; preference cookies (symfinity_ui_kernel_lineage, symfinity_ui_kernel_scheme) persist the choice.

Scheme API

PATCH /_ui/theme/scheme returns resolved theme id, colorScheme, and refreshed CSS when the client needs server-side dark-mode resolution.

Theme shell

ui_kernel_theme_shell() emits layout chrome (lineage + light/dark/auto controls) wired to the same preference model.

Overlays

Config user_tokens merge atop the registry theme; app config/themes/{lineage}/ overrides bundle lineages; optional session preview on designated preview hosts (pairs with symfinity/ui-designer in dev).

Theme inheritance

Optional extends on a variant entry in theme.meta.yaml merges a parent theme layer before child overrides — multi-hop chains with cycle detection (0.3.1).

Capability probe

ui_kernel_theme_capabilities() reports which engine features are active (runtime_switch, user_token_overlay, nested_theme, and others).

Built-in lineages still ship as W3C DTCG files under config/themes/{lineage}/ (theme.meta.yaml + {variant}.dtcg.yaml), not a bespoke preset blob. Consumer apps drop overrides in the same layout; merged variants appear in ThemeRegistry.

Authoring UI for composition graphs, import/export, and the visual studio at /ui-designer lives in symfinity/ui-designer (dev-only, requires ui-kernel ^0.3.1) — the kernel stays the runtime contract.

How this stays scalable

Semantic tokens, not utility soup

Components and UX Blocks reference roles and semantic variables (data-ui-role, --ui-color-accent, spacing rhythm) — not p-4 text-gray-600 copied across Twig files.

The kernel owns the look spine; symfinity/ux-blocks-* packages own component CSS that consumes those variables. Boundary stays explicit: kernel emits tokens and global profile rules inside @layer kernel.*; blocks emit role selectors inside @layer blocks.*. Call ui_kernel_layer_order() before theme CSS so integrator CSS can override predictably.

One theme graph, many surfaces

The same resolved token graph can feed:

  • Web layouts — static emission plus 0.3.x runtime switching, overlays, and inheritance without forking app.css
  • Email HTML with inline-safe subsets (horizon)
  • PDF/print (horizon)
  • CLI/TUI structured output (horizon)

That is the payoff of server-resolved tokens versus “whatever landed in public/build/app.css last Tuesday.”

OKLCH inside, CSS outside

Internally, Symfinity generates palettes in OKLCH for perceptual ramps and ref resolution. Public CSS still ships browser-ready strings (sRGB hex/rgb(), with P3 where supported). You get palette math without asking every integrator to hand-author oklch() literals in YAML.

Works with what you already run

UI Kernel complements Symfony UX — it does not replace Live Components, Turbo, or Stimulus.

Typical Stage A path:

  • Install kernel for tokens on the layout shell.
  • Drop UX Blocks into existing Twig.
  • Add font-manager when typography tokens need real webfonts.

Plain Twig + your own CSS remains valid for marketing pages. The stack targets long-lived product and admin UI where theme drift hurts.

When Tailwind or Encore still make sense

Honest scope:

  • Tailwind — excellent for marketing microsites, rapid prototypes, or teams already standardized on utility CSS. Symfinity does not ask you to rip it out on day one.
  • Encore/Vite — still right when you need a heavy JS application bundle, legacy React/Vue islands, or org-wide frontend tooling unrelated to tokens.

UI Kernel draws a line: theming and design-system variables are Symfony infrastructure, not npm devDependencies.

If your pain is “every client has a different primary colour and our admin UI looks nothing like last year’s project,” tokens in PHP beat another tailwind.config.js fork.

Try it locally

After adding the symfinity/recipes Flex endpoint:

composer require symfinity/ui-kernel

Enter fullscreen mode Exit fullscreen mode

Handbook: ui-kernel docsquick start, themes (0.3.x), upgrade guide.

What’s next

UI Kernel roadmap: 0.3.x theme engine (current) → 0.4.x token engine (computed tokens, references, expressions) → 0.5.x generator engine. See ROADMAP.md.

Don’t miss the introduction series to Symfinity:

For package-level deep dives already published:

Articles on further Symfinity package tiers are planned — ui-designer (theme authoring studio) and UX Blocks deep dives are on the list.

Explore packages and source at github.com/symfinity.

Updated 2026-07-05 for ui-kernel 0.3.x (runtime switching, cascade layers, theme inheritance).

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다