Skip to content

[Css] Add the CSS component - #3933

Draft
Kocal wants to merge 2 commits into
symfony:3.xfrom
Kocal:feat/ux-css
Draft

Kocal wants to merge 2 commits into
symfony:3.xfrom
Kocal:feat/ux-css

Conversation

@Kocal

@Kocal Kocal commented Sep 27, 2026 •

Copy link
Copy Markdown
Member
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.

<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 PR complements #3932 (Simon's "[DesignTokens] Add the Design Tokens component") and does not compete with it. DesignTokens is where tokens are defined, CSS is a styling layer that uses them.

I wrote the first commit on September 24 and 25, before #3932 was opened, with its own token config in Panda's format. The second commit replaces that: CSS now reads every token from DesignTokens' DTCG files, and the generated rules use DesignTokens' --dt-* CSS variables. That means this PR now depends on #3932, and CI stays red until it's merged. I'm keeping this as a draft so both approaches can be compared side by side.

DesignTokens (#3932) CSS (this PR)
Role Reads design tokens and exposes their values Styles templates with tokens
Tokens written in DTCG 2025.10 JSON files, plus a Resolver document for themes Nothing of its own: it reads the DTCG files from DesignTokens
Output CSS variables (--dt-*), typed token objects in PHP, exports to CSS, JavaScript, DTCG, Tailwind and DESIGN.md Atomic classes in one CSS file; rules read DesignTokens' --dt-* variables, no token variable declared here
Use in a template var(--dt-...) in your own CSS, and ux_token() where CSS variables cannot go (emails, PDF, SVG) class="{{ css({ ... }) }}"
Hover, breakpoints, dark mode Written in your own CSS; light and dark variables generated from a Resolver modifier Written in the hash (_hover, md, _dark, custom conditions): _dark reuses DesignTokens' dark selectors, breakpoints come from breakpoint.* tokens
Errors lint:design-tokens; an unknown token path fails the render At compile time, for each css() call, with suggestions
Theme per request Yes, with Resolver inputs Yes, through DesignTokens: its variables change per request, the classes from css() stay the same

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/.

Note

css({}) auto-completion requires a modification on the Symfony Plugin for PHPStorm (EDIT: Haehnchen/idea-php-symfony2-plugin#2889), and Symfony Language Tools for VSCode (EDIT: symfony/language-tools#108)

Current limitations

For the moment, it's not possible to generate a uber array-shaped PHPDoc type, because of PHPStan and PHPStorm limitations, given Opus 5.5.

It means it's not possible to have support for (| represents the cursor) :

  • key with { base: '', ...states} as value, for examples: { color: { |, color: { base : '|, and { _hover: { |
  • have auto-completion for values with brackets, ex: [blue.500/20], which equals NB colors variants * 101 (100 because opacity can go from 0 to 100), which means making the array-shaped PHPDoc type much biger

@Kocal

Kocal commented Sep 27, 2026 •

Copy link
Copy Markdown
Member Author

Some updates:

Kocal added 2 commits October 1, 2026 08:29
| 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant