Créer un thème
🎨 Qu’est-ce qu’un thème Drupal ?
Section intitulée « 🎨 Qu’est-ce qu’un thème Drupal ? »Un thème Drupal est un ensemble de fichiers qui contrôle l’apparence visuelle de votre site. Il transforme les données brutes (contenu, menus, blocs) en HTML, CSS et JavaScript pour le navigateur.
Architecture en couches
Section intitulée « Architecture en couches »┌─────────────────────────────────────┐│ Contenu (Drupal Core) │ ← Données, logique métier, base de données├─────────────────────────────────────┤│ Thème (Twig + CSS + JS) │ ← Présentation, UX, design├─────────────────────────────────────┤│ Navigateur (HTML rendu) │ ← Affichage final pour l'utilisateur└─────────────────────────────────────┘Thème vs Module
Section intitulée « Thème vs Module »| Aspect | Thème | Module |
|---|---|---|
| Rôle | Présentation, affichage | Logique métier, fonctionnalités |
| Fichiers | .twig, .css, .js | .php, .module, .routing.yml |
| Exemples | Couleurs, grilles, animations | Types de contenu, API, formulaires |
| Localisation | themes/custom/ | modules/custom/ |
| Modifie | Comment les choses apparaissent | Ce que les choses font |
Pour TailStore : Le thème gère l’apparence e-commerce (grilles produits, panier visuel, animations), tandis que les modules custom gèrent la logique métier (panier, session, traitement des commandes avec Stripe).
Héritage des thèmes
Section intitulée « Héritage des thèmes »core/themes/stable9/ (base Drupal) ↓themes/custom/tailstore/ (votre thème)Deux options :
base theme: false: Thème indépendant (contrôle total, plus de travail)base theme: olivero: Hérite d’un thème existant (rapide, moins flexible)
Choix pour TailStore : base theme: false car nous créons un design e-commerce sur-mesure avec Tailwind CSS.
📁 Emplacement des thèmes
Section intitulée « 📁 Emplacement des thèmes »Les thèmes personnalisés se placent dans :
drupal/├── core/│ └── themes/ # Thèmes du core (ne pas modifier)│ ├── olivero/│ ├── claro/│ └── stark/├── themes/│ └── custom/ # Vos thèmes personnalisés│ └── tailstore/ # Notre thème└── sites/🏗️ Créer la structure
Section intitulée « 🏗️ Créer la structure »-
Créez le dossier du thème
Fenêtre de terminal mkdir -p themes/custom/tailstorecd themes/custom/tailstore -
Créez les sous-dossiers
Fenêtre de terminal mkdir -p css/{base,components,layout}mkdir -p jsmkdir -p imagesmkdir -p templates/{layout,block,node,field,views,misc}mkdir -p config/install
Structure finale
Section intitulée « Structure finale »Répertoiretailstore/
- tailstore.info.yml
- tailstore.libraries.yml
- tailstore.theme
- tailstore.breakpoints.yml
Répertoirecss/
Répertoirebase/
- …
Répertoirecomponents/
- …
Répertoirelayout/
- …
- tailstore.css
Répertoirejs/
- tailstore.js
Répertoireimages/
- logo.svg
Répertoiretemplates/
Répertoirelayout/
- …
Répertoireblock/
- …
Répertoirenode/
- …
Répertoirefield/
- …
Répertoireviews/
- …
Répertoiremisc/
- …
📄 Fichier .info.yml
Section intitulée « 📄 Fichier .info.yml »Le fichier tailstore.info.yml est obligatoire et déclare le thème :
# tailstore.info.ymlname: TailStoretype: themedescription: 'Thème e-commerce moderne pour la boutique TailStore'package: Customcore_version_requirement: ^10 || ^11
# Thème parent (héritage)base theme: false# Ou hériter d'un thème : base theme: olivero
# Logo et screenshotlogo: images/logo.svgscreenshot: screenshot.png
# Régions du thème (zones de placement des blocs)regions: header: 'Header' primary_menu: 'Primary menu' secondary_menu: 'Secondary menu' highlighted: 'Highlighted' help: 'Help' breadcrumb: 'Breadcrumb' content: 'Content' sidebar: 'Sidebar' content_below: 'Content below' footer_top: 'Footer top' footer_bottom: 'Footer bottom'
# Librairies attachées globalementlibraries: - tailstore/global
# Formulaires de configuration du thèmeckeditor5-stylesheets: - css/ckeditor.css
# Configuration par défautlibraries-override: {}libraries-extend: {}📐 Comprendre les régions
Section intitulée « 📐 Comprendre les régions »Les régions définissent les zones où vous pouvez placer des blocs. Voici comment elles s’organisent visuellement :
┌────────────────────────────────────────────────┐│ header │ ← Logo, recherche, langues├────────────────────────────────────────────────┤│ primary_menu │ secondary_menu │ ← Navigation principale/secondaire├────────────────────────────────────────────────┤│ highlighted │ ← Messages système, bandeau promo├────────────────────────────────────────────────┤│ breadcrumb │ ← Fil d'Ariane├───────────────────────────┬────────────────────┤│ │ ││ content │ sidebar │ ← Contenu principal + filtres/widgets│ (obligatoire) │ │├───────────────────────────┴────────────────────┤│ content_below │ ← Produits recommandés, newsletter├────────────────────────────────────────────────┤│ footer_top │ ← Liens, réseaux sociaux│ footer_bottom │ ← Copyright, mentions légales└────────────────────────────────────────────────┘🛒 Régions spécifiques e-commerce
Section intitulée « 🛒 Régions spécifiques e-commerce »Pour le projet TailStore, nous ajouterons des régions supplémentaires adaptées à l’e-commerce. Modifiez la section regions: pour inclure :
regions: # Régions standards header: 'Header' cart_header: 'Cart icon & counter' primary_menu: 'Primary menu' secondary_menu: 'Secondary menu' promo_banner: 'Promotional banner' highlighted: 'Highlighted' help: 'Help' breadcrumb: 'Breadcrumb'
# Zones de contenu content: 'Content' sidebar: 'Sidebar (filters, widgets)' content_below: 'Content below (recommendations)'
# Footer footer_top: 'Footer top' footer_bottom: 'Footer bottom' trust_badges: 'Trust badges (checkout)'Utilisation des nouvelles régions :
cart_header: Icône panier avec compteur d’articles (dynamique)promo_banner: Bandeau promotionnel (ex: “Livraison gratuite dès 50€”)sidebar: Filtres de prix, catégories, disponibilité sur les pages cataloguecontent_below: “Vous aimerez aussi”, cross-sell, upselltrust_badges: Badges de réassurance (paiement sécurisé, satisfait ou remboursé)
📚 Fichier .libraries.yml
Section intitulée « 📚 Fichier .libraries.yml »Le fichier tailstore.libraries.yml déclare les CSS et JS selon les standards Drupal :
# tailstore.libraries.yml
# Librairie globale (chargée sur toutes les pages)global: version: 1.0.0 css: base: css/base/reset.css: {} css/base/typography.css: {} layout: css/layout/grid.css: {} css/layout/regions.css: {} component: css/components/buttons.css: {} css/components/cards.css: {} css/components/forms.css: {} css/components/navigation.css: {} theme: css/tailstore.css: {} js: js/tailstore.js: {} dependencies: - core/drupal - core/once
# Librairie spécifique pour le sliderslider: version: 1.0.0 css: component: css/components/slider.css: {} js: js/slider.js: {} dependencies: - tailstore/global - core/once
# Librairie pour le panier avec drupalSettingscart: version: 1.0.0 js: js/cart.js: { attributes: { defer: true } } dependencies: - tailstore/global - core/drupalSettings
# CDN externes (Swiper)swiper: version: 11.0.0 remote: https://swiperjs.com/ license: name: MIT url: https://github.com/nolimits4web/swiper/blob/master/LICENSE gpl-compatible: true css: theme: https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css: { type: external, minified: true } js: https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js: { type: external, minified: true }
# Alpine.js avec attributsalpine: version: 3.14.1 remote: https://alpinejs.dev/ license: name: MIT url: https://github.com/alpinejs/alpine/blob/main/LICENSE.md gpl-compatible: true js: https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js: { type: external, attributes: { defer: true } }
# Librairie chargée dans le header (ex: polices)fonts: version: 1.0.0 header: true css: theme: https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap: { type: external }
# Librairie avec JS non agrégéanalytics: version: 1.0.0 js: js/analytics.js: { preprocess: false } dependencies: - core/drupal# Types de CSS avec leurs poids de chargementexample: css: # Poids CSS_BASE = -200 : Reset, normalisation HTML base: css/base/reset.css: {} css/base/normalize.css: {}
# Poids CSS_LAYOUT = -100 : Structure, grille, régions layout: css/layout/grid.css: {} css/layout/regions.css: {}
# Poids CSS_COMPONENT = 0 : Composants individuels component: css/components/buttons.css: {} css/components/cards.css: {} css/components/forms.css: {}
# Poids CSS_STATE = 100 : États (hover, active, focus) state: css/state/states.css: {}
# Poids CSS_THEME = 200 : Styles finaux, surcharges theme: css/theme/colors.css: {} css/theme/custom.css: {}# Options avancées pour JavaScriptadvanced-js: js: # JS chargé dans le <head> js/header-script.js: { header: true }
# JS déjà minifié (évite la minification Drupal) js/minified.js: { minified: true }
# Attributs personnalisés js/custom.js: { attributes: { id: "custom-script", "data-version": "1.0" } }
# JS non agrégé js/no-aggregate.js: { preprocess: false }🟨 JavaScript dans Drupal
Section intitulée « 🟨 JavaScript dans Drupal »Structure recommandée : IIFE + Drupal.behaviors
Section intitulée « Structure recommandée : IIFE + Drupal.behaviors »Les fichiers JavaScript Drupal utilisent une structure spécifique appelée IIFE (Immediately Invoked Function Expression) combinée avec Drupal.behaviors.
// Fonction anonyme classiquefunction() { console.log('Hello World!');}
// Devient une IIFE(function() { console.log('Hello World!');})();// IIFE avec paramètres (injection de dépendances)(function($, Drupal, once) { 'use strict';
// Code protégé dans la portée locale Drupal.behaviors.myBehavior = { attach: function(context, settings) { // Votre code ici } };
})(jQuery, Drupal, once);// js/tailstore.js - Exemple complet(function($, Drupal, once) { 'use strict';
// Comportement pour le menu mobile Drupal.behaviors.tailstoreMobileMenu = { attach: function(context, settings) { once('mobile-menu', '.mobile-menu-toggle', context).forEach(function(element) { $(element).on('click', function() { $('.main-navigation').toggleClass('is-open'); }); }); } };
// Comportement pour les sliders Drupal.behaviors.tailstoreSliders = { attach: function(context, settings) { once('product-slider', '.product-slider', context).forEach(function(element) { // Initialisation du slider new Swiper(element, { slidesPerView: 1, spaceBetween: 10, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', }, }); }); } };
})(jQuery, Drupal, once);Pourquoi cette structure ?
Section intitulée « Pourquoi cette structure ? »Exemple minimal pour TailStore
Section intitulée « Exemple minimal pour TailStore »// js/tailstore.js(function($, Drupal, once) { 'use strict';
// Menu mobile toggle Drupal.behaviors.tailstoreMobileMenu = { attach: function(context, settings) { once('mobile-menu', '.mobile-menu-toggle', context).forEach(function(toggle) { $(toggle).on('click', function(e) { e.preventDefault(); $('.main-navigation').toggleClass('is-open'); }); }); } };
})(jQuery, Drupal, once);🐘 Fichier .theme
Section intitulée « 🐘 Fichier .theme »Le fichier tailstore.theme contient les fonctions PHP qui utilisent le système de hooks de Drupal.
Qu’est-ce qu’un hook Drupal ?
Section intitulée « Qu’est-ce qu’un hook Drupal ? »Un hook est un point d’extension dans Drupal qui permet aux modules et thèmes de modifier ou d’étendre le comportement du système. Les hooks sont des fonctions PHP nommées selon une convention spécifique : nom_du_module_ou_theme_nom_du_hook().
// Hook de préprocessing : Modifie les variables avant le renduhook_preprocess_HOOK(&$variables)
// Hook d'altération de formulaire : Modifie les formulaireshook_form_alter(&$form, $form_state, $form_id)
// Hook de suggestions de template : Ajoute des templates alternatifshook_theme_suggestions_HOOK_alter(&$suggestions, $variables)// Pour un thème nommé "tailstore" :// hook_preprocess_page → tailstore_preprocess_page// hook_form_alter → tailstore_form_alter// hook_theme_suggestions_node_alter → tailstore_theme_suggestions_node_alter
function tailstore_preprocess_page(&$variables) { // Votre code ici}Le fichier tailstore.theme contient les fonctions PHP :
<?php
/** * @file * Functions to support theming in the TailStore theme. */
use Drupal\Core\Form\FormStateInterface;
/** * Implements hook_preprocess_HOOK() for html.html.twig. * * Ce hook permet de modifier les variables avant le rendu du template html.html.twig. * Ici, on ajoute des classes CSS au body pour le styling et la logique conditionnelle. */function tailstore_preprocess_html(&$variables) { // Ajouter une classe globale au thème $variables['attributes']['class'][] = 'tailstore-theme';
// Ajouter une classe spécifique pour les pages produit // Utile pour appliquer des styles particuliers aux pages e-commerce $node = \Drupal::routeMatch()->getParameter('node'); if ($node && $node->bundle() === 'product') { $variables['attributes']['class'][] = 'page-product'; }}
## 🧠 Bonnes pratiques essentielles
### 🔒 Sécurité : Validation des données
Ne jamais faire confiance aux données utilisateur ou même aux champs Drupal sans validation.
**❌ Dangereux** :```phpfunction tailstore_preprocess_node(&$variables) { $node = $variables['node']; $variables['price'] = $node->get('field_price')->value; // Pas de validation !}✅ Sécurisé :
function tailstore_preprocess_node(&$variables) { $node = $variables['node'];
if ($node->hasField('field_price') && !$node->get('field_price')->isEmpty()) { $price = $node->get('field_price')->value;
// Valider que c'est un nombre positif if (is_numeric($price) && $price >= 0) { $variables['price'] = number_format((float) $price, 2, ',', ' ') . ' €'; } else { // Logger l'erreur \Drupal::logger('tailstore')->warning('Invalid price for node @nid', [ '@nid' => $node->id() ]); } }}⚡ Performance : Chargement conditionnel
Section intitulée « ⚡ Performance : Chargement conditionnel »Principe : Évitez de charger des librairies lourdes sur toutes les pages.
Exemple : Charger le slider uniquement sur la homepage :
function tailstore_preprocess_page(&$variables) { // Slider chargé UNIQUEMENT sur la homepage if (\Drupal::service('path.matcher')->isFrontPage()) { $variables['#attached']['library'][] = 'tailstore/slider'; $variables['#attached']['library'][] = 'tailstore/swiper'; }
// Panier chargé UNIQUEMENT sur les pages produit et panier $route_name = \Drupal::routeMatch()->getRouteName(); if (in_array($route_name, ['entity.node.canonical', 'tailstore.cart'])) { $variables['#attached']['library'][] = 'tailstore/cart'; }}Impact :
- Homepage : 200KB JS
- Page article : 50KB JS (150KB économisés !)
♿ Accessibilité : Régions et landmarks
Section intitulée « ♿ Accessibilité : Régions et landmarks »📖 Documentation : PHPDoc obligatoire
Section intitulée « 📖 Documentation : PHPDoc obligatoire »Chaque hook doit avoir un bloc de documentation PHPDoc.
Template recommandé :
/** * Implements hook_preprocess_HOOK() for node.html.twig. * * Prepare variables for node templates. * * @param array $variables * An associative array containing: * - elements: Node entity and view mode. * - node: The node entity. * - view_mode: View mode (full, teaser, card, etc.). */function tailstore_preprocess_node(&$variables) { // Code...}Le fichier tailstore.theme contient les fonctions PHP :
<?php
/** * @file * Functions to support theming in the TailStore theme. */
use Drupal\Core\Form\FormStateInterface;
/** * Implements hook_preprocess_HOOK() for html.html.twig. * * Ce hook permet de modifier les variables avant le rendu du template html.html.twig. * Ici, on ajoute des classes CSS au body pour le styling et la logique conditionnelle. */function tailstore_preprocess_html(&$variables) { // Ajouter une classe globale au thème $variables['attributes']['class'][] = 'tailstore-theme';
// Ajouter une classe spécifique pour les pages produit // Utile pour appliquer des styles particuliers aux pages e-commerce $node = \Drupal::routeMatch()->getParameter('node'); if ($node && $node->bundle() === 'product') { $variables['attributes']['class'][] = 'page-product'; }}
/** * Implements hook_preprocess_HOOK() for page.html.twig. * * Ce hook permet de modifier les variables avant le rendu du template page.html.twig. * Ici, on charge des librairies JavaScript selon le contexte de la page. */function tailstore_preprocess_page(&$variables) { // Charger la librairie slider uniquement sur la page d'accueil // Évite de charger du JS inutile sur les autres pages if (\Drupal::service('path.matcher')->isFrontPage()) { $variables['#attached']['library'][] = 'tailstore/slider'; $variables['#attached']['library'][] = 'tailstore/swiper'; }}
/** * Implements hook_preprocess_HOOK() for node.html.twig. * * Ce hook permet de modifier les variables avant le rendu des nodes. * Ici, on prépare des données spécifiques pour les produits e-commerce. */function tailstore_preprocess_node(&$variables) { /** @var \Drupal\node\NodeInterface $node */ $node = $variables['node'];
// Variables personnalisées pour les produits if ($node->bundle() === 'product') { // Formater le prix avec séparateur français if ($node->hasField('field_price') && !$node->get('field_price')->isEmpty()) { $variables['formatted_price'] = number_format( $node->get('field_price')->value, 2, ',', ' ' ) . ' €'; }
// Vérifier la disponibilité en stock if ($node->hasField('field_stock')) { $variables['in_stock'] = $node->get('field_stock')->value > 0; }
// Gestion des prix barrés pour les promotions if ($node->hasField('field_old_price') && !$node->get('field_old_price')->isEmpty()) { $variables['old_price'] = number_format( $node->get('field_old_price')->value, 2, ',', ' ' ) . ' €'; $variables['is_on_sale'] = TRUE; } }}
/** * Implements hook_preprocess_HOOK() for field.html.twig. * * Ce hook permet de modifier les variables avant le rendu des champs. * Ici, on ajoute des classes CSS spécifiques selon le type de champ. */function tailstore_preprocess_field(&$variables) { $field_name = $variables['field_name'];
// Ajouter une classe spécifique pour le prix if ($field_name === 'field_price') { $variables['attributes']['class'][] = 'product-price'; }}
/** * Implements hook_form_alter(). * * Ce hook permet de modifier tous les formulaires du système. * Ici, on personnalise l'apparence et le comportement de certains formulaires. */function tailstore_form_alter(&$form, FormStateInterface $form_state, $form_id) { // Personnaliser le formulaire de recherche if ($form_id === 'search_block_form') { $form['keys']['#attributes']['placeholder'] = t('Rechercher...'); $form['keys']['#attributes']['class'][] = 'search-input'; }
// Personnaliser les filtres exposés des vues if (strpos($form_id, 'views_exposed_form') === 0) { $form['#attributes']['class'][] = 'filters-form'; }}
/** * Implements hook_theme_suggestions_HOOK_alter() for node. * * Ce hook permet de suggérer des templates alternatifs selon le contexte. * Ici, on propose des templates spécifiques pour les produits en promotion. */function tailstore_theme_suggestions_node_alter(array &$suggestions, array $variables) { /** @var \Drupal\node\NodeInterface $node */ $node = $variables['elements']['#node']; $view_mode = $variables['elements']['#view_mode'];
// Ajouter une suggestion basée sur un champ personnalisé if ($node->bundle() === 'product' && $node->hasField('field_featured')) { if ($node->get('field_featured')->value) { $suggestions[] = 'node__product__featured'; $suggestions[] = 'node__product__' . $view_mode . '__featured'; } }}## 📐 Fichier .breakpoints.yml
Le fichier **tailstore.breakpoints.yml** définit les points de rupture responsive :
```yaml# tailstore.breakpoints.yml
tailstore.mobile: label: Mobile mediaQuery: '' weight: 0 multipliers: - 1x - 2x
tailstore.narrow: label: Narrow mediaQuery: 'all and (min-width: 560px)' weight: 1 multipliers: - 1x - 2x
tailstore.medium: label: Medium mediaQuery: 'all and (min-width: 768px)' weight: 2 multipliers: - 1x - 2x
tailstore.wide: label: Wide mediaQuery: 'all and (min-width: 1024px)' weight: 3 multipliers: - 1x - 2x
tailstore.extra_wide: label: Extra Wide mediaQuery: 'all and (min-width: 1280px)' weight: 4 multipliers: - 1x - 2x✅ Activer le thème
Section intitulée « ✅ Activer le thème »Via l’interface
Section intitulée « Via l’interface »- Appearance (
/admin/appearance) - Trouvez “TailStore” dans la liste
- Cliquez sur Install and set as default
Via Drush
Section intitulée « Via Drush »# Activer le thèmedrush theme:enable tailstore
# Définir comme thème par défautdrush config:set system.theme default tailstore -y
# Vider le cachedrush cr🎨 CSS de base
Section intitulée « 🎨 CSS de base »css/base/reset.css
Section intitulée « css/base/reset.css »/* Reset moderne */*,*::before,*::after { box-sizing: border-box;}
* { margin: 0; padding: 0;}
html { font-size: 16px; scroll-behavior: smooth;}
body { line-height: 1.5; -webkit-font-smoothing: antialiased;}
img,picture,video,canvas,svg { display: block; max-width: 100%;}
input,button,textarea,select { font: inherit;}
p,h1,h2,h3,h4,h5,h6 { overflow-wrap: break-word;}css/base/typography.css
Section intitulée « css/base/typography.css »/* Variables de typographie */:root { --font-sans: 'Inter', system-ui, -apple-system, sans-serif; --font-mono: 'Fira Code', monospace;
--text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.125rem; --text-xl: 1.25rem; --text-2xl: 1.5rem; --text-3xl: 2rem; --text-4xl: 2.5rem;}
body { font-family: var(--font-sans); font-size: var(--text-base); color: var(--color-dark, #1a1a2e);}
h1, h2, h3, h4, h5, h6 { font-weight: 600; line-height: 1.2;}
h1 { font-size: var(--text-4xl); }h2 { font-size: var(--text-3xl); }h3 { font-size: var(--text-2xl); }h4 { font-size: var(--text-xl); }
a { color: var(--color-primary, #0073e6); text-decoration: none;}
a:hover { text-decoration: underline;}Placez votre logo dans images/logo.svg ou configurez-le via :
- Appearance → Settings → TailStore
- Section Logo image
- Uploadez ou spécifiez le chemin
💾 Vérifier l’installation
Section intitulée « 💾 Vérifier l’installation »# Vérifier que le thème est actifdrush config:get system.theme default
# Devrait retourner : 'tailstore'
# Vider le cachedrush cr
# Visitez le site pour voir le thème✅ Checkpoint : Thème activé et fonctionnel
Section intitulée « ✅ Checkpoint : Thème activé et fonctionnel »Vérifiez que tout fonctionne correctement :
-
Vérifier l’activation
Fenêtre de terminal drush config:get system.theme defaultAttendu :
'system.theme:default': tailstore -
Vérifier les logs d’erreurs
Fenêtre de terminal drush watchdog:show --severity=Error --count=10Attendu : Aucune erreur liée au thème
-
Tester l’accessibilité du site
Fenêtre de terminal curl -I http://localhost/Attendu :
HTTP/1.1 200 OK -
Inspecter le HTML dans le navigateur
- Ouvrez le site dans votre navigateur
- F12 → Onglet Elements
- Vérifiez que
<body>contientclass="tailstore-theme"
-
Vérifier le chargement CSS/JS
- F12 → Onglet Network
- Rechargez la page
- Vérifiez que
tailstore.cssettailstore.jssont chargés (statut 200)
🎯 Exercices pratiques
Section intitulée « 🎯 Exercices pratiques »Exercice 1 : Créer le thème minimal
Section intitulée « Exercice 1 : Créer le thème minimal »Objectif : Créer un thème fonctionnel avec seulement les fichiers obligatoires.
-
Créez la structure minimale :
Fenêtre de terminal mkdir -p themes/custom/tailstorecd themes/custom/tailstoretouch tailstore.info.yml -
Éditez
tailstore.info.ymlavec le strict minimum :name: TailStoretype: themecore_version_requirement: ^10 || ^11regions:content: 'Content' -
Activez le thème :
Fenêtre de terminal drush theme:enable tailstoredrush config:set system.theme default tailstore -ydrush cr -
Visitez votre site : il doit s’afficher (même sans style)
Validation : ✅ Le site est accessible sans erreur 500
Exercice 2 : Premier hook de preprocessing
Section intitulée « Exercice 2 : Premier hook de preprocessing »Objectif : Ajouter une classe CSS personnalisée au <body>.
-
Créez le fichier
tailstore.theme:Fenêtre de terminal touch tailstore.theme -
Ajoutez ce code :
<?phpfunction tailstore_preprocess_html(&$variables) {$variables['attributes']['class'][] = 'tailstore-active';} -
Videz le cache :
Fenêtre de terminal drush cr -
Inspectez le HTML (F12) : vous devriez voir :
<body class="tailstore-active">
Validation : ✅ La classe tailstore-active apparaît dans le <body>
Exercice 3 : Charger un CSS personnalisé
Section intitulée « Exercice 3 : Charger un CSS personnalisé »Objectif : Créer et charger un fichier CSS de test.
-
Créez la structure CSS :
Fenêtre de terminal mkdir -p css -
Créez
css/test.cssavec un style visible :body {background-color: #ffe6e6;border-top: 5px solid #dc3545;} -
Créez
tailstore.libraries.yml:global:version: 1.0.0css:theme:css/test.css: {} -
Déclarez la librairie dans
tailstore.info.yml:libraries:- tailstore/global -
Videz le cache et rechargez :
Fenêtre de terminal drush cr
Validation : ✅ Le fond de la page doit être rose clair avec une bordure rouge en haut
Si ça ne fonctionne pas :
- Vérifiez le chemin dans
libraries.yml(relatif au dossier du thème) - Inspectez F12 → Network : le fichier
test.cssdoit être chargé (statut 200) - Vérifiez la console pour des erreurs 404
Exercice 4 : Ajouter les régions e-commerce
Section intitulée « Exercice 4 : Ajouter les régions e-commerce »Objectif : Personnaliser les régions pour le projet TailStore.
-
Modifiez la section
regions:danstailstore.info.ymlpour ajouter :regions:header: 'Header'cart_header: 'Cart icon'primary_menu: 'Primary menu'content: 'Content'sidebar: 'Sidebar'footer_top: 'Footer' -
Videz le cache :
Fenêtre de terminal drush cr -
Allez dans Structure → Block layout (
/admin/structure/block) -
Vérifiez que vos nouvelles régions apparaissent
Validation : ✅ Les régions “Cart icon” et “Sidebar” sont visibles dans la page de placement des blocs
✅ Checklist complète
Section intitulée « ✅ Checklist complète »| Étape | Fichier/Action | Statut | Commande de validation |
|---|---|---|---|
| 1 | Créer themes/custom/tailstore/ | ⬜ | ls themes/custom/tailstore |
| 2 | tailstore.info.yml (minimal) | ⬜ | drush theme:list | grep tailstore |
| 3 | Régions e-commerce ajoutées | ⬜ | Vérifier /admin/structure/block |
| 4 | tailstore.libraries.yml | ⬜ | Valider syntaxe YAML |
| 5 | tailstore.theme avec hook | ⬜ | php -l themes/custom/tailstore/tailstore.theme |
| 6 | CSS de base (reset, typography) | ⬜ | Inspecter <head> (F12) |
| 7 | tailstore.breakpoints.yml | ⬜ | drush pm:enable breakpoint |
| 8 | Activer le thème | ⬜ | drush config:get system.theme default |
| 9 | Vider le cache | ⬜ | drush cr |
| 10 | Site fonctionnel | ⬜ | Ouvrir dans le navigateur |
Temps estimé total : 45 minutes
🔜 Prochaine étape
Section intitulée « 🔜 Prochaine étape »Le thème est créé ! Passons aux Templates Twig.