Aller au contenu

#La coquille

layout/theme.liquid est le HTML qui entoure toutes les pages du site : le doctype, la balise head, l’en-tête, le pied, et le trou dans lequel le contenu de la page vient tomber. C’est le seul fichier de layout/ que le moteur connaisse, et l’un des trois sans lesquels un thème ne rend rien.

Cette page le dissèque à partir de themes/origo/layout/theme.liquid, qui tient en quatre-vingt-dix-huit lignes et fait tout ce qu’une coquille doit faire.

#Le fichier entier

themes/origo/layout/theme.liquidliquid
<!doctype html>
{%- comment -%}
  ORIGO — la coquille.

  Les réglages deviennent des variables CSS, ici et nulle part ailleurs.
  C'est ce qui permet de tout retoucher depuis l'éditeur sans qu'une seule
  section ait à connaître une couleur : elles héritent toutes de `:root`.

  Deux choses ne PEUVENT PAS se dire avec une variable, et sortent donc en
  attribut sur `<html>` :

  · l'ÉDITION — les signatures d'un habillage (barre d'accent devant les
    titres, filet doublé sous l'en-tête, cadres, ombres, interlettrage)
    tiennent à des règles entières, pas à un nombre ;
  · rien d'autre. Tout le reste passe par `:root`, et c'est voulu : un
    attribut de plus, c'est une combinaison de plus à vérifier.

  Le thème NOMME une police, la plateforme la sert : `assets/` n'accepte
  que `.css` et `.svg`, donc aucun fichier de police ne peut y vivre.
  Quand aucune police n'est choisie, on retombe sur la pile système — un
  thème de départ doit rendre même vide.
{%- endcomment -%}
<html lang="fr" data-edition="{{ settings.edition | default: 'origo' }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ page.titre }} — {{ site.nom }}</title>
    <meta name="theme-color" content="{{ settings.papier | default: '#ffffff' }}">
    {{ 'theme.css' | asset_url | stylesheet_tag }}

    <style>
      {{ settings.police_titres | font_face }}
      {%- comment -%}
        Origo sort de sa boîte avec la MÊME police partout. Émettre deux
        fois la règle ferait télécharger le fichier une fois — les
        navigateurs dédoublonnent — mais l'écrirait deux fois dans le HTML
        de chaque page, ce qui se voit à la lecture de la source et donne
        l'air d'un thème mal réglé.
      {%- endcomment -%}
      {%- if settings.police_texte != settings.police_titres -%}
        {{ settings.police_texte | font_face }}
      {%- endif -%}
    </style>

    <style>
      {%- assign pile_titres = settings.police_titres | font_famille -%}
      {%- assign pile_texte = settings.police_texte | font_famille -%}
      :root {
        --encre: {{ settings.encre }};
        --papier: {{ settings.papier }};
        --voile: {{ settings.voile }};
        --accent: {{ settings.accent }};
        --accent-texte: {{ settings.accent_texte }};
        --accent-sombre: {{ settings.accent | teinte: -18 }};
        --accent-clair: {{ settings.accent | teinte: 88 }};
        --largeur: {{ settings.largeur }}px;
        --arrondi: {{ settings.arrondi }}px;
        --echelle: {{ settings.echelle }};
        --densite: {{ settings.densite }};

        /* L'épaisseur des filets. Un seul nombre pour tous les traits du
           thème : sans lui, épaissir la séparation de l'en-tête laisserait
           le pied et la FAQ en cheveu, et le site paraîtrait mal réglé. */
        --filet: {{ settings.filets | default: 1 }}px;

        /* La forme des boutons. `droit` vaut ZÉRO et non « presque zéro » :
           c'est la seule valeur qui se voit sur une vignette. */
        --bouton-rayon: {% case settings.boutons %}{% when 'droit' %}0px{% when 'doux' %}8px{% else %}999px{% endcase %};
        /* Les pastilles suivent les boutons — numéros d'étape, étiquettes de
           forfait, jetons. Un bouton carré au-dessus d'un numéro rond fait
           deux thèmes sur la même page. */
        --pastille: var(--bouton-rayon);

        --texte-police: {% if pile_texte != blank %}{{ pile_texte }}{% else %}ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif{% endif %};
        --titre-police: {% if pile_titres != blank %}{{ pile_titres }}{% else %}var(--texte-police){% endif %};
        --titre-casse: {% if settings.casse_titres == 'capitales' %}uppercase{% else %}none{% endif %};
        /* Un titre en capitales serré au crénage négatif se referme sur
           lui-même : la valeur change AVEC la casse, jamais séparément. */
        --titre-espace: {% if settings.casse_titres == 'capitales' %}0.005em{% else %}-0.022em{% endif %};
        --surtitre-casse: {% if settings.casse_surtitres == 'normale' %}none{% else %}uppercase{% endif %};
        /* L'interlettrage d'un surtitre suit sa casse, jamais l'inverse :
           les 0,12 em qui aèrent DES CAPITALES écartèlent un mot en bas de
           casse, qu'on lit alors lettre à lettre. */
        --surtitre-espace: {% if settings.casse_surtitres == 'normale' %}0.015em{% else %}0.12em{% endif %};
      }
    </style>
  </head>
  <body>
    <a class="saut" href="#contenu">Aller au contenu</a>

    {% section 'annonce' %}
    {% section 'entete' %}

    <main id="contenu">{{ content_for_layout }}</main>

    {% section 'pied' %}
  </body>
</html>
Note

La dissection qui suit ne reprend pas tout : elle s’arrête à {{ content_for_layout }}, au titre, à la feuille de style, aux variables CSS, au lien d’évitement et à {% section %}. Les filtres font_face et font_famille sont détaillés dans Filtres, et le réglage edition dans Ajouter un thème au catalogue.

#{{ content_for_layout }} — le seul point obligatoire

Le moteur rend d’abord toutes les sections du template, concatène leur HTML, puis rend la coquille avec cette chaîne dans content_for_layout. C’est la mécanique de Shopify, et elle a l’avantage de laisser la coquille décider où le contenu tombe et ce qui l’entoure.

Une coquille sans {{ content_for_layout }} rend une page sans contenu. Sans erreur, sans avertissement : l’en-tête et le pied s’affichent, le milieu est vide. Si une page paraît « ne rien afficher », c’est la première ligne à chercher.

Attention

content_for_header n’existe pas dans Webcosa. Chez Shopify, il injecte les balises de la plateforme dans le head. Ici il n’y a rien à injecter, et la variable n’est pas définie : elle rendra une chaîne vide, silencieusement, puisque le moteur tourne avec strictVariables désactivé. Ne le copie pas depuis un thème Shopify en croyant qu’il fait quelque chose.

#Le head

#Le titre

liquid
<title>{{ page.titre }} — {{ site.nom }}</title>

page.titre est le titre de la page courante, site.nom celui de l’entreprise. Les huit thèmes d’origine utilisent exactement cette forme. Voir Objets.

Sur le site en ligne, ce titre est remplacé : la plateforme retire le <title>, le <meta charset> et le viewport de la coquille et pose les siens dans le vrai <head> — le titre de référencement que le client a réglé, sinon « Page — Site ». Le tien sert à l’aperçu et aux outils qui lisent la coquille seule. Garde-le : il ne coûte rien et ne s’affiche jamais en double.

#Ce que la plateforme pose pour toi

Tout ce qui suit est émis par Webcosa sur chaque page de chaque site, quel que soit le thème. Ta coquille ne doit pas le répéter — deux descriptions, deux canoniques ou deux graphes se contredisent, et les moteurs choisissent alors au hasard.

BaliseCe que la plateforme y met
<title>le titre de référencement de la page, sinon « Page — Site »
meta descriptioncelle de la page, sinon celle du site, sinon le premier texte de la page (155 signes)
link rel="canonical"l’adresse absolue sur l’hôte principal du site — son domaine vérifié, sinon <slug>.webcosa.site
meta robotsindex, follow (avec max-image-preview:large), ou noindex si le client refuse l’indexation
og:*, twitter:*titre, description, adresse, et toujours une image : celle de la page, du site, la photo du bandeau, ou une carte fabriquée au nom du site
icônesl’icône réglée par le client, sinon ses initiales à la couleur d’accent du thème
application/ld+jsonun seul graphe : l’entreprise (LocalBusiness si elle a une adresse, Organization sinon), le site (WebSite), la page, son fil d’Ariane, la FAQ si une section en montre au moins deux questions, l’article sur le blog
robots.txt, sitemap.xmlpar site, sur l’hôte principal ; vides si le site est protégé, plan vide s’il refuse l’indexation. Les robots d’IA passent, sauf ceux que le client ferme depuis l’écran Agentique du CMS
llms.txtpar site : sa fiche (métier, adresse, téléphone, horaires), la date de sa dernière mise à jour, les questions-réponses que le client a validées dans l’écran Agentique, ses pages publiées et ses articles, aux adresses canoniques ; 404 si le site est protégé, masqué des moteurs ou sans page publiée

Ce qui reste à ta charge, et que la plateforme ne peut pas deviner : un seul <h1> par page, une hiérarchie de titres sans saut, un alt sur les images que tu poses toi-même (les photos de la médiathèque reçoivent celui que le client a écrit), des dimensions sur chaque <img>.

Astuce

La description de repli et le graphe lisent tes sections : le premier réglage de texte courant (texte, chapeau, intro, description…), les réglages de type image, les blocs question / reponse d’une FAQ, le logo et les adresses de profils (instagram, facebook…) de l’en-tête et du pied. Nomme tes réglages ainsi et ton thème en profite sans une ligne de plus.

#La feuille de style

liquid
{{ 'theme.css' | asset_url | stylesheet_tag }}

Deux filtres à la suite. asset_url transforme un nom de fichier en adresse servie, stylesheet_tag l’enveloppe dans une balise link. Le résultat, pour un thème d’identifiant abc… :

HTML renduhtml
<link rel="stylesheet" href="/theme-assets/abc123.../theme.css">
Danger

asset_url prend le nom seul, jamais le chemin. L’adresse produite est /theme-assets/<id-du-thème>/<nom> — un seul segment de nom — et c’est la route qui remet le préfixe assets/ pour interroger la base.

Écrire {{ 'assets/theme.css' | asset_url }} fabrique une adresse à deux segments qui ne correspond à aucune route. La feuille de style répond 404, et la page rend quand même : du HTML nu, mais du HTML. Un site sans CSS ressemble à un thème mal écrit, pas à une adresse fausse — c’est pour ça que le défaut a tenu longtemps, sur tous les sites à la fois.

Voir Assets pour le détail de la route et de son cache, et Filtres pour les autres filtres maison.

#Les réglages posés en variables CSS

C’est la technique des huit thèmes d’origine, et elle mérite d’être copiée : les réglages globaux deviennent des variables CSS sur :root, ici et nulle part ailleurs.

liquid
<style>
  :root {
    --encre: {{ settings.encre }};
    --accent: {{ settings.accent }};
    --accent-sombre: {{ settings.accent | teinte: -18 }};
    --largeur: {{ settings.largeur }}px;
  }
</style>

Aucune section n’a alors besoin de connaître une couleur : elles héritent toutes de :root, et la feuille de style dans assets/ reste un fichier CSS ordinaire, sans une ligne de Liquid.

Le filtre teinte évite de multiplier les réglages : teinte: -18 assombrit de 18 %, teinte: 88 éclaircit de 88 %. Sans lui, chaque nuance d’une couleur réglable devrait être un réglage de plus, et un panneau de vingt sélecteurs de couleur ne se remplit jamais.

Astuce

Les unités s’écrivent hors de l’expression : {{ settings.largeur }}px et non {{ settings.largeur | append: 'px' }}. Le réglage reste un nombre, donc comparable et utilisable dans un {% if %}, et le CSS reste lisible.

Une condition Liquid peut aussi produire directement une valeur CSS :

liquid
--bouton-rayon: {% case settings.boutons %}{% when 'droit' %}0px{% when 'doux' %}8px{% else %}999px{% endcase %};

C’est ce que fait origo pour la forme des boutons, la police des titres et la casse des surtitres. Un select dans le schéma, une variable CSS dans la coquille, et toute la feuille de style suit.

#Le body

#Le lien d’évitement

liquid
<a class="saut" href="#contenu">Aller au contenu</a>

Premier élément focalisable de la page, visuellement masqué jusqu’à ce qu’il reçoive le focus. Les huit thèmes le posent, et l’ancre #contenu correspond au <main id="contenu"> plus bas. Ce n’est pas une exigence du format, c’est une exigence d’accessibilité — et il coûte une ligne.

#{% section %} — l’en-tête et le pied

liquid
{% section 'annonce' %}
{% section 'entete' %}

<main id="contenu">{{ content_for_layout }}</main>

{% section 'pied' %}

Le tag {% section 'nom' %} rend sections/nom.liquid depuis la coquille, c’est-à-dire hors de tout template. Sans lui, il faudrait écrire l’en-tête et le pied en dur ici, donc les sortir de l’éditeur et du schéma : le client ne pourrait plus changer son numéro de téléphone sans toucher au code.

Ce que la section reçoit :

section.idchaîne

Le nom passé au tag. {% section 'entete' %} donne entete.

section.typechaîne

Le même nom. Pour une section de coquille, id et type sont identiques — il n’y a qu’une instance par nom.

section.settingsobjet

Les valeurs lues dans config/settings_data.json, sous current.sections.<nom>.settings, par-dessus les default du schéma de la section.

section.blockstableau

Les blocs lus sous current.sections.<nom>.blocks, ou un tableau vide.

Attention

Pour une section de coquille, blocks est passé tel quel : ce doit donc être un tableau d’objets { "type": …, "settings": … } dans settings_data.json, et non l’objet indexé par identifiant qu’utilisent les templates. C’est la seule asymétrie du format, et elle n’est écrite nulle part ailleurs. Voir Les blocs.

Une section absente ne casse rien : le tag rend un commentaire HTML <!-- section absente : nom -->. Une section qui lève une erreur rend <!-- section nom : message -->. Et l’imbrication est bornée à six niveaux, compteur hors de portée du thème — détails dans Le bac à sable.

#Ce que la coquille voit, et ce qu’elle ne voit pas

La coquille est rendue avec le contexte complet : site, page, settings, menu, articles, annee, theme_base, plus content_for_layout.

Elle ne voit pas section — il n’y a pas de section en cours à ce niveau. {{ section.settings.titre }} dans la coquille rend du vide.

Note

Un {% schema %} dans layout/theme.liquid casse la page : schema n’est pas un tag reconnu, et le fichier est rendu tel quel, sans que le bloc ne soit retiré comme il l’est pour les sections. L’erreur remontée est tag "schema" not found. La coquille n’a pas de réglages propres — ceux dont elle a besoin sont les réglages globaux.

#Une coquille minimale qui fonctionne

Pour démarrer, ou pour isoler un problème :

layout/theme.liquidliquid
<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ page.titre }} — {{ site.nom }}</title>
    {{ 'theme.css' | asset_url | stylesheet_tag }}
  </head>
  <body>
    {{ content_for_layout }}
  </body>
</html>

#Les erreurs qui se voient le moins

content_for_layout oublié

La page s’affiche, l’en-tête et le pied aussi, et le contenu n’est nulle part. Aucune erreur.

asset_url avec le chemin complet

Le site rend en HTML nu. La page fonctionne, elle est juste sans style. À vérifier dans l’onglet réseau : un 404 sur /theme-assets/…/assets/theme.css.

Une variable mal orthographiée

{{ settings.acent }} rend une chaîne vide. Le moteur tourne avec strictVariables désactivé, exprès : une faute de frappe dans un coin de pied de page ne doit pas éteindre la page entière. Traître en développement, salutaire en production.

Une section de coquille absente du thème

{% section 'annonce' %} sans sections/annonce.liquid rend un commentaire HTML. Invisible à l’écran, lisible dans le code source de la page.

#Pages voisines