Comment créer un thème WordPress personnalisé sans constructeur de pages

Créer un thème WordPress personnalisé sans constructeur de pages consiste à écrire ses modèles PHP, charger ses styles et utiliser les fonctions natives du CMS. Gutenberg reste disponible pour rédiger le contenu, tandis que le thème maîtrise la présentation.

Vous voulez comprendre ce que WordPress affiche, reprendre le contrôle du HTML et construire une interface adaptée à votre projet ? Un thème maison permet précisément cela. Encore faut-il assembler les fichiers dans le bon ordre et respecter les mécanismes du CMS.

Dans ce tutoriel, nous allons créer Sky Starter, un thème classique en PHP. Il affichera les articles, les pages, les archives, les résultats de recherche et les commentaires. Il disposera également d’un menu, d’une page 404 et d’une présentation responsive.

L’objectif dépasse le petit exemple qui fonctionne uniquement sur une page vide. Vous disposerez d’une base compréhensible, que vous pourrez adapter progressivement. Les exemples ciblent PHP 8.3 ou supérieur et WordPress 6.6 ou supérieur. Cette version minimale permet notamment d’utiliser la version 3 de theme.json.

Sans constructeur de pages : que garde-t-on dans WordPress ?

Un constructeur de pages et un thème répondent à des besoins différents. Le thème organise la présentation générale. L’éditeur sert à préparer le contenu. Ainsi, supprimer Elementor ou Divi Builder ne vous oblige pas à écrire chaque paragraphe en HTML.

Ici, nous conservons l’éditeur de blocs natif. Les auteurs pourront ajouter des titres, des images, des listes ou des boutons. Cependant, les modèles PHP détermineront l’en-tête, le pied de page et l’affichage des articles.

Vous rencontrerez principalement trois approches :

ApprocheFichiers principauxUsage
Thème classique personnaliséModèles PHP, CSS, functions.phpContrôle direct du rendu et apprentissage des API WordPress
Thème à blocs personnaliséModèles HTML de blocs, theme.jsonPersonnalisation du site avec l’éditeur de site
Thème enfantstyle.css, adaptations cibléesPersonnalisation d’un thème parent existant

Un thème à blocs permet aussi de travailler sans constructeur tiers. Nous choisissons ici l’approche classique, parce qu’elle rend visibles les étapes du rendu PHP. Ajouter theme.json à cette base ne la transforme pas automatiquement en thème à blocs.

Par ailleurs, un thème maison n’est pas systématiquement plus rapide. Une mauvaise requête, une image énorme ou des scripts inutiles peuvent annuler son avantage. Son intérêt réside surtout dans votre capacité à choisir exactement ce que vous chargez.

WPEngine: Premium Managed WooCommerce hosting

Préparer l’environnement de développement

Commencez sur une installation locale ou sur une préproduction. Vous pourrez modifier les fichiers, provoquer une erreur et la corriger sans interrompre les visiteurs. Pour choisir votre environnement, consultez le comparatif des serveurs locaux pour développer WordPress.

Préparez ensuite quelques contenus représentatifs : un article long, une page, une image mise en avant et deux catégories. Ajoutez également un article protégé par mot de passe et quelques commentaires. Ces exemples révèlent rapidement les oublis invisibles sur une installation vide.

Enfin, utilisez un éditeur de code et versionnez le dossier du thème. Vous gagnerez un historique précis de vos modifications. Une sauvegarde complète reste nécessaire avant de changer le thème d’un site existant.

Tous les fichiers de ce tutoriel appartiennent à un nouveau thème. Nous ne modifions ni le cœur de WordPress ni un thème commercial. Si vous souhaitez seulement adapter GeneratePress ou un autre thème existant, partez plutôt sur un thème enfant.

1. Créer l’arborescence minimale du thème

Dans wp-content/themes/, créez un dossier nommé sky-starter. Pour commencer, un thème classique peut être reconnu avec ces deux fichiers :

wp-content/themes/sky-starter/style.css
wp-content/themes/sky-starter/index.php

style.css contient l’en-tête d’identification du thème. De son côté, index.php sert de modèle de secours. Cette base suffit pour démarrer, mais elle ne couvre pas encore correctement tous les écrans.

Nous compléterons ensuite le dossier avec cette organisation :

Fichier ou dossierRôle
functions.phpDéclaration des fonctionnalités et chargement des ressources
header.phpEn-tête HTML, identité du site et menu
footer.phpPied de page et hooks de fermeture
index.phpListes et modèle de secours
single.phpArticle individuel
page.phpPage statique
comments.phpListe des commentaires et formulaire
404.phpPage introuvable
template-parts/content-excerpt.phpCarte d’article réutilisable
theme.jsonRéglages visuels partagés avec Gutenberg
assets/css/editor.cssAjustements propres à l’éditeur
languages/Fichiers de traduction éventuels

La documentation sur la structure des thèmes distingue bien les fichiers requis des fichiers complémentaires. Gardez cette distinction : un minimum technique n’est pas encore un thème prêt pour votre projet.

2. Déclarer le thème dans style.css

Placez cet en-tête au début de style.css :

/*
Theme Name: Sky Starter
Theme URI: https://www.skyminds.net/creer-theme-wordpress-personnalise-sans-constructeur/
Author: Matt Biscay
Author URI: https://www.skyminds.net/
Description: Thème pédagogique classique, léger et compatible avec Gutenberg.
Version: 1.0.0
Requires at least: 6.6
Requires PHP: 8.3
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: sky-starter
*/Langage du code : JSON / JSON avec commentaires (json)

WordPress lit ce commentaire pour afficher le thème dans l’administration. En revanche, il ne charge pas automatiquement toute la feuille dans les pages publiques : nous l’enregistrerons dans functions.php.

Le champ Text Domain identifie les chaînes traduisibles du thème. Il correspond ici au dossier sky-starter. Les champs Requires PHP et Requires at least annoncent les exigences techniques de cette base.

N’ajoutez pas de champ Template : il sert à identifier le parent d’un thème enfant. Nous construisons un thème autonome. Vous pouvez retrouver les détails des en-têtes dans la documentation officielle de style.css.

Kinsta: Premium Managed WordPress hosting

3. Afficher un premier contenu avec index.php

Avant de découper le thème, vérifiez que WordPress peut afficher une page. Utilisez temporairement ce premier index.php :

<?php
/**
 * Temporary standalone template for the first tutorial step.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
?>
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
	<meta charset="<?php bloginfo( 'charset' ); ?>">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<main>
	<h1><?php esc_html_e( 'Articles', 'sky-starter' ); ?></h1>
	<?php while ( have_posts() ) : ?>
		<?php the_post(); ?>
		<article <?php post_class(); ?>>
			<h2><?php echo esc_html( get_the_title() ); ?></h2>
			<?php the_content(); ?>
		</article>
	<?php endwhile; ?>
</main>
<?php wp_footer(); ?>
</body>
</html>Langage du code : PHP (php)

Ce modèle contient la boucle WordPress. have_posts() vérifie la présence de contenus, puis the_post() prépare l’article courant. Ensuite, the_content() affiche son contenu avec les traitements natifs.

Les appels wp_head(), wp_body_open() et wp_footer() constituent les points d’intégration du thème. Les extensions peuvent y ajouter des styles, des scripts ou d’autres éléments. Les omettre peut produire un site apparemment correct dont certaines fonctions ne marchent plus.

Activez le thème dans Apparence → Thèmes. Le rendu restera très simple, puisque nous n’avons pas encore chargé de CSS. À ce stade, vérifiez seulement l’affichage du titre et du contenu.

Ce fichier est provisoire. À l’étape 6, vous le remplacerez intégralement. Nous déplacerons alors l’en-tête et le pied de page dans leurs propres fichiers.

4. Configurer les fonctionnalités dans functions.php

Créez maintenant functions.php :

<?php
/**
 * Theme setup and assets.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Register theme features and navigation locations.
 *
 * @return void
 */
function sky_starter_setup(): void {
	load_theme_textdomain( 'sky-starter', get_template_directory() . '/languages' );
	add_theme_support( 'title-tag' );
	add_theme_support( 'post-thumbnails' );
	add_theme_support( 'responsive-embeds' );
	add_theme_support( 'align-wide' );
	add_theme_support( 'editor-styles' );
	add_editor_style( 'assets/css/editor.css' );
	add_theme_support(
		'html5',
		array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script' )
	);
	add_theme_support(
		'custom-logo',
		array(
			'height'      => 80,
			'width'       => 240,
			'flex-height' => true,
			'flex-width'  => true,
		)
	);
	register_nav_menus(
		array( 'primary' => __( 'Menu principal', 'sky-starter' ) )
	);
}
add_action( 'after_setup_theme', 'sky_starter_setup' );

/**
 * Load the stylesheet and threaded-comment script when needed.
 *
 * @return void
 */
function sky_starter_enqueue_assets(): void {
	$theme = wp_get_theme();

	wp_enqueue_style(
		'sky-starter-style',
		get_stylesheet_uri(),
		array(),
		(string) $theme->get( 'Version' )
	);

	if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
		wp_enqueue_script( 'comment-reply' );
	}
}
add_action( 'wp_enqueue_scripts', 'sky_starter_enqueue_assets' );Langage du code : HTML, XML (xml)

Le hook after_setup_theme déclenche la configuration du thème au bon moment. Nous y déclarons notamment les images mises en avant, le logo, les menus et les fonctions utiles à l’éditeur.

title-tag laisse WordPress gérer le titre du document. Vous évitez ainsi un <title> codé en dur, qui compliquerait l’intégration d’une extension SEO. Les autres déclarations indiquent les fonctionnalités prises en charge ; elles ne remplacent pas les modèles qui doivent les afficher.

Ensuite, wp_enqueue_scripts charge notre feuille de style. Le nom sky-starter-style constitue son identifiant. Sa version provient de l’en-tête du thème : incrémentez Version lorsque vous publiez une nouvelle version du CSS, puis purgez les caches concernés.

Cette méthode évite une vérification du système de fichiers à chaque requête. En développement, vous pouvez désactiver le cache dans les outils du navigateur. Pour une chaîne de déploiement plus avancée, vous pourrez utiliser des fichiers portant une empreinte de contenu.

Enfin, comment-reply se charge uniquement lorsque les commentaires imbriqués peuvent être utilisés. Aucun JavaScript personnalisé ne s’ajoute au thème. Les API de chargement des ressources permettent de conserver cette maîtrise.

5. Séparer l’en-tête et le pied de page

Créer header.php

Copiez ce code dans header.php :

<?php
/**
 * Site header.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
?>
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
	<meta charset="<?php bloginfo( 'charset' ); ?>">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<a class="skip-link" href="#main-content">
	<?php esc_html_e( 'Aller au contenu', 'sky-starter' ); ?>
</a>
<header class="site-header">
	<div class="site-container header-inner">
		<div class="site-branding">
			<?php if ( has_custom_logo() ) : ?>
				<?php the_custom_logo(); ?>
			<?php else : ?>
				<a class="site-title" href="<?php echo esc_url( home_url( '/' ) ); ?>">
					<?php echo esc_html( get_bloginfo( 'name' ) ); ?>
				</a>
			<?php endif; ?>
		</div>
		<?php if ( has_nav_menu( 'primary' ) ) : ?>
			<nav class="site-navigation" aria-label="<?php esc_attr_e( 'Menu principal', 'sky-starter' ); ?>">
				<?php
				wp_nav_menu(
					array(
						'theme_location' => 'primary',
						'container'      => false,
						'menu_class'     => 'menu',
						'fallback_cb'    => false,
						'depth'          => 2,
					)
				);
				?>
			</nav>
		<?php endif; ?>
	</div>
</header>Langage du code : JavaScript (javascript)

Nous utilisons un lien vers le contenu principal pour les personnes naviguant au clavier. Son ancre correspond à l’identifiant du futur élément <main>. Le CSS le rendra visible lorsqu’il reçoit le focus.

Le nom du site reste un lien, sans devenir automatiquement un titre H1. Chaque écran pourra ainsi réserver son titre principal à son propre contenu. Si vous configurez un logo, WordPress l’affichera à la place du nom.

Le menu dépend d’un emplacement nommé primary. Il apparaît uniquement lorsqu’un menu lui est affecté. Rendez-vous dans Apparence → Menus, créez votre navigation et cochez l’emplacement Menu principal.

Pour ce premier thème, les sous-menus restent visibles. Ce choix simplifie la navigation au clavier et évite un menu mobile dépendant d’un script. Une interface plus complexe demandera une conception adaptée, notamment pour les boutons et leurs états d’ouverture.

Créer footer.php

Copiez ensuite ce code dans footer.php :

<?php
/**
 * Site footer.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
?>
<footer class="site-footer">
	<div class="site-container">
		<p>
			&copy; <?php echo esc_html( wp_date( 'Y' ) ); ?>
			<?php echo esc_html( get_bloginfo( 'name' ) ); ?>
		</p>
	</div>
</footer>
<?php wp_footer(); ?>
</body>
</html>Langage du code : HTML, XML (xml)

L’année suit le fuseau horaire configuré dans WordPress grâce à wp_date(). Si vous souhaitez personnaliser davantage les dates, consultez le guide pour afficher une date française dans un thème WordPress.

Remarquez enfin la position de wp_footer(), juste avant la fermeture du document. Un thème peut perdre des interactions d’extensions simplement parce que cet appel manque. Ce n’est pas le genre de minimalisme que l’on cherche.

Kinsta: Premium Managed WordPress hosting

6. Construire les listes avec la boucle WordPress

Créez template-parts/content-excerpt.php :

<?php
/**
 * Post card used in listings.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
?>
<article id="post-<?php the_ID(); ?>" <?php post_class( 'post-card' ); ?>>
	<?php if ( has_post_thumbnail() && ! post_password_required() ) : ?>
		<div class="post-card-image">
			<?php the_post_thumbnail( 'medium_large' ); ?>
		</div>
	<?php endif; ?>
	<h2 class="entry-title">
		<a href="<?php the_permalink(); ?>"><?php echo esc_html( get_the_title() ); ?></a>
	</h2>
	<?php if ( 'post' === get_post_type() ) : ?>
		<p class="entry-meta">
			<time datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>">
				<?php echo esc_html( get_the_date() ); ?>
			</time>
		</p>
	<?php endif; ?>
	<div class="entry-summary"><?php the_excerpt(); ?></div>
</article>Langage du code : JavaScript (javascript)

Ce fragment contient la carte réutilisable d’un résultat. Il affiche une image, un titre lié, une date pour les articles et un extrait. L’image mise en avant reste masquée lorsqu’un mot de passe protège le contenu.

Nous utilisons the_post_thumbnail() pour conserver le traitement natif des images. Cela permet notamment à WordPress de fournir les dimensions et les variantes responsive disponibles. Évitez de remplacer cette fonction par une simple URL vers l’image originale.

Remplacez maintenant tout le contenu du premier index.php par ce modèle :

<?php
/**
 * Default listing template.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

get_header();
?>
<main id="main-content" class="site-main site-container" tabindex="-1">
	<header class="page-header">
		<?php if ( is_archive() ) : ?>
			<?php the_archive_title( '<h1>', '</h1>' ); ?>
			<?php the_archive_description( '<div class="archive-description">', '</div>' ); ?>
		<?php elseif ( is_search() ) : ?>
			<h1>
				<?php
				printf(
					/* translators: %s: Search query. */
					esc_html__( 'Résultats pour : %s', 'sky-starter' ),
					esc_html( get_search_query( false ) )
				);
				?>
			</h1>
		<?php else : ?>
			<h1><?php esc_html_e( 'Derniers articles', 'sky-starter' ); ?></h1>
		<?php endif; ?>
	</header>
	<?php if ( have_posts() ) : ?>
		<div class="post-grid">
			<?php
			while ( have_posts() ) {
				the_post();
				get_template_part( 'template-parts/content', 'excerpt' );
			}
			?>
		</div>
		<?php the_posts_pagination(); ?>
	<?php else : ?>
		<p><?php esc_html_e( 'Aucun contenu trouvé.', 'sky-starter' ); ?></p>
		<?php get_search_form(); ?>
	<?php endif; ?>
</main>
<?php get_footer(); ?>Langage du code : HTML, XML (xml)

La boucle utilise ici la requête principale déjà préparée par WordPress. Vous n’avez pas besoin d’un WP_Query supplémentaire pour afficher les articles de l’archive courante. Vous évitez ainsi une requête redondante et des problèmes de pagination.

Le titre s’adapte aux archives et aux recherches. Les listes utilisent des titres H2 sous le H1 de l’écran. Quant à the_posts_pagination(), elle s’appuie sur la requête principale pour construire les liens entre les pages.

N’utilisez pas query_posts() pour réorganiser cette liste. Si un projet exige de modifier la requête principale, faites-le dans une extension dédiée, avec pre_get_posts et des conditions précises. Une restriction métier ne devrait pas disparaître au prochain changement de thème.

7. Créer les modèles des articles et des pages

Afficher un article avec single.php

Ajoutez le fichier single.php :

<?php
/**
 * Single post template.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

get_header();
?>
<main id="main-content" class="site-main site-container" tabindex="-1">
	<?php while ( have_posts() ) : ?>
		<?php the_post(); ?>
		<article id="post-<?php the_ID(); ?>" <?php post_class( 'single-entry' ); ?>>
			<header class="entry-header">
				<h1><?php echo esc_html( get_the_title() ); ?></h1>
				<p class="entry-meta">
					<time datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>">
						<?php echo esc_html( get_the_date() ); ?>
					</time>
				</p>
			</header>
			<?php if ( has_post_thumbnail() && ! post_password_required() ) : ?>
				<div class="entry-image"><?php the_post_thumbnail( 'large' ); ?></div>
			<?php endif; ?>
			<div class="entry-content">
				<?php
				the_content();
				wp_link_pages();
				?>
			</div>
		</article>
		<?php
		the_post_navigation();
		if ( comments_open() || get_comments_number() ) {
			comments_template();
		}
		?>
	<?php endwhile; ?>
</main>
<?php get_footer(); ?>Langage du code : JavaScript (javascript)

Ce modèle affiche le contenu complet. the_content() applique les filtres attendus par WordPress, notamment ceux nécessaires au rendu des blocs. N’entourez pas cet appel de esc_html(), car vous transformeriez le HTML attendu en texte visible.

wp_link_pages() gère les articles découpés en plusieurs pages. Cette fonction complète the_posts_pagination(), qui pagine les listes d’articles. Les deux répondent donc à des situations différentes.

Nous laissons WordPress décider des attributs de chargement de l’image. Évitez d’imposer loading="lazy" à toutes les images : l’image principale peut être immédiatement visible et participer au LCP. Ajustez ce comportement après observation du rendu réel.

La référence de the_content() explique le mécanisme d’affichage. Vous trouverez aussi les paramètres des images dans la référence de the_post_thumbnail().

Afficher une page avec page.php

Créez ensuite page.php :

<?php
/**
 * Static page template.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

get_header();
?>
<main id="main-content" class="site-main site-container" tabindex="-1">
	<?php while ( have_posts() ) : ?>
		<?php the_post(); ?>
		<article id="post-<?php the_ID(); ?>" <?php post_class( 'single-entry' ); ?>>
			<header class="entry-header">
				<h1><?php echo esc_html( get_the_title() ); ?></h1>
			</header>
			<div class="entry-content">
				<?php
				the_content();
				wp_link_pages();
				?>
			</div>
		</article>
		<?php
		if ( comments_open() || get_comments_number() ) {
			comments_template();
		}
		?>
	<?php endwhile; ?>
</main>
<?php get_footer(); ?>Langage du code : JavaScript (javascript)

Une page conserve son titre et son contenu, sans afficher automatiquement une date de publication. Cette présentation convient à une page de contact, une présentation de services ou une page administrative.

Ajouter le fichier comments.php

Puisque les modèles appellent comments_template(), fournissons également le fichier correspondant :

<?php
/**
 * Comment list and form.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( post_password_required() ) {
	return;
}
?>
<section id="comments" class="comments-area" aria-label="<?php esc_attr_e( 'Commentaires', 'sky-starter' ); ?>">
	<?php if ( have_comments() ) : ?>
		<h2><?php esc_html_e( 'Commentaires', 'sky-starter' ); ?></h2>
		<ol class="comment-list">
			<?php
			wp_list_comments(
				array(
					'style'       => 'ol',
					'short_ping'  => true,
					'avatar_size' => 48,
				)
			);
			?>
		</ol>
		<?php the_comments_navigation(); ?>
	<?php endif; ?>
	<?php if ( ! comments_open() && get_comments_number() ) : ?>
		<p><?php esc_html_e( 'Les commentaires sont fermés.', 'sky-starter' ); ?></p>
	<?php endif; ?>
	<?php comment_form(); ?>
</section>Langage du code : HTML, XML (xml)

WordPress prend en charge le formulaire, la liste et la navigation des commentaires. Nous gardons ces fonctions natives plutôt que de reconstruire un traitement de formulaire dans le thème.

Par ailleurs, le contrôle du mot de passe intervient avant l’affichage des commentaires. Un thème doit tenir compte des protections du contenu, même dans les sections que l’on consulte moins souvent.

8. Comprendre la hiérarchie des modèles

WordPress choisit un modèle selon le type d’écran. Lorsqu’un fichier spécialisé manque, il cherche un fichier plus général. Pour les écrans courants de notre thème, retenez ces chaînes simplifiées :

ÉcranModèles recherchés, du plus spécialisé au plus général
Article standardsingle-post.php → single.php → singular.php → index.php
Page sans modèle attribuépage-{slug}.php → page-{id}.php → page.php → singular.php → index.php
Catégoriecategory-{slug}.php → category-{id}.php → category.php → archive.php → index.php
Recherchesearch.php → index.php
Liste des articleshome.php → index.php
Page introuvable404.php → index.php

La page d’accueil possède un cas particulier : front-page.php est prioritaire lorsqu’il existe. Ensuite, le comportement dépend du choix effectué dans Réglages → Lecture. Une page d’accueil statique et la page des articles ne désignent pas le même écran.

Pour garder ce tutoriel compact, nous utilisons page.php pour une page d’accueil statique et index.php pour les listes. Vous pourrez ajouter front-page.php plus tard, lorsque votre projet nécessitera un modèle d’accueil distinct.

Consultez la hiérarchie officielle des modèles pour les cas supplémentaires, notamment les types de contenus personnalisés et les modèles attribués aux pages.

Kinsta: Premium Managed WordPress hosting

9. Ajouter une page 404 utile

Créez 404.php :

<?php
/**
 * Not-found template.
 *
 * @package Sky_Starter
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

get_header();
?>
<main id="main-content" class="site-main site-container" tabindex="-1">
	<h1><?php esc_html_e( 'Cette page est introuvable', 'sky-starter' ); ?></h1>
	<p><?php esc_html_e( 'Essayez une recherche ou revenez à la page d’accueil.', 'sky-starter' ); ?></p>
	<?php get_search_form(); ?>
	<p>
		<a href="<?php echo esc_url( home_url( '/' ) ); ?>">
			<?php esc_html_e( 'Revenir à l’accueil', 'sky-starter' ); ?>
		</a>
	</p>
</main>
<?php get_footer(); ?>Langage du code : HTML, XML (xml)

Cette page propose une recherche et un retour vers l’accueil. Elle fournit une vraie sortie au visiteur, au lieu d’un simple message technique.

WordPress gère normalement le statut HTTP de la requête introuvable. Le modèle personnalise son affichage. Vérifiez toutefois ce statut sur votre installation : certaines extensions ou règles de redirection peuvent modifier le comportement attendu.

10. Harmoniser Gutenberg avec theme.json

Ajoutez le fichier theme.json à la racine du thème :

{
	"$schema": "https://schemas.wp.org/wp/6.6/theme.json",
	"version": 3,
	"settings": {
		"layout": {
			"contentSize": "46rem",
			"wideSize": "64rem"
		},
		"color": {
			"defaultPalette": false,
			"palette": [
				{ "slug": "accent", "name": "Bleu", "color": "#00579e" },
				{ "slug": "text", "name": "Texte", "color": "#17212b" },
				{ "slug": "white", "name": "Blanc", "color": "#ffffff" }
			]
		}
	},
	"styles": {
		"color": { "text": "var:preset|color|text", "background": "var:preset|color|white" },
		"typography": {
			"fontFamily": "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
			"lineHeight": "1.7"
		},
		"elements": {
			"link": { "color": { "text": "var:preset|color|accent" } }
		}
	}
}Langage du code : JSON / JSON avec commentaires (json)

Ce fichier fournit une palette et des réglages partagés avec l’éditeur. Les auteurs retrouvent ainsi les couleurs du projet au lieu d’une collection de choix sans rapport avec le site.

Le schéma cible WordPress 6.6 pour rester cohérent avec notre version minimale. Le fichier emploie la version 3 de la configuration. Ce réglage ne change pas la nature de nos modèles PHP : Sky Starter reste un thème classique.

Les largeurs déclarées servent de référence pour le contenu et les alignements larges. Le CSS public reprend ces dimensions. Dans cette base, un bloc pleine largeur reste contenu dans la zone principale du thème ; il ne déborde pas automatiquement jusqu’aux bords de la fenêtre.

Créez également assets/css/editor.css :

.editor-styles-wrapper {
	font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	line-height: 1.7;
}

.editor-styles-wrapper pre {
	padding: 1rem;
	background: #f5f7fa;
	overflow-x: auto;
}Langage du code : CSS (css)

Cette feuille ajuste l’expérience d’écriture. Elle n’importe pas les styles du menu, du pied de page ou des cartes d’articles, inutiles dans l’éditeur. La documentation de theme.json détaille les réglages disponibles.

11. Ajouter une feuille CSS responsive et légère

Ajoutez les règles suivantes après le commentaire d’en-tête de style.css :

:root {
	--sky-accent: #00579e;
	--sky-text: #17212b;
	--sky-muted: #526170;
	--sky-border: #d6dde5;
	--sky-surface: #f5f7fa;
	--sky-width: 72rem;
}

*,
*::before,
*::after {
	box-sizing: border-box;
}

body {
	margin: 0;
	color: var(--sky-text);
	background: #fff;
	font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
	font-size: 1.0625rem;
	line-height: 1.7;
}

img {
	display: block;
	max-inline-size: 100%;
	block-size: auto;
}

a {
	color: var(--sky-accent);
	text-underline-offset: 0.2em;
}

:where(a, button, input, textarea, select):focus-visible {
	outline: 3px solid var(--sky-accent);
	outline-offset: 4px;
}

:where(h1, h2, h3) {
	line-height: 1.2;
	overflow-wrap: anywhere;
}

h1 {
	font-size: clamp(2rem, 4vw, 3.25rem);
}

.site-container {
	inline-size: min(var(--sky-width), calc(100% - 2rem));
	margin-inline: auto;
}

.site-header,
.site-footer {
	background: var(--sky-surface);
}

.header-inner {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 1rem 2rem;
	padding-block: 1.25rem;
}

.site-branding {
	min-inline-size: 0;
	max-inline-size: 100%;
}

.site-title {
	font-size: 1.5rem;
	font-weight: 750;
	text-decoration: none;
	overflow-wrap: anywhere;
}

.custom-logo {
	max-inline-size: min(15rem, 100%);
}

.site-navigation .menu {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem 1.25rem;
	list-style: none;
	margin: 0;
	padding: 0;
}

.site-navigation a {
	display: inline-block;
	padding-block: 0.4rem;
	overflow-wrap: anywhere;
}

.site-navigation .sub-menu {
	list-style: none;
	margin: 0;
	padding-inline-start: 1rem;
	font-size: 0.9375rem;
}

.site-main {
	padding-block: clamp(2rem, 5vw, 4rem);
}

.post-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
	gap: 2rem;
}

.post-card {
	min-inline-size: 0;
	padding: 1.25rem;
	border: 1px solid var(--sky-border);
	border-radius: 0.75rem;
}

.post-card-image img {
	inline-size: 100%;
	aspect-ratio: 16 / 9;
	object-fit: cover;
}

.entry-meta {
	color: var(--sky-muted);
	font-size: 0.9375rem;
}

.single-entry {
	max-inline-size: 64rem;
	margin-inline: auto;
}

.entry-header,
.entry-content > * {
	max-inline-size: 46rem;
	margin-inline: auto;
}

.entry-content > .alignwide {
	max-inline-size: 64rem;
}

.entry-content > .alignfull {
	max-inline-size: none;
}

.entry-image {
	margin-block: 2rem;
}

.entry-image img {
	inline-size: 100%;
}

.entry-content :where(p, li, a),
.entry-summary {
	overflow-wrap: anywhere;
}

pre,
.wp-block-table {
	max-inline-size: 100%;
	overflow-x: auto;
}

pre {
	padding: 1rem;
	background: var(--sky-surface);
}

table {
	border-collapse: collapse;
}

th,
td {
	padding: 0.5rem;
	border: 1px solid var(--sky-border);
	text-align: start;
}

.site-main > .navigation,
.comments-area {
	margin-block-start: 2.5rem;
}

.comments-area {
	max-inline-size: 46rem;
	margin-inline: auto;
}

.comment-list,
.comment-list .children {
	padding-inline-start: 1.25rem;
}

input,
textarea,
select,
button {
	font: inherit;
}

input:not([type="checkbox"]):not([type="radio"]),
textarea {
	max-inline-size: 100%;
	padding: 0.65rem;
	border: 1px solid var(--sky-border);
	border-radius: 0.35rem;
}

textarea {
	inline-size: 100%;
}

input[type="submit"],
button {
	color: #fff;
	background: var(--sky-accent);
	cursor: pointer;
}

.site-footer {
	padding-block: 1.5rem;
}

.skip-link {
	position: absolute;
	inset-block-start: 1rem;
	inset-inline-start: 1rem;
	z-index: 1000;
	padding: 0.75rem 1rem;
	color: #fff;
	background: var(--sky-accent);
	transform: translateY(-200%);
}

.skip-link:focus {
	transform: translateY(0);
}

.screen-reader-text {
	position: absolute;
	inline-size: 1px;
	block-size: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.screen-reader-text:focus {
	z-index: 1000;
	inline-size: auto;
	block-size: auto;
	padding: 0.75rem;
	margin: 0;
	color: var(--sky-text);
	background: #fff;
	clip-path: none;
	white-space: normal;
}Langage du code : CSS (css)

Le thème utilise des propriétés personnalisées pour centraliser ses couleurs et ses dimensions. Vous pouvez donc changer l’accent visuel sans rechercher chaque valeur dans plusieurs fichiers.

La grille adapte le nombre de colonnes à la place disponible. De plus, les propriétés logiques, comme margin-inline, évitent de lier systématiquement la mise en page à la gauche ou à la droite.

Les polices système suppriment le téléchargement d’une famille typographique externe. Elles constituent un choix raisonnable pour cette base pédagogique. Si votre identité exige une autre police, chargez seulement les fichiers et les graisses nécessaires.

Nous conservons aussi un focus visible et le défilement horizontal des blocs de code. Ces détails comptent dans un tutoriel technique : un extrait PHP qui déborde de l’écran mobile n’aide personne.

Pour un rendu destiné au papier, vous pourrez ensuite ajouter une feuille dédiée en suivant le guide sur la création d’un CSS d’impression moderne.

12. Sécurité, performances et maintenance : les bons réflexes

Échapper les données selon leur contexte

Les exemples emploient esc_html() pour les valeurs textuelles, esc_attr() pour les attributs et esc_url() pour les URL construites. L’échappement intervient au moment de l’affichage.

Cependant, les fonctions natives destinées à afficher du HTML, comme the_content() ou wp_nav_menu(), suivent leur propre traitement. Leur appliquer aveuglément un échappement textuel détruirait le rendu attendu. La documentation WordPress sur l’échappement permet de choisir la bonne fonction.

Si vous ajoutez des actions personnalisées, contrôlez également les permissions et validez les entrées. Un nonce limite certains abus de requêtes ; il ne remplace pas current_user_can(). Aucun traitement personnalisé de ce type n’est nécessaire dans cette première version.

Garder les fonctionnalités métier dans une extension

Le thème gère la présentation. Placez les types de contenus, les restrictions d’accès et les intégrations métier dans une extension adaptée. Ainsi, ces fonctions survivront à une refonte.

Même logique pour les shortcodes qui produisent un contenu durable. Un changement de thème ne devrait pas laisser vos pages remplies de balises devenues inactives.

Mesurer les performances sur des pages représentatives

Vérifiez les ressources réellement chargées, la taille des images et les requêtes supplémentaires. Comparez des pages équivalentes, avec les mêmes contenus et un état de cache comparable. Un score isolé ne décrit pas toute l’expérience utilisateur.

Ne retirez pas globalement les styles des blocs WordPress pour gagner quelques octets. Vous risquez de casser des blocs utilisés dans les articles. Commencez par identifier les ressources inutiles avant de les désactiver.

Prévoir les traductions et les évolutions PHP

Les chaînes d’interface utilisent le domaine sky-starter. Pour créer des traductions, consultez le tutoriel sur PoEdit et les traductions des thèmes WordPress.

Ensuite, contrôlez régulièrement le comportement du thème sur les versions de PHP utilisées par vos hébergements. Le guide de compatibilité WordPress avec PHP 8 complète cette démarche.

13. Vérifier le thème avant sa mise en production

Testez une liste paginée, un article, une page, une catégorie, une recherche vide et une URL inexistante. Ensuite, ouvrez un contenu protégé et confirmez que son corps ne s’affiche pas avant la saisie du mot de passe.

Vérifiez aussi les images larges, les tableaux, les blocs Code, les boutons et les contenus multipages. Essayez des titres très longs. Ce sont généralement eux qui rappellent au développeur que ses données de démonstration étaient trop polies.

Au clavier, parcourez les liens et le menu. Le lien d’évitement doit apparaître et vous conduire au contenu. Sur mobile, contrôlez les débordements, la lisibilité et les éléments interactifs. Testez également le zoom à 200 %.

Sur votre machine de développement, vérifiez les fichiers PHP et les conventions de code :

find wp-content/themes/sky-starter -type f -name '*.php' -exec php -l {} \;

phpcs --standard=WordPress wp-content/themes/sky-starterLangage du code : JavaScript (javascript)

La première commande vérifie la syntaxe. La seconde nécessite PHP_CodeSniffer et WordPress Coding Standards, préalablement installés et configurés. Elle complète la relecture ; elle ne remplace pas un essai réel avec WordPress.

L’archive fournie constitue une base pédagogique. Avant de l’utiliser sur un site client, validez son rendu avec ses extensions, ses contenus et sa configuration. Elle ne fournit pas une intégration WooCommerce dédiée ni un ensemble de composants prêts pour toutes les boutiques.

Enfin, si vous remplacez un constructeur existant, examinez les pages concernées avant de supprimer son extension. Changer le thème ne convertit pas ses mises en page. Pour Elementor, consultez la procédure permettant de supprimer proprement Elementor après une migration.

Dépannage : les erreurs les plus fréquentes

SymptômeVérification prioritaire
Le thème n’apparaît pasEmplacement du dossier et présence de l’en-tête dans style.css
Le thème est signalé comme incompletPrésence de index.php et absence d’un champ Template erroné
Les styles ne changent pasEnregistrement de la feuille, incrément de version et caches
Le menu reste absentAffectation du menu à l’emplacement primary
Un article affiche seulement un extraitPrésence du bon single.php dans le thème actif
Une extension perd ses scriptsPrésence de wp_head() et wp_footer()
Le HTML des blocs apparaît en texteUsage incorrect de esc_html() autour du contenu

En cas d’erreur PHP, consultez les journaux de votre environnement local. Corrigez d’abord la première erreur signalée. Les messages suivants découlent parfois du même fichier mal fermé ou d’une fonction renommée.

FAQ : créer son thème WordPress sans constructeur

Peut-on créer un thème WordPress sans Elementor ni Divi ?

Oui. WordPress dispose des fonctions nécessaires pour afficher les contenus, les menus, les images et les commentaires. Un thème personnalisé peut les utiliser directement, tout en conservant l’éditeur de blocs pour la rédaction.

Quels fichiers sont indispensables pour un thème classique ?

La base technique repose sur style.css, avec son en-tête, et index.php. En pratique, ajoutez des fichiers spécialisés pour les pages, les articles et les éléments communs afin de construire un thème maintenable.

Faut-il désactiver Gutenberg pour développer un thème PHP ?

Non. Un thème classique fonctionne avec l’éditeur de blocs. Les fichiers PHP organisent les écrans, tandis que Gutenberg produit le contenu des articles et des pages. theme.json peut rapprocher leurs réglages visuels.

Un thème personnalisé est-il forcément plus rapide ?

Non. Il vous donne la possibilité de réduire les ressources, mais son résultat dépend du code et des contenus. Mesurez les images, les scripts, les requêtes et les temps de chargement avant de conclure.

Peut-on utiliser cette base pour WooCommerce ?

Cette base vise un site éditorial. Une boutique demande des validations supplémentaires : catalogue, fiches produits, panier, paiement et compte client. Prévoyez une intégration WooCommerce avant de l’utiliser pour un commerce en production.

Faire évoluer votre thème sans perdre sa simplicité

Vous disposez maintenant d’un thème autonome, dont chaque fichier répond à un rôle précis. Pour la suite, ajoutez seulement les modèles et les composants dont votre projet a besoin.

Une page d’accueil spécifique, des compositions Gutenberg ou une feuille d’impression peuvent constituer de bonnes évolutions. En revanche, une nouvelle fonctionnalité métier mérite généralement sa propre extension.

Sources et documentation

Laisser un commentaire