Pourquoi un thème sur mesure en 2026 ?
Créer un thème WordPress sur mesure en 2026, c’est faire le choix de la maîtrise totale : design, performance, accessibilité et maintenabilité. Exit les page builders lourds et les thèmes génériques — place à un code propre, modulaire et pensé pour durer trois à cinq ans sans dette technique.
Un thème pré-fait charge en moyenne 15 à 30 scripts, 10+ feuilles de style et des milliers de règles CSS non utilisées. Résultat : LCP au-delà de 4s, CLS ingérable, et un back-office qui ralentit avec chaque plugin tiers. À l’inverse, un thème custom bien architecturé tient dans 5 fichiers PHP + 1 CSS compilé, charge en moins de 1s et permet des scores Lighthouse 95+ de façon reproductible.
Dans ce guide, nous allons construire un thème WordPress complet en utilisant Tailwind CSS 4 pour le front et ACF Pro pour rendre chaque contenu administrable sans toucher au code. Stack cible : WordPress 6.6+, PHP 8.2+, Node 18+, Tailwind 4 (configuration CSS-first), ACF Pro 6+.
Un bon thème WordPress n'est pas celui qui fait tout — c'est celui qui fait bien ce qu'il doit faire, sans compromis sur la qualité du code.
Prérequis et environnement de développement
Avant toute chose, un environnement local propre : versions à jour, outils de debug actifs et une licence ACF Pro valide. Voici le matériel recommandé pour suivre ce guide dans de bonnes conditions :
- WordPress 6.6+ installé en local via Local by Flywheel, DDEV ou
wp-env(Docker officiel WordPress) - PHP 8.2+ avec les extensions
mbstring,intl,gd,curl - Node.js 18+ et npm 9+ pour le build Tailwind (ou pnpm/bun selon préférences)
- ACF Pro 6+ (licence active) installé comme plugin — indispensable pour la gestion des champs complexes
- WP-CLI 2.10+ pour les opérations de maintenance et scripts d’import
- Un éditeur avec support PHP solide : VS Code + Intelephense + PHP DocBlocker (ou PhpStorm)
WP_DEBUG,WP_DEBUG_LOGetWP_DEBUG_DISPLAYactivés danswp-config.php
// Configuration de debug recommandée en dev
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'SCRIPT_DEBUG', true );
define( 'DISALLOW_FILE_EDIT', true );Utilisez wp-env pour un environnement Docker standardisé et reproductible entre développeurs. C’est la solution recommandée par l’équipe WordPress Core et elle s’intègre nativement avec Gutenberg.
Architecture du thème
Un thème WordPress bien structuré suit une arborescence claire et prévisible. La philosophie : un fichier, une responsabilité. Le functions.php ne fait qu’orchestrer des modules inc/ — il ne contient jamais de logique métier.
madebysteph/
├── style.css # Métadonnées du thème (obligatoire WP)
├── functions.php # Bootstrap (charge les modules inc/)
├── header.php, footer.php # Parts globales
├── index.php, front-page.php # Templates racine
├── single.php, page.php # Détail article / page
├── archive.php, 404.php # Archives + erreur
├── template-parts/ # Morceaux réutilisables
│ ├── hero.php
│ ├── card-project.php
│ └── card-article.php
├── inc/ # Modules PHP (1 fichier = 1 responsabilité)
│ ├── setup.php # theme_supports, nav menus, image sizes
│ ├── enqueue.php # Scripts & styles
│ ├── acf-options.php # ACF options pages
│ ├── post-types.php # CPT + taxonomies
│ └── security.php # Hardening WP
├── blocks/ # Blocs ACF Gutenberg
├── assets/
│ ├── css/source.css # Source Tailwind
│ ├── css/tailwind.css # Build compilé
│ └── js/*.js
├── acf-json/ # Sync ACF local (versionné Git)
└── package.json + postcss.config.jsLe fichier style.css
Obligatoire pour que WordPress reconnaisse le thème. Il ne contient que les métadonnées — tout le CSS réel vit dans Tailwind.
/*
Theme Name: Made by Steph
Theme URI: https://madebysteph.fr
Author: Stéphane Donna
Description: Thème WordPress sur mesure — Portfolio & blog
Version: 2.0.0
Requires at least: 6.6
Requires PHP: 8.2
Tested up to: 6.7
Text Domain: madebysteph
License: GPL-2.0-or-later
*/Le fichier functions.php
Le functions.php reste léger : il charge les modules depuis inc/, déclare les supports de thème, et c’est tout. Pas de logique métier ici.
<?php
/**
* Bootstrap du thème.
*
* @package Madebysteph
*/
if ( ! defined( 'ABSPATH' ) ) exit;
$modules = array(
'inc/setup.php',
'inc/enqueue.php',
'inc/security.php',
'inc/post-types.php',
'inc/acf-options.php',
);
foreach ( $modules as $module ) {
require_once get_template_directory() . '/' . $module;
}Theme supports dans inc/setup.php
Les déclarations add_theme_support() vivent dans leur propre module. Cela centralise les capacités du thème : balises title auto, thumbnails, formats Gutenberg, nav menus.
<?php
add_action( 'after_setup_theme', 'mbs_theme_setup' );
function mbs_theme_setup(): void {
add_theme_support( 'title-tag' );
add_theme_support( 'post-thumbnails' );
add_theme_support( 'html5', array( 'search-form', 'gallery', 'caption', 'script', 'style' ) );
add_theme_support( 'responsive-embeds' );
add_theme_support( 'align-wide' );
add_theme_support( 'custom-logo', array( 'height' => 60, 'flex-width' => true ) );
register_nav_menus( array(
'primary' => __( 'Menu principal', 'madebysteph' ),
'footer' => __( 'Menu footer', 'madebysteph' ),
) );
add_image_size( 'card-medium', 800, 500, true );
add_image_size( 'hero-large', 1920, 1080, true );
}Intégration de Tailwind CSS 4
Tailwind CSS 4 apporte un nouveau moteur (Oxide, écrit en Rust) et une configuration CSS-first. Fini le tailwind.config.js verbeux — tout se configure directement dans le CSS via la directive @theme. Build jusqu’à 10× plus rapide, scan automatique du code source, zero config pour démarrer.
Installation
Initialisez Node.js à la racine du thème puis installez Tailwind 4 avec PostCSS.
# À la racine du thème
npm init -y
npm install -D tailwindcss @tailwindcss/postcss postcss postcss-cliScripts npm
Deux scripts suffisent : dev pour le watch, build pour le build de production minifié.
{"scripts":{"dev":"tailwindcss -i .\/assets\/css\/source.css -o .\/assets\/css\/tailwind.css --watch","build":"tailwindcss -i .\/assets\/css\/source.css -o .\/assets\/css\/tailwind.css --minify"}}Fichier CSS source avec @theme
Toute la configuration tokens (couleurs, polices, espacements) passe par la directive @theme. Les tokens deviennent automatiquement des utilitaires Tailwind et des variables CSS consommables.
@import "tailwindcss";
@theme {
--color-primary: #34d399;
--color-dark: #050505;
--font-display: "Orbitron", sans-serif;
--font-body: "DM Sans", sans-serif;
--font-mono: "JetBrains Mono", monospace;
--breakpoint-xl: 1280px;
}
/* Utilitaires custom via @utility */
@utility glow-emerald {
text-shadow: 0 0 18px rgba(52,211,153,0.55), 0 0 40px rgba(52,211,153,0.35);
}Enqueue côté WordPress
Charger le build Tailwind et — surtout — dégager les CSS core inutiles (libère ~50 KB sur chaque page).
add_action( 'wp_enqueue_scripts', 'mbs_enqueue_assets' );
function mbs_enqueue_assets(): void {
$css_path = get_template_directory() . '/assets/css/tailwind.css';
wp_enqueue_style(
'mbs-tailwind',
get_template_directory_uri() . '/assets/css/tailwind.css',
array(),
file_exists( $css_path ) ? filemtime( $css_path ) : '2.0.0'
);
}
// Désactive les CSS core non utilisés (libère ~50 KB)
add_action( 'wp_enqueue_scripts', function (): void {
wp_dequeue_style( 'wp-block-library' );
wp_dequeue_style( 'wp-block-library-theme' );
wp_dequeue_style( 'classic-theme-styles' );
wp_dequeue_style( 'global-styles' );
}, 100 );Tailwind CSS 4 n’utilise plus tailwind.config.js. Si vous migrez depuis v3, supprimez-le et déplacez la config dans @theme. Le scan des classes est automatique (tout fichier .php, .html, .js du projet).
Configuration d’ACF Pro
ACF Pro est la clé pour rendre chaque contenu administrable sans toucher au code. Trois piliers à configurer : le sync JSON (versionner les champs dans Git), les options pages (contenus globaux : header, footer, coordonnées), et les blocs ACF pour enrichir Gutenberg.
Activer le sync JSON
Le sync JSON exporte automatiquement chaque groupe de champs dans acf-json/ à la sauvegarde. Vos champs deviennent versionnables, déployables via Git et synchronisables entre environnements (local/staging/prod).
add_filter( 'acf/settings/save_json', function (): string {
return get_template_directory() . '/acf-json';
} );
add_filter( 'acf/settings/load_json', function ( array $paths ): array {
$paths[] = get_template_directory() . '/acf-json';
return $paths;
} );
// Options page globale (hooké sur acf/init pour éviter les notices textdomain)
add_action( 'acf/init', function (): void {
if ( ! function_exists( 'acf_add_options_page' ) ) return;
acf_add_options_page( array(
'page_title' => __( 'Options du thème', 'madebysteph' ),
'menu_title' => __( 'Options thème', 'madebysteph' ),
'menu_slug' => 'theme-options',
'capability' => 'manage_options',
'icon_url' => 'dashicons-admin-customizer',
'redirect' => false,
) );
} );Le dossier acf-json/ doit être créé manuellement à la racine du thème. ACF y dépose automatiquement les fichiers JSON à chaque sauvegarde de groupe de champs. Ces fichiers peuvent être commités dans Git sans risque.
Lire un champ ACF dans un template
Pattern à respecter : get_field() dans le template, escaping systématique à la sortie, check d’existence avant d’afficher.
<?php
$titre = get_field( 'hero_titre' );
$sous_titre = get_field( 'hero_sous_titre' );
$cta_url = get_field( 'hero_cta_url' );
$cta_label = get_field( 'hero_cta_label' );
?>
<section class="relative py-32">
<h1 class="font-display text-6xl font-extrabold">
<?php echo esc_html( $titre ); ?>
</h1>
<?php if ( $sous_titre ) : ?>
<p class="text-neutral-400 mt-4"><?php echo esc_html( $sous_titre ); ?></p>
<?php endif; ?>
<?php if ( $cta_url && $cta_label ) : ?>
<a href="<?php echo esc_url( $cta_url ); ?>" class="btn-primary">
<?php echo esc_html( $cta_label ); ?>
</a>
<?php endif; ?>
</section>Custom Post Types et taxonomies
Pour un portfolio, il est indispensable de créer un CPT projet avec des taxonomies type_projet et technologie. Cela permet aux projets d’être filtrables, archivables et indexables séparément des articles de blog standards.
add_action( 'init', function (): void {
register_post_type( 'projet', array(
'labels' => array(
'name' => __( 'Projets', 'madebysteph' ),
'singular_name' => __( 'Projet', 'madebysteph' ),
'add_new_item' => __( 'Ajouter un projet', 'madebysteph' ),
),
'public' => true,
'has_archive' => true,
'rewrite' => array( 'slug' => 'projets', 'with_front' => false ),
'menu_icon' => 'dashicons-portfolio',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt', 'revisions' ),
'show_in_rest' => true, // Gutenberg
) );
register_taxonomy( 'type_projet', 'projet', array(
'labels' => array( 'name' => __( 'Types', 'madebysteph' ) ),
'hierarchical' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'type' ),
) );
register_taxonomy( 'technologie', 'projet', array(
'labels' => array( 'name' => __( 'Technologies', 'madebysteph' ) ),
'hierarchical' => false,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'techno' ),
) );
} );Templates WordPress : la hiérarchie
WordPress applique une hiérarchie stricte de templates pour choisir quel fichier PHP rendre une requête donnée. Connaître cette hiérarchie évite les hacks et permet de surcharger finement chaque cas. Pour un CPT projet, l’ordre est : single-projet.php → single.php → singular.php → index.php.
header.php minimal
<!DOCTYPE html>
<html <?php language_attributes(); ?> class="scroll-smooth">
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<nav id="navbar" class="fixed top-0 inset-x-0 z-40">
<div class="max-w-7xl mx-auto px-6 py-5 flex items-center justify-between">
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" class="font-display font-bold">
<?php bloginfo( 'name' ); ?>
</a>
<?php wp_nav_menu( array(
'theme_location' => 'primary',
'container' => false,
'items_wrap' => '%3$s',
'depth' => 1,
) ); ?>
</div>
</nav>
<main>single.php avec template-part
Le détail d’un article : loop WP standard, post_class() pour les classes auto, the_content() pour injecter les blocs.
<?php get_header(); ?>
<?php while ( have_posts() ) : the_post(); ?>
<article <?php post_class( 'max-w-3xl mx-auto px-6 py-24' ); ?>>
<header class="mb-10">
<h1 class="font-display text-5xl font-extrabold"><?php the_title(); ?></h1>
<div class="mt-4 text-sm text-neutral-500">
<?php echo esc_html( get_the_date() ); ?>
· <?php the_category( ', ' ); ?>
</div>
</header>
<?php if ( has_post_thumbnail() ) : ?>
<figure class="mb-10"><?php the_post_thumbnail( 'hero-large' ); ?></figure>
<?php endif; ?>
<div class="prose-article">
<?php the_content(); ?>
</div>
</article>
<?php endwhile; ?>
<?php get_footer(); ?>Performance et Core Web Vitals
Google classe trois métriques critiques : LCP (Largest Contentful Paint) doit être < 2.5s, CLS (Cumulative Layout Shift) < 0.1, et INP (Interaction to Next Paint) < 200ms. Un thème correctement architecturé atteint ces cibles sans effort — il suffit de ne pas saboter le navigateur.
- Lazy loading natif :
loading="lazy"sur toutes les images below-the-fold et les iframes. - Preload des fonts critiques :
<link rel="preload" as="font" crossorigin>pour la première font utilisée en hero. - Purge CSS automatique : Tailwind 4 scanne et purge tout seul, le build final ne contient que les classes utilisées.
- Images responsives modernes :
srcset+sizes+ format WebP/AVIF viawp_get_attachment_image(). - Cache HTTP côté serveur : headers
Cache-Control,ETag, plugin type LiteSpeed Cache ou WP Rocket. - Éviter les layout shifts : dimensions fixes sur images +
font-display: swap+ aspect-ratio CSS.
Désactiver les scripts WP inutiles
// Supprime les emojis WordPress (gain ~4 KB + 1 requête)
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
// Supprime les liens oEmbed REST
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
remove_action( 'wp_head', 'rest_output_link_wp_head' );
// Désactive XML-RPC (surface d'attaque, rarement utile en 2026)
add_filter( 'xmlrpc_enabled', '__return_false' );
add_filter( 'wp_headers', function ( array $headers ): array {
unset( $headers['X-Pingback'] );
return $headers;
} );
// Désactive les embeds WP
add_action( 'init', function (): void {
wp_deregister_script( 'wp-embed' );
} );JSON-LD structured data
Les données structurées permettent à Google d’afficher des rich snippets (date, auteur, image, note…) dans les résultats. Pour un article, le schéma BlogPosting est attendu.
add_action( 'wp_head', function (): void {
if ( ! is_singular( array( 'post', 'projet' ) ) ) return;
$post = get_queried_object();
$img = get_the_post_thumbnail_url( $post, 'hero-large' ) ?: '';
$data = array(
'@context' => 'https://schema.org',
'@type' => 'BlogPosting',
'headline' => get_the_title( $post ),
'description' => get_the_excerpt( $post ),
'image' => $img,
'datePublished' => get_the_date( 'c', $post ),
'dateModified' => get_the_modified_date( 'c', $post ),
'author' => array(
'@type' => 'Person',
'name' => get_the_author_meta( 'display_name', $post->post_author ),
),
'publisher' => array(
'@type' => 'Organization',
'name' => get_bloginfo( 'name' ),
),
'mainEntityOfPage' => get_permalink( $post ),
);
echo '<script type="application/ld+json">'
. wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE )
. '</script>';
} );Sécurité : les bases non-négociables
Un thème bien conçu applique les trois règles de base du WordPress security model : escaping en sortie, sanitization en entrée, nonces pour les actions. Aucune excuse pour les oublier.
esc_html()pour du texte affiché dans le DOMesc_attr()pour un attribut HTMLesc_url()pour une URL (href, src)wp_kses_post()pour un contenu riche (WYSIWYG)sanitize_text_field()/absint()sur les inputs utilisateurwp_nonce_field()+check_admin_referer()sur toute action sensible
// Masque la version WP dans les headers HTML et RSS
remove_action( 'wp_head', 'wp_generator' );
add_filter( 'the_generator', '__return_empty_string' );
// Force SSL en prod
if ( ! is_admin() && ! wp_is_json_request() ) {
add_action( 'template_redirect', function (): void {
if ( ! is_ssl() && wp_get_environment_type() === 'production' ) {
wp_safe_redirect( 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 301 );
exit;
}
} );
}
// Security headers
add_action( 'send_headers', function (): void {
header( 'X-Content-Type-Options: nosniff' );
header( 'X-Frame-Options: SAMEORIGIN' );
header( 'Referrer-Policy: strict-origin-when-cross-origin' );
header( 'Permissions-Policy: camera=(), microphone=(), geolocation=()' );
} );Conclusion
Développer un thème WordPress sur mesure demande un investissement initial plus important qu’utiliser un thème pré-fait, mais les bénéfices sont réels et durables : performance optimale (Lighthouse 95+ reproductible), code maintenable (modules < 200 lignes), contenu 100 % administrable via ACF, et expérience utilisateur soignée sans dette technique.
L’association WordPress 6.6+ · PHP 8.2+ · Tailwind CSS 4 · ACF Pro forme un stack solide et éprouvé pour 2026. La clé est de rester discipliné : architecture claire, séparation des responsabilités, tests à chaque étape, et versionnement Git avec sync ACF JSON. Une fois ces habitudes prises, vous livrez un thème de qualité en quelques jours plutôt qu’en semaines.
Prochaines étapes possibles : blocs ACF Gutenberg custom (hero, stats, timeline), intégration WP-CLI pour les déploiements, pipeline GitHub Actions pour le build Tailwind + déploiement FTP/rsync automatique, tests visuels avec Playwright.
Le meilleur thème WordPress est celui que vous comprenez de bout en bout. Pas de magie noire, pas de dépendances inutiles — juste du code propre et intentionnel.