enabel/ux
Enabel UX is a Symfony bundle offering reusable Twig components and utilities for building consistent, maintainable user interfaces. Install it with Composer to streamline common UI patterns across Symfony applications and reduce repetitive template code. Documentation included.
This bundle is Symfony/Twig-first (Twig layouts, importmap() via AssetMapper), so treat it as a drop-in UI layer rather than something you wire up like a Laravel Blade package — the mental model is closest to a Laravel app with Vite, where one JS entrypoint owns all CSS/JS imports.
composer require enabel/ux and register the bundle; the interesting surface area is templates/ (layouts, form themes, components) and the asset contract in assets/.@enabel/enabel-bootstrap-theme plus bootstrap via your package manager, then php bin/console importmap:require bootstrap @enabel/enabel-bootstrap-theme.assets/app.js the single source of truth. As of v2.6.0 this is mandatory, not optional:import 'bootstrap';
import '@enabel/enabel-bootstrap-theme/dist/css/enabel-bootstrap-theme.min.css';
importmap:update / asset-map:compile, then open a page that extends @EnabelUx/layout/bootstrap/base.html.twig.First use case: stop hand-rolling auth screens. Extend the provided Bootstrap layout for login / reset-password / 2FA pages so they inherit the theme and form styling without copying markup. These are exactly the pages that broke silently when the CDN references were removed, because they were the ones never overriding the stylesheets / javascripts blocks.
One entrypoint, many layouts. base.html.twig renders only importmap('app') in its javascripts block and leaves stylesheets empty. Keep it that way: put Bootstrap, the Enabel theme, Font Awesome and flag-icons in app.js, and let every page (including unauthenticated ones) inherit them. Only reach for block overrides when a page genuinely needs a different entrypoint:
{% block javascripts %}
{{ parent() }}
{{ importmap('chart-page') }}
{% endblock %}
Override by copying, not by editing vendor. Templates live under a bundle path, so the standard Symfony override applies — copy into templates/bundles/EnabelUx/... and keep your diff against the upstream file small so future releases are cheap to reconcile. Since both stylesheets and javascripts blocks survived v2.6.0, existing overrides keep working; that's the safe extension point.
Form themes over raw markup. The bundle's value is consistency across forms — use its form theme and component partials so buttons, validation states and input groups match the layout without per-template class juggling.
Inline scripts go through the module graph. Anything that used the CDN's implicit global must become an explicit import, either in app.js or in a <script type="module"> block:
import { Toast, Collapse } from 'bootstrap';
This is the pattern to standardise on now, because the global is gone and will not come back.
Theme 4 migration as a deliberate step, not a side effect. If your templates use fa-* or fi fi-* classes, add those packages to your importmap and app.js before bumping the theme, since v4 drops Font Awesome and flag-icons. Theme 4 self-hosts fonts, so moving to it is also the cleanest way to close out the Google Fonts / visitor-IP issue the release notes call out.
app.js imports the theme CSS. Check every layout that renders outside your main authenticated shell.importmap:update ignores version constraints and will happily jump you to theme 4. If you're not ready, pin ^3.0.2 deliberately, add the Font Awesome / flag-icons imports first, then update.window.bootstrap no longer exists. Grep the codebase and any CMS/email/template content for window.bootstrap and bootstrap. globals; silent undefined errors on dropdowns and toasts are the symptom.stylesheets block is intentional and still defined. Don't "fix" it by re-adding CDN links; that reintroduces the double-load and the third-party font request.debug:asset-mapper (and checking the compiled importmap.php) tells you whether the theme CSS is actually in the graph; a page that looks unstyled but has a correct importmap('app') tag usually means the import lives in a different entrypoint than the one being rendered.asset-map:compile and clear the template cache after any override or bundle upgrade, otherwise stale Twig caches mask both the removal of the CDN tags and your new imports.How can I help you explore Laravel packages today?