Symfony UX 3.4 expands Toolkit with a Bootstrap 5.3 kit, reusable design-system-agnostic behaviors and richer documentation for kit authors. The release also adds dynamic Twig component names, safer on-demand icons, modern React and Vue bundler support and a stable Translator component.

Bootstrap 5.3 Kit

Contributed by in #3713

Bootstrap users previously had to turn the framework's markup into reusable Twig components themselves. The new Bootstrap kit provides 25 ready-to-use recipes based on Bootstrap 5.3, including accordions, alerts, cards, carousels, dropdowns, modals, navigation bars, pagination, toasts and tooltips.

Install each recipe on demand with the Toolkit command:

1
$ php bin/console ux:install alert --kit bootstrap

The installed Twig components use the familiar Bootstrap options and classes:

1
2
3
<twig:Alert color="success" heading="Profile updated" dismissible>
    Your account settings have been saved.
</twig:Alert>

The components are copied into your application, where you can use them as-is or customize their markup and behavior. Toolkit also includes the kit-level Bootstrap dependency when installing any of these recipes.

Design-System-Agnostic Toolkit Recipes

Contributed by in #3707 , #3725 and #3740

Common interface behaviors are often rebuilt in every application because they don't belong to a specific design system. The new common Toolkit kit provides five unstyled recipes that you can install and customize in your project:

  • post-link submits a link as a form, with optional method spoofing, CSRF protection and a confirmation prompt;
  • logout-link builds on post-link to log users out securely with a POST request;
  • closeable removes an element immediately, after a delay or automatically;
  • clipboard copies a value or an element's content and supports several kinds of success feedback;
  • tooltip provides accessible hover, focus, click and manual tooltips positioned with Floating UI.

Install recipes independently with the Toolkit command:

1
$ php bin/console ux:install clipboard --kit common

The installed controller can then be used directly in your templates:

1
2
3
4
5
6
7
<div data-controller="clipboard">
    <code data-clipboard-target="source">php bin/console cache:clear</code>
    <button type="button" data-action="clipboard#copy">
        <span data-clipboard-target="idle">Copy</span>
        <span data-clipboard-target="success" hidden>Copied!</span>
    </button>
</div>

Because the recipes ship without presentation styles, they work with any CSS framework or your own design system.

Self-Documenting Toolkit Recipes

Hugo Alliaume
Contributed by Hugo Alliaume in #3718

Before Symfony UX 3.4, a recipe lived in the Symfony UX repository while its documentation was a Twig template in the ux.symfony.com repository. Adding a recipe or reorganizing its examples split the work almost equally between the two repositories.

Recipes now ship a self-contained README.md next to their implementation. Toolkit renders it as HTML or portable Markdown, including installation instructions, live examples and generated API references. In practice, about 99% of recipe work can now happen in the Symfony UX repository; the generic rendering layer in ux.symfony.com accounts for the remaining 1%.

Stimulus Controller API References

Kevin Bond
Contributed by Kevin Bond in #3724 and #3733

Recipes that only ship a Stimulus controller had no generated API reference. Toolkit can now document the complete public API of a controller from @value, @target, @css-class, @outlet and @action tags in its docblock:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * @value  autoClose  Delay before the notification closes.
 * @target timerbar   Element that displays the remaining time.
 * @action close      Removes the notification.
 */
export default class extends Controller {
    static values = { autoClose: Number };
    static targets = ['timerbar'];

    close() {
        this.element.remove();
    }
}

Toolkit generates the matching attributes, types and descriptions in the API reference. Its linter also checks that the tags agree with the controller, so the code remains the source of truth.

Kit-Level Dependencies

Simon André
Contributed by Simon André in #3719

Dependencies shared by every recipe in a kit were declared in the kit manifest but omitted when installing an individual recipe. The installer now includes these Composer, npm and importmap dependencies automatically.

Kit authors can therefore declare a library once instead of repeating it in every recipe manifest:

1
2
3
4
5
6
7
8
9
10
{
    "name": "Bootstrap",
    "dependencies": {
        "npm": ["bootstrap@^5.3.0"],
        "importmap": [
            "bootstrap",
            "bootstrap/dist/css/bootstrap.min.css"
        ]
    }
}

Dynamic Twig Component Names

andreybotanic
Contributed by andreybotanic in #3395

Dynamic component selection previously required the component() function, which cannot define component blocks. The {% component %} tag now accepts a parenthesized expression, combining dynamic selection with props and blocks:

1
2
3
4
5
6
7
{% for tab in dashboard.tabs %}
    {% component (tab.componentName) with {loading: 'lazy'} %}
        {% block loadingContent %}
            <p>Loading {{ tab.label }}...</p>
        {% endblock %}
    {% endcomponent %}
{% endfor %}

Parentheses distinguish an expression from a literal component name. For example, {% component Alert %} still renders the component named Alert, while {% component (componentName) %} resolves the name from the current context.

Persistent On-Demand Icons

Hugo Alliaume
Contributed by Hugo Alliaume in #3741

The icon lock command cannot discover names that are built dynamically, which can leave production dependent on the Iconify API. Enable the new iconify.auto_lock option in development to save every on-demand icon the first time it is rendered:

1
2
3
4
5
# config/packages/ux_icons.yaml
when@dev:
    ux_icons:
        iconify:
            auto_lock: true

Browsing the application now writes those icons to assets/icons/. Commit the generated files so production can serve them locally without contacting Iconify.

Translator Is Now Stable

Hugo Alliaume
Contributed by Hugo Alliaume in #3716

Symfony UX Translator has been available for three years and its API has stabilized. It is no longer marked as experimental, so applications can use Symfony translations from JavaScript and TypeScript with the regular Symfony backward compatibility expectations.

React and Vue Support for Modern Bundlers

Hugo Alliaume
Contributed by Hugo Alliaume in #3714

React and Vue controller registration previously expected Webpack's require.context(). Both integrations now accept import.meta.glob(), which makes them work naturally with Vite and Rsbuild through Symfony Reprise:

1
2
3
4
5
6
7
8
9
10
import { registerReactControllerComponents } from '@symfony/ux-react';
import { registerVueControllerComponents } from '@symfony/ux-vue';

registerReactControllerComponents(
    import.meta.glob('./react/controllers/**/*.{jsx,tsx}', { eager: true })
);

registerVueControllerComponents(
    import.meta.glob('./vue/controllers/**/*.vue')
);

React components must be imported eagerly. Vue supports both eager imports and lazy loading. Existing Webpack Encore applications can continue using require.context() unchanged.

Full Changelog

  • #3740 [Toolkit][Common] Add clipboard and tooltip recipes (@kbond)
  • #3733 [Toolkit] Document Stimulus controller CSS classes and outlets in the API reference (@kbond)
  • #3741 [Icons] Add auto_lock to persist on-demand icons (@Kocal)
  • #3395 [TwigComponent] Support component tag names from expressions (@andreybotanic)
  • #3736 [Toolkit] Rework and normalize usage of attributes.defaults() in kit components (@seb-jean, @Kocal)
  • #3735 Add sync-packages.php to normalize package .gitattributes files (@Kocal)
  • #3730 Fix broken assets/tsconfig.json reference affecting Webpack 5.109 and later (@zalesak)
  • #3734 [Toolkit] Remove the height preview option (@Kocal)
  • #3724 [Toolkit] Document Stimulus controllers in the recipe API reference (@kbond)
  • #3725 [Toolkit][Common] Add the closeable recipe (@kbond)
  • #3716 [Translator] Make the component non-experimental (@Kocal)
  • #3713 [Toolkit] Add the Bootstrap Toolkit kit (@smnandre, @Kocal)
  • #3720 [Toolkit] Fix suggested frontend installation commands (@smnandre)
  • #3719 [Toolkit] Support kit-level dependencies (@smnandre)
  • #3721 [Toolkit] Require the correct twig/html-extra version for html_attr_* filters (@Kocal)
  • #3718 [Toolkit] Add a documentation rendering layer so kits can describe themselves (@Kocal)
  • #3714 [React][Vue] Add support for import.meta.glob() through Symfony Reprise (@Kocal)
  • #3707 [Toolkit][Common] Add the common kit with logout-link and post-link recipes (@kbond)
  • #3710 [Toolkit][Shadcn] Fix Table:Cell and Table:Head class attribute merging (@stephen-lewis)
  • #3709 [Toolkit] Add the ClassMergeSpacingChecker to the linter (@Kocal)
Published in #Releases #Symfony UX