Pourquoi créer votre propre thème WordPress
Les thèmes pré-faits sont pratiques mais limités : code surchargé, dépendance à un page builder, personnalisation restreinte, et des dizaines de milliers de sites qui ressemblent au vôtre. Créer un thème from scratch vous donne un contrôle total sur le code, de meilleures performances (pas de bloat), et une compréhension profonde de WordPress qui fait de vous un meilleur développeur.
La structure minimale d’un thème WordPress
Un thème WordPress fonctionnel nécessite seulement 2 fichiers. Créez un dossier dans wp-content/themes/montheme/ :
montheme/
├── style.css (obligatoire : métadonnées du thème)
├── index.php (obligatoire : template par défaut)
├── functions.php (configuration et fonctionnalités)
├── header.php (en-tête HTML commun)
├── footer.php (pied de page commun)
├── single.php (template article)
├── page.php (template page)
├── archive.php (template archives/catégories)
├── 404.php (page erreur 404)
├── sidebar.php (barre latérale)
├── screenshot.png (aperçu 1200×900 dans le dashboard)
├── css/
│ └── custom.css (vos styles)
└── js/
└── main.js (vos scripts)
Étape 1 : style.css — la carte d’identité du thème
/*
Theme Name: MonThème
Theme URI: https://monsite.com
Author: Votre Nom
Author URI: https://monsite.com
Description: Thème WordPress personnalisé, léger et performant
Version: 1.0.0
Requires at least: 6.0
Tested up to: 6.5
Requires PHP: 8.0
License: GNU General Public License v2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: montheme
Tags: custom, lightweight, responsive
*/
Ce commentaire CSS est lu par WordPress pour afficher les infos du thème dans Apparence → Thèmes.
Étape 2 : functions.php — le cerveau du thème
<?php
/**
* Configuration du thème MonThème
*/
// Support des fonctionnalités WordPress
function montheme_setup() {
// Support du titre dynamique dans <title>
add_theme_support('title-tag');
// Support des images mises en avant
add_theme_support('post-thumbnails');
// Tailles d'images personnalisées
add_image_size('card-thumbnail', 400, 300, true);
add_image_size('hero-image', 1600, 900, true);
// Support du logo personnalisé
add_theme_support('custom-logo', array(
'height' => 60,
'width' => 200,
'flex-width' => true,
'flex-height' => true,
));
// Menus de navigation
register_nav_menus(array(
'primary' => 'Menu Principal',
'footer' => 'Menu Footer',
));
// Support HTML5
add_theme_support('html5', array(
'search-form', 'comment-form', 'comment-list',
'gallery', 'caption', 'style', 'script'
));
// Support de l'éditeur Gutenberg
add_theme_support('align-wide');
add_theme_support('responsive-embeds');
add_theme_support('editor-styles');
}
add_action('after_setup_theme', 'montheme_setup');
// Charger les styles et scripts
function montheme_enqueue_assets() {
// Style principal
wp_enqueue_style(
'montheme-style',
get_stylesheet_uri(),
array(),
filemtime(get_stylesheet_directory() . '/style.css')
);
// CSS personnalisé
wp_enqueue_style(
'montheme-custom',
get_template_directory_uri() . '/css/custom.css',
array('montheme-style'),
filemtime(get_template_directory() . '/css/custom.css')
);
// JavaScript (dans le footer)
wp_enqueue_script(
'montheme-main',
get_template_directory_uri() . '/js/main.js',
array(),
filemtime(get_template_directory() . '/js/main.js'),
true
);
}
add_action('wp_enqueue_scripts', 'montheme_enqueue_assets');
// Zone de widgets (sidebar)
function montheme_widgets() {
register_sidebar(array(
'name' => 'Sidebar',
'id' => 'sidebar-1',
'before_widget' => '<div class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
));
register_sidebar(array(
'name' => 'Footer',
'id' => 'footer-1',
'before_widget' => '<div class="footer-widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h4 class="footer-widget-title">',
'after_title' => '</h4>',
));
}
add_action('widgets_init', 'montheme_widgets');
Étape 3 : header.php — l’en-tête commun
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<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(); ?>
<header class="site-header">
<div class="container">
<div class="header-inner">
<!-- Logo -->
<div class="site-branding">
<?php if (has_custom_logo()) : ?>
<?php the_custom_logo(); ?>
<?php else : ?>
<a href="<?php echo home_url('/'); ?>" class="site-title">
<?php bloginfo('name'); ?>
</a>
<?php endif; ?>
</div>
<!-- Navigation -->
<nav class="main-nav">
<?php wp_nav_menu(array(
'theme_location' => 'primary',
'container' => false,
'menu_class' => 'nav-menu',
'fallback_cb' => false,
)); ?>
</nav>
<!-- Bouton menu mobile -->
<button class="menu-toggle" aria-label="Menu">
<span></span>
<span></span>
<span></span>
</button>
</div>
</div>
</header>
<main class="site-main">
Étape 4 : footer.php — le pied de page
</main><!-- .site-main -->
<footer class="site-footer">
<div class="container">
<div class="footer-grid">
<div class="footer-col">
<h4><?php bloginfo('name'); ?></h4>
<p><?php bloginfo('description'); ?></p>
</div>
<div class="footer-col">
<h4>Navigation</h4>
<?php wp_nav_menu(array(
'theme_location' => 'footer',
'container' => false,
'menu_class' => 'footer-menu',
'depth' => 1,
)); ?>
</div>
<div class="footer-col">
<h4>Contact</h4>
<p>Dakar, Sénégal<br>
<a href="tel:+221770000000">+221 77 000 00 00</a><br>
<a href="mailto:contact@monsite.com">contact@monsite.com</a></p>
</div>
<?php if (is_active_sidebar('footer-1')) : ?>
<div class="footer-col">
<?php dynamic_sidebar('footer-1'); ?>
</div>
<?php endif; ?>
</div>
<div class="footer-bottom">
<p>© <?php echo date('Y'); ?> <?php bloginfo('name'); ?>. Tous droits réservés.</p>
</div>
</div>
</footer>
<?php wp_footer(); ?>
</body>
</html>
Étape 5 : index.php — le template principal
<?php get_header(); ?>
<div class="container">
<div class="content-area">
<?php if (have_posts()) : ?>
<div class="posts-grid">
<?php while (have_posts()) : the_post(); ?>
<article <?php post_class('post-card'); ?>>
<?php if (has_post_thumbnail()) : ?>
<a href="<?php the_permalink(); ?>" class="post-card-image">
<?php the_post_thumbnail('card-thumbnail'); ?>
</a>
<?php endif; ?>
<div class="post-card-content">
<div class="post-card-meta">
<span class="post-category">
<?php the_category(', '); ?>
</span>
<time datetime="<?php the_time('c'); ?>">
<?php the_time('j F Y'); ?>
</time>
</div>
<h2 class="post-card-title">
<a href="<?php the_permalink(); ?>">
<?php the_title(); ?>
</a>
</h2>
<p class="post-card-excerpt">
<?php echo wp_trim_words(get_the_excerpt(), 20); ?>
</p>
</div>
</article>
<?php endwhile; ?>
</div>
<!-- Pagination -->
<div class="pagination">
<?php the_posts_pagination(array(
'mid_size' => 2,
'prev_text' => '← Précédent',
'next_text' => 'Suivant →',
)); ?>
</div>
<?php else : ?>
<p>Aucun article trouvé.</p>
<?php endif; ?>
</div>
</div>
<?php get_footer(); ?>
Étape 6 : single.php — template article
<?php get_header(); ?>
<div class="container">
<div class="content-with-sidebar">
<article <?php post_class('single-post'); ?>>
<header class="post-header">
<div class="post-category"><?php the_category(', '); ?></div>
<h1><?php the_title(); ?></h1>
<div class="post-meta">
<span>Par <?php the_author(); ?></span>
<time datetime="<?php the_time('c'); ?>">
<?php the_time('j F Y'); ?>
</time>
<span><?php echo ceil(str_word_count(get_the_content()) / 200); ?> min de lecture</span>
</div>
</header>
<?php if (has_post_thumbnail()) : ?>
<div class="post-featured-image">
<?php the_post_thumbnail('hero-image'); ?>
</div>
<?php endif; ?>
<div class="post-content">
<?php the_content(); ?>
</div>
<!-- Navigation articles -->
<nav class="post-navigation">
<div class="nav-previous">
<?php previous_post_link('%link', '← %title'); ?>
</div>
<div class="nav-next">
<?php next_post_link('%link', '%title →'); ?>
</div>
</nav>
<?php comments_template(); ?>
</article>
<aside class="sidebar">
<?php get_sidebar(); ?>
</aside>
</div>
</div>
<?php get_footer(); ?>
Étape 7 : Le CSS de base
Créez css/custom.css avec une base solide :
/* Reset et variables */
:root {
--primary: #2563EB;
--primary-hover: #1D4ED8;
--text: #1F2937;
--text-light: #6B7280;
--bg: #FFFFFF;
--bg-alt: #F9FAFB;
--border: #E5E7EB;
--radius: 8px;
--container: 1200px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', -apple-system, sans-serif;
font-size: 1rem;
line-height: 1.6;
color: var(--text);
background: var(--bg);
}
img { max-width: 100%; height: auto; display: block; }
a { color: var(--primary); text-decoration: none; }
a:hover { color: var(--primary-hover); }
.container { max-width: var(--container); margin: 0 auto; padding: 0 24px; }
/* Header */
.site-header {
background: white;
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 100;
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 72px;
}
.site-title { font-size: 1.25rem; font-weight: 800; color: var(--text); }
.nav-menu { display: flex; list-style: none; gap: 32px; }
.nav-menu a { color: var(--text); font-weight: 500; font-size: 0.9375rem; }
.nav-menu a:hover { color: var(--primary); }
/* Grille d'articles */
.posts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 32px;
padding: 48px 0;
}
.post-card {
background: white;
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
transition: box-shadow 0.2s;
}
.post-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
.post-card-content { padding: 20px; }
.post-card-title { font-size: 1.125rem; margin: 8px 0; }
.post-card-title a { color: var(--text); }
.post-card-excerpt { color: var(--text-light); font-size: 0.9375rem; }
/* Article single */
.single-post { max-width: 780px; }
.post-header h1 { font-size: 2.5rem; line-height: 1.2; margin: 12px 0 16px; }
.post-content { margin-top: 32px; }
.post-content h2 { margin-top: 48px; margin-bottom: 16px; font-size: 1.75rem; }
.post-content h3 { margin-top: 32px; margin-bottom: 12px; font-size: 1.375rem; }
.post-content p { margin-bottom: 16px; }
.post-content pre { background: #1F2937; color: #F9FAFB; padding: 24px; border-radius: var(--radius); overflow-x: auto; margin: 24px 0; }
.post-content code { font-family: 'JetBrains Mono', monospace; font-size: 0.875rem; }
/* Footer */
.site-footer { background: #111827; color: #D1D5DB; padding: 64px 0 24px; margin-top: 80px; }
.footer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 40px; }
.footer-bottom { margin-top: 48px; padding-top: 24px; border-top: 1px solid #374151; text-align: center; font-size: 0.875rem; }
/* Menu mobile */
.menu-toggle { display: none; }
@media (max-width: 768px) {
.menu-toggle { display: block; background: none; border: none; cursor: pointer; }
.menu-toggle span { display: block; width: 24px; height: 2px; background: var(--text); margin: 5px 0; }
.main-nav { display: none; }
.main-nav.active { display: block; position: absolute; top: 72px; left: 0; right: 0; background: white; border-bottom: 1px solid var(--border); padding: 24px; }
.main-nav.active .nav-menu { flex-direction: column; gap: 16px; }
.posts-grid { grid-template-columns: 1fr; }
.post-header h1 { font-size: 1.75rem; }
}
Étape 8 : JavaScript — menu mobile
Créez js/main.js :
// Menu mobile toggle
document.addEventListener('DOMContentLoaded', function() {
const toggle = document.querySelector('.menu-toggle');
const nav = document.querySelector('.main-nav');
if (toggle && nav) {
toggle.addEventListener('click', function() {
nav.classList.toggle('active');
this.classList.toggle('active');
});
}
});
Activer et tester votre thème
- Apparence → Thèmes → votre thème apparaît avec le screenshot
- Cliquez « Activer »
- Créez vos menus : Apparence → Menus → assignez aux emplacements « Menu Principal » et « Menu Footer »
- Testez toutes les pages : accueil, article, page, 404, archives
Aller plus loin
Ce thème de base est votre fondation. Vous pouvez l’enrichir avec :
- search.php : Template pour la page de résultats de recherche
- archive.php : Template pour les pages catégories et tags
- page-contact.php : Template personnalisé pour la page contact
- template-parts/ : Fragments réutilisables avec
get_template_part() - Customizer : Options personnalisables depuis Apparence → Personnaliser
- Support FSE : Ajoutez
templates/etparts/pour le Full Site Editing