kevinpapst/tabler-bundle
Symfony bundle integrating the Tabler.io admin template, with Twig helpers, vertical and horizontal layouts, security pages, event-driven menus, a context helper, and translations—speeding up backend development on Bootstrap 5.
The bundle is a Symfony theme package, not an app generator: it gives you Tabler's layouts, Twig components and navbar widgets, and expects you to feed them from your own services. Install is four steps and no more:
composer require kevinpapst/tabler-bundle
cp vendor/kevinpapst/tabler-bundle/config/packages/tabler.yaml config/packages/
// config/bundles.php
return [
// ...
KevinPapst\TablerBundle\TablerBundle::class => ['all' => true],
];
Then read vendor/kevinpapst/tabler-bundle/docs/index.md in order — it is short and split by topic (layout, menu, notifications, tasks, ThemeContext, one file per component). Do that before writing any Twig. Also clone TablerBundle-Demo: it is the fastest way to see a working dashboard, security screens and every component in context, and it doubles as a smoke test for how your asset pipeline should look.
Where to look first:
config/packages/tabler.yaml — short; mostly route names for the user dropdown (login/logout/profile), asset/cache-busting flags, and icon defaults. Dump the full reference with bin/console config:dump-reference tabler.bin/console debug:twig --filter=tabler — lists the bundle's functions/filters.vendor/kevinpapst/tabler-bundle/templates/ — they are plain Twig and are the real documentation for block names and embed arguments.bin/console debug:event-dispatcher — shows the Tabler menu/tasks/notifications events, which is where most of your integration work happens.First use case (30 minutes): a read-only dashboard behind an existing firewall.
{# templates/dashboard/index.html.twig #}
{% extends '@Tabler/layout-vertical.html.twig' %}
{% block page_title %}Dashboard{% endblock %}
{% block page_actions %}
<a href="{{ path('app_orders_index') }}" class="btn btn-primary">All orders</a>
{% endblock %}
{% block page_content %}
<div class="row row-cards">
<div class="col-md-4">
{% embed '@Tabler/embeds/card.html.twig' with {title: 'Open orders'} %}
{% block body %}{{ openCount }}{% endblock %}
{% endembed %}
</div>
</div>
{% endblock %}
Keep page_content as your default extension point; page_wrapper, page_title and page_actions cover the rest (the wrapper blocks were added specifically for layout flexibility, so prefer them over copying the layout).
One base template, everything else inherits. Create templates/base.html.twig that extends @Tabler/layout-vertical.html.twig (or layout-horizontal.html.twig) and add your own javascripts/stylesheets blocks plus global flash rendering there. Every page then extends your base, so a future bundle upgrade touches one file. Do the same for auth screens by extending the security layout rather than hand-rolling a login page.
Menus are event-driven, so no template edits per page. Register a subscriber once and add/remove items based on the current user's roles:
final class MenuSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [MenuEvent::class => 'onMenu'];
}
public function onMenu(MenuEvent $event): void
{
$menu = $event->getMenu();
// illustrative shape: identifier, label, route, icon — confirm exact
// builder/constructor arguments in docs/menu.md before copying
$menu->add(new MenuItem('app_orders', 'Orders', 'app_orders_index', 'fa-solid fa-shopping-cart'));
if ($this->security->isGranted('ROLE_ADMIN')) {
$menu->add(new MenuItem('app_admin', 'Administration', 'app_admin_index', 'fa-solid fa-cog'));
}
}
}
Use the generated navbar-menu-<identifier> CSS classes (and the disabled / parent item options) for styling and sub-menus instead of overriding navbar templates. Same pattern for TasksEvent and the notification event: implement the bundle's notification interface in a small value object, and convert from your domain entities in the subscriber.
ThemeContext is your per-user "preferences" hook. Instead of duplicating layouts, inject the context into a kernel.request listener (after security) and apply stored user settings — layout, theme/colour mode, compactness. That is precisely the intended use case, and it keeps the choice out of every controller.
Lean on embeds/macros rather than writing markup. Cards, alerts, modals, accordions, dropdowns, timelines, progress bars, avatars and status badges all ship as @Tabler/embeds/* or macros. The daily loop is: open the matching embed file, copy its {% block %} names and with {...} keys from the file itself (block names differ per component), then fill in your data. This is what makes upgrades cheap — the release history shows template file renames (snake_case → kebab-case in 2.0) and class changes, so copied markup ages badly while embeds keep working.
Forms: Tabler is Bootstrap 5, so use Symfony's Bootstrap 5 form theme rather than any bundle-specific one; then your form_row output matches the theme's spacing out of the box.
npm install @tabler/core (or import the bundle's shipped assets), pin the npm version to match or you get subtly broken dropdowns/modals with no error message. Upgrade Tabler only when a bundle release does.UPGRADING.md: url → href on menu/notification options, snake_case → kebab-case option names, notification interfaces merged, progress-bar deprecations, and the bundle extension switched from prepend to build when attaching config. Anything you copied from a 1.x blog post or tutorial is suspect.fa fa-* classes may render blank; use fa-solid/fa-regular and verify against your icons config.templates/bundles/TablerBundle/.... Templates changed shape across releases; a fork of the navbar is the most common reason people get stuck on an old version.tabler_logout was deprecated in 1.4.2, and login/logout/profile links are configured in tabler.yaml. Point the config at your own route names rather than creating aliases to satisfy the bundle.??, spaceless removal, Twig 3.15 fixes). Keep twig/twig current and treat deprecation notices from @Tabler templates as upgrade signals, not noise."kevinpapst/tabler-bundle": "^2.4", read each release note before bumping minor versions, and budget an hour for the Symfony/PHP constraint hop — it has moved from Symfony 6→8 and PHP 8.1→8.5 support incrementally, and each step is a small migration.How can I help you explore Laravel packages today?