Repository navigation
Conversation
Parse and validate DTCG 2025.10 token documents (Format and Color modules), resolve aliases, $ref JSON Pointers, $extends and type inheritance, and assemble themes from Resolver documents: sets, modifiers, contexts and resolutionOrder. TokenRegistry exposes the resolved tokens by path, for any set of Resolver inputs.
Render the resolved tokens as CSS custom properties, inline with ux_token_css() or as an AssetMapper stylesheet with ux_token_stylesheet(). The color_scheme option maps a Resolver modifier to prefers-color-scheme and [data-theme]; the dark block holds only the variables that change. Resolutions are cached in the system cache and warmed with the application. Add lint:design-tokens, debug:design-tokens and ux:design-tokens:export (DTCG, CSS, JavaScript).
ux:design-tokens:import converts a Tailwind CSS v4 @theme block to DTCG. Values DTCG cannot hold are logged as warnings, entries it has no type for are listed with -v. ux:design-tokens:export gains the "tailwind" and "design.md" formats; design.md needs symfony/yaml. Any ImporterInterface service joins the import command through autoconfiguration.
Add the README, the CHANGELOG and a single documentation page: writing tokens, configuration, CSS output with light and dark, reading tokens in Twig and PHP, Resolver themes, export and import, and the lint and debug commands. DocumentationExamplesTest checks that each documented example parses.
| /* assets/styles/app.css */ | ||
| .button { | ||
| background: var(--dt-color-action-primary); | ||
| color: var(--dt-color-content-default); | ||
| border-radius: var(--dt-dimension-radius-control); | ||
| padding: var(--dt-dimension-spacing-md); | ||
| } |
There was a problem hiding this comment.
Many questions here:
- Where does this file comes from?
- Is it automatically generated from somewhere, or is something you write manually?
- Is VSCode or PHPStorm able to autocomplete
--dt-variables? If no, I'm afraid the DX is bad :/
At section Reading a token, you show <h1 style="color: {{ ux_token('color.action.primary') }}">Welcome</h1>, where you read a token with ux_token() and directly inject it in style:
- is it the good way to go, to keep styles around its HTML ?
- how do you deal with
hover:or::before? - do you have auto-completion / validation on the value passed to
ux_token()?
There was a problem hiding this comment.
I really think you must insist on answering these questions on the documentation, with a lot of examples 🙏🏻
EDIT: From a purely pragmatic standpoint, I think #3933 will solve this issue. For now, the PR reads its tokens from its ux_css.yaml, but I'm sure it will be able to read the tokens from DesignTokens instead :)
| ``ux_token_css()`` writes the CSS into every response. When the stylesheet is | ||
| the same for every visitor, ``ux_token_stylesheet()`` links it instead, so the | ||
| browser caches it. It needs AssetMapper: |
There was a problem hiding this comment.
Do we really needs to depends on AssetMapper to include a CSS file on a page? 😕
In #3933, a .css file is automatically generated, meaning that you can easily load it through AssetMapper, or load it through a Bundler.
| public function renderStylesheet(): string | ||
| { | ||
| if (null === $this->assetMapper) { | ||
| throw new LogicException('Serving design tokens as a stylesheet requires the AssetMapper component. Try running "composer require symfony/asset-mapper", or use ux_token_css() to inline the CSS instead.'); | ||
| } | ||
|
|
||
| $this->stylesheets?->path(); | ||
|
|
||
| $logicalPath = UXDesignTokensBundle::ASSET_NAMESPACE.'/'.StylesheetCache::STYLESHEET; | ||
| $publicPath = $this->assetMapper->getPublicPath($logicalPath); | ||
|
|
||
| if (null === $publicPath) { | ||
| throw new RuntimeException(\sprintf('No design token stylesheet was found at "%s".', $logicalPath)); | ||
| } | ||
|
|
||
| return \sprintf('<link rel="stylesheet" href="%s">', htmlspecialchars($publicPath, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8')); | ||
| } |
There was a problem hiding this comment.
Maybe I misunderstand something here, but how users using bundlers are supposed to include the .css file (with the tokens) on their page?
UX CSS now reads every design token from symfony/ux-design-tokens (symfony#3932): DTCG token files, resolved when the container is built, with every Resolver context included. A token's name in css() comes from its path, with a fixed prefix per category, for example color.* for colors and dimension.spacing.* for spacing. Generated rules use UX Design Tokens' own CSS variables, like var(--dt-color-primary), so values, dark mode and per-request themes come from there. _dark reuses the same selectors as its stylesheet. The tokens, semantic_tokens, default_tokens and breakpoints options of ux_css are removed; breakpoints now come from breakpoint.* tokens. CI stays red until symfony#3932 is merged, since the package isn't in the repository yet.
| Q | A | -------------- | --- | Bug fix? | no | New feature? | yes | Deprecations? | no | Documentation? | yes | Issues | | License | MIT This PR adds a new package, `symfony/ux-css`. It gives Twig a `css()` function in the spirit of Panda CSS (https://panda-css.com/): you pass a hash of style properties and get back atomic class names built from design tokens. The hash is checked when Twig compiles the template, so a misspelled property or an unknown token shows up as a Twig syntax error pointing to the template line, not as a silent CSS bug. PHP writes one CSS file (`var/ux_css/styles.css`). There is no Node.js involved. AssetMapper serves the file directly, and Webpack Encore or Symfony Reprise can import it like any other stylesheet. Class names and generated CSS match what Panda CSS produces (a pinned version) for the supported syntax, checked against test cases recorded from Panda's own test suite. ```html+twig <div class="{{ css({ p: 'md', color: 'fg', _hover: { color: 'primary' }, md: { p: 'lg' } }) }}"> ``` This renders `class="p_md c_fg hover:c_primary md:p_lg"`. This is complementary to symfony#3932 (Simon's "[DesignTokens] Add the Design Tokens component"), not competing with it. symfony#3932 reads DTCG token files and exposes their values as CSS variables and PHP values. This PR is a styling API that consumes tokens. A later step could let `ux-css` read its tokens from `ux-design-tokens` instead of its own config. I open this as a draft so both approaches can be compared side by side. Most of the diff, about 64,000 of the 114,000 lines, is test fixtures recorded from Panda CSS: reviewers can skip `src/Css/tests/Fixtures/Panda/`. The package suite passes (5445 tests, 224 skipped because they cover Panda syntax we don't support), and PHP-CS-Fixer, Twig-CS-Fixer and DOCtor-RST all pass too.
UX CSS now reads every design token from symfony/ux-design-tokens (symfony#3932): DTCG token files, resolved when the container is built, with every Resolver context included. A token's name in css() comes from its path, with a fixed prefix per category, for example color.* for colors and dimension.spacing.* for spacing. Generated rules use UX Design Tokens' own CSS variables, like var(--dt-color-primary), so values, dark mode and per-request themes come from there. _dark reuses the same selectors as its stylesheet. The tokens, semantic_tokens, default_tokens and breakpoints options of ux_css are removed; breakpoints now come from breakpoint.* tokens. CI stays red until symfony#3932 is merged, since the package isn't in the repository yet.
Symfony UX Design Tokens reads design tokens written in the DTCG 2025.10 format (W3C Design Tokens Community Group) and hands their resolved values to CSS, Twig and PHP.
{{ ux_token_css() }} <h1 style="color: {{ ux_token('color.action.primary') }}">Welcome</h1>What it provides:
$refJSON Pointers,$extends, sets and modifiers@propertyrules; light and dark written from a Resolver modifier, the dark block holding only what changesux_token(),ux_token_css()andux_token_stylesheet()(a versioned AssetMapper file)TokenRegistryInterface: typed token values in PHP, with Resolver inputs per calllint:design-tokens(with--fix),debug:design-tokens --sources,ux:design-tokens:export(CSS, JavaScript, DTCG, Tailwind CSS v4, DESIGN.md) andux:design-tokens:import(Tailwind CSS v4)I tried here to keep the minimum of things for ease review and first iterations. As I see it, the next steps could be:
Website and demo in the making, not sure they'll be ready before next week