Thème admin Symfony basé sur TailAdmin — bundle réutilisable (Symfony UX, Stimulus, AssetMapper, Tailwind CSS v4).
Ce dépôt suit la structure définie dans ADR-002 :
tailsfadmin/
├── src/ # TailsfadminBundle (le produit, distribué sur Packagist)
│ ├── TailsfadminBundle.php # AbstractBundle (Symfony >= 6.1)
│ └── Info/ # Services du bundle
├── config/
│ └── services.yaml # Services DI du bundle (préfixe tailsfadmin.*)
├── templates/ # Templates Twig namespacés @Tailsfadmin
├── assets/
│ └── controllers/ # Contrôleurs Stimulus du bundle (ADR-003)
├── demo/ # Application de démo (consomme le bundle en local)
│ ├── src/Controller/ # Contrôleurs de la démo (usage seul)
│ └── templates/ # Templates de la démo
├── tests/ # Tests du bundle (unitaires)
├── docs/adr/ # Architecture Decision Records
└── composer.json # Package du bundle (type: symfony-bundle)
IMPORTANT : aucun code réutilisable ne doit vivre dans
demo/. La démo ne contient que de l'usage du bundle. Toute logique partageable appartient àsrc/.
- PHP >= 8.5
- Composer >= 2.0
- Symfony >= 7.3 || 8.x
# 1. Cloner le dépôt
git clone https://github.com/thibmonier/tailsfadmin.git
cd tailsfadmin
# 2. Installer les dépendances du bundle
composer install
# 3. Installer la démo
cd demo
composer install
# 4. Lancer la démo (Symfony CLI)
symfony serve -d
# ou : php -S localhost:8000 -t publiccomposer require tailsfadmin/tailsfadmin-bundleLe bundle est activé automatiquement par Symfony Flex.
Les contrôleurs Stimulus du bundle vivent sous bundles/tailsfadmin. Stimulus
doit être démarré côté hôte (assets/bootstrap.js du skeleton). Déclarez le path
AssetMapper des contrôleurs dans votre config/packages/asset_mapper.yaml (le
prepend() du bundle ne survit pas au merge Flex ; la recette Flex — à venir —
l'automatisera) :
framework:
asset_mapper:
paths:
'assets/': ''
'vendor/tailsfadmin/tailsfadmin-bundle/assets/controllers': 'bundles/tailsfadmin'
'vendor/tailsfadmin/tailsfadmin-bundle/assets/vendor-src': 'bundles/tailsfadmin-vendor'Dépendances JS tierces — commande d'installation. Cinq contrôleurs du bundle
s'appuient sur des bibliothèques tierces (ApexCharts, jsvectormap, flatpickr,
Dropzone, FullCalendar). Un bundle ne peut pas injecter d'entrées dans
l'importmap.php de l'hôte (l'importmap n'est pas une config mergeable — voir
ADR-007). Le bundle fournit
donc une commande qui ajoute ces pins et vendore les fichiers localement :
php bin/console tailsfadmin:assets:install- Sans CDN au runtime : les libs sont téléchargées à l'installation puis
servies en local (ADR-004/006). La source de vérité des versions pinées est
config/importmap-entries.php, distribué avec le bundle. - Idempotente : relançable sans risque, n'ajoute que ce qui manque.
- Conflits de version : si votre app a déjà piné une lib dans une autre
version, la commande ne l'écrase pas ; elle signale le conflit. Forcez la
réécriture avec
--force. - Garde-fou : si une page utilise un composant dont la lib n'est pas installée, une erreur explicite en console rappelle la commande à lancer (au lieu du cryptique « Failed to resolve module specifier »).
Compilez ensuite les assets :
php bin/console tailwind:build # CSS Tailwind v4 (symfonycasts/tailwind-bundle)
php bin/console asset-map:compileLes contrôleurs sans dépendance externe (
theme,sidebar,modal,dropdown,alert-dismiss,preloader,search,submenu) fonctionnent sans cette commande, via le seul path AssetMapper.
Le bundle distribue son thème (tokens @theme, dark mode, classes composants
.menu-item*…) dans assets/styles/theme.css. Votre app le branche en
une seule ligne, dans sa propre entrée Tailwind v4 — sans recopier les
tokens ni le CSS des composants :
/* assets/styles/app.css de VOTRE application */
@import "tailwindcss";
@import "../../vendor/tailsfadmin/tailsfadmin-bundle/assets/styles/theme.css";-
Standalone, sans Node : compilez avec le binaire
symfonycasts/tailwind-bundle(php bin/console tailwind:build), comme le reste de votre CSS. -
Scan du contenu du bundle :
theme.cssdéclare des@source(relatifs, donc portables depuisvendor/) vers les templates et contrôleurs du bundle, afin que les utilitaires qu'ils emploient soient bien générés chez vous. -
Dark mode : stratégie par classe — activez
.darksur<html>. L'échelle de gris n'est pas inversée (les composants portent des variantesdark:explicites). -
Rebranding : redéfinissez les tokens
--color-brand-*après l'import (la cascade:rootl'emporte sur@theme) — cela repeint boutons, liens et item de menu actif :@import "tailwindcss"; @import ".../theme.css"; :root { --color-brand-500: #7c3aed; --color-brand-600: #6d28d9; }
Besoin d'une entrée « tout-en-un » (Tailwind + thème) ? Importez plutôt
assets/styles/app.cssdu bundle. La démo (demo/assets/styles/app.css) illustre le pattern hôte ci-dessus.
Les label sont des clés de traduction (voir i18n ci-dessous) ou des libellés bruts :
tailsfadmin:
default_locale: fr
locales: ['fr', 'en'] # whitelist de la bascule de langue
rtl_locales: ['ar']
menu:
- group: menu.groups.menu
items:
- { label: menu.dashboard, path: /, icon: dashboard }
- label: menu.tables
path: /tables
icon: tables
children:
- { label: menu.tables_basic, path: /tables/basic }Filtrage par permission (optionnel). Chaque item (et sous-item) accepte une clé
permission. Si un composant de sécurité Symfony est installé, les items dont
l'utilisateur courant n'a pas l'autorisation sont masqués (is_granted), et un
groupe entièrement filtré n'est pas rendu. Sans sécurité, tout reste visible.
tailsfadmin:
menu:
- group: menu.groups.admin
items:
- { label: menu.users, path: /admin/users, icon: user-profile, permission: ROLE_ADMIN }
- { label: menu.reports, path: /reports, icon: charts, permission: 'view:reports' }Le MenuBuilder reçoit @?security.authorization_checker (injection optionnelle,
aucune dépendance dure). Pour l'activer, installez symfony/security-bundle.
{% extends '@Tailsfadmin/layout/admin.html.twig' %}
{% block breadcrumb %}<twig:tsf:Layout:Breadcrumb pageName="Tableau de bord" />{% endblock %}
{% block content %}
<twig:tsf:Ui:Card title="Bienvenue">
<twig:block name="body">Votre première page tailsfadmin.</twig:block>
</twig:tsf:Ui:Card>
{% endblock %}Le layout attend une route nommée
home(logo de la sidebar) etlocale_switchsi vous utilisez le sélecteur de langue.
Personnaliser le header. Le composant tsf:Layout:Header expose des blocs
surchargeables — language, notifications, user_menu — pour brancher un vrai
menu utilisateur (déconnexion + CSRF), un flux de notifications réel, ou retirer le
sélecteur de langue si l'application ne définit pas de route locale_switch.
Surchargez {% block header %} du layout avec le composant et vos slots :
{% block header %}
<twig:tsf:Layout:Header>
{# Retirer le sélecteur de langue (pas de route locale_switch) #}
<twig:block name="language"></twig:block>
{# Vrai menu utilisateur #}
<twig:block name="user_menu">
<a href="{{ path('account') }}">{{ app.user.userIdentifier }}</a>
<form method="post" action="{{ path('app_logout') }}">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('logout') }}">
<button type="submit">{{ 'Déconnexion'|trans }}</button>
</form>
</twig:block>
</twig:tsf:Layout:Header>
{% endblock %}Installez symfony/translation, réglez framework.default_locale + enabled_locales,
et fournissez vos catalogues. Le bundle expose ses propres traductions du chrome
(header, breadcrumb…) et les helpers Twig tsf_dir() / tsf_locales().
Tous les composants <twig:tsf:… /> (props, slots, exemples) sont documentés
dans docs/components.md ; une galerie vivante est
servie sur /ui-kit dans la démo.
# Analyse statique (niveau max)
composer phpstan
# Vérification du style PSR-12
composer cs
# Correction automatique du style
composer cs-fix
# Tests unitaires du bundle
composer test
# Tests fonctionnels de la démo
cd demo && php bin/phpunit
# Tests E2E (montage JS réel + audit accessibilité axe-core)
cd demo && composer test:e2e
# Lint des contrôleurs Stimulus (Biome)
npx @biomejs/biome check assets/controllers/Le versionnement suit SemVer ; les évolutions sont consignées dans CHANGELOG.md (format Keep a Changelog).
# Conteneur DI — doit lister ≥ 1 service tailsfadmin.*
cd demo && php bin/console debug:container tailsfadmin.bundle_info
# Warmup du cache — doit passer sans erreur
cd demo && php bin/console cache:warmupLa démo peut aussi être lancée via Docker Compose, sous FrankenPHP / PHP 8.5,
pour garantir un environnement reproductible (local = CI). Voir
demo/Dockerfile et compose.yaml pour le détail de l'image.
# Construire l'image et démarrer le conteneur en arrière-plan
docker compose up --build -d
# Suivre les logs
docker compose logs demo -f
# Arrêter et supprimer le conteneur
docker compose downL'application est ensuite disponible sur http://localhost/.
# Statut du healthcheck (doit passer à "healthy")
docker compose ps
# Version de PHP et de FrankenPHP dans le conteneur
docker compose exec demo php -v
docker compose exec demo frankenphp versionSi le port 80 (ou 443) est déjà occupé sur la machine hôte, définir
HOST_HTTP_PORT / HOST_HTTPS_PORT avant de lancer la commande :
HOST_HTTP_PORT=8080 HOST_HTTPS_PORT=8443 docker compose up --build -d
# → application disponible sur http://localhost:8080/Ces variables peuvent aussi être placées dans un fichier .env à la racine
du dépôt (non versionné) pour éviter de les répéter à chaque commande.
L'image de base dunglas/frankenphp:php8.5 peut être téléchargée à l'avance,
par exemple avant un déplacement sans accès réseau ou pour accélérer un
premier build :
docker pull dunglas/frankenphp:php8.5Le docker compose up --build suivant réutilisera l'image déjà présente en
cache local pour l'étape FROM, sans nouveau téléchargement.
| Couche | Technologie |
|---|---|
| Backend | PHP 8.5, Symfony 8.1, AbstractBundle |
| Frontend | Stimulus / Symfony UX, AssetMapper |
| CSS | Tailwind CSS v4 |
| Tests | PHPUnit 12, WebTestCase |
| Qualité | PHPStan max, PHP CS Fixer (PSR-12) |
MIT — voir LICENSE