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
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
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-linksubmits a link as a form, with optional method spoofing, CSRF protection and a confirmation prompt;logout-linkbuilds onpost-linkto log users out securely with aPOSTrequest;closeableremoves an element immediately, after a delay or automatically;clipboardcopies a value or an element's content and supports several kinds of success feedback;tooltipprovides 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
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
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
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
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
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
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
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
clipboardandtooltiprecipes (@kbond) - #3733 [Toolkit] Document Stimulus controller CSS classes and outlets in the API reference (@kbond)
- #3741 [Icons] Add
auto_lockto 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.phpto normalize package.gitattributesfiles (@Kocal) - #3730 Fix broken
assets/tsconfig.jsonreference affecting Webpack 5.109 and later (@zalesak) - #3734 [Toolkit] Remove the
heightpreview option (@Kocal) - #3724 [Toolkit] Document Stimulus controllers in the recipe API reference (@kbond)
- #3725 [Toolkit][Common] Add the
closeablerecipe (@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-extraversion forhtml_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
commonkit withlogout-linkandpost-linkrecipes (@kbond) - #3710 [Toolkit][Shadcn] Fix
Table:CellandTable:Headclass attribute merging (@stephen-lewis) - #3709 [Toolkit] Add the
ClassMergeSpacingCheckerto the linter (@Kocal)