Aller au contenu

#Les types de réglage

Un réglage est une entrée du tableau settings d’un schéma. Son type dit de quelle nature est la valeur, son id sous quel nom on la lit en Liquid, et son label comment on l’appelle. Webcosa en admet seize : quatorze de Shopify — les siens, moins ceux qui n’auraient pas de sens sur un site vitrine — et deux qui n’appartiennent qu’à Webcosa, segmented et icone.

La liste vit à un seul endroit, TYPES_REGLAGE dans src/lib/liquid/format.ts, et tout le reste en dérive : le formulaire de l’éditeur, le vérificateur de thème, cette page. Elle a été triple pendant un temps, et les trois copies avaient divergé.

Cette page les passe tous en revue, avec pour chacun sa déclaration complète et la façon de lire sa valeur.

#Avant tout : ce que le type fait, et ce qu’il ne fait pas

Attention

Le type n’a aucun effet au moment du rendu. section.settings.<id> rend exactement la valeur JSON enregistrée, quelle qu’elle soit. Déclarer "type": "number" ne convertit rien, ne valide rien et ne refuse rien : si le JSON porte la chaîne "douze", c’est "douze" que Liquid verra.

Le type est une déclaration d’intention. Il dit à qui écrit les valeurs, et au panneau de réglages, quelle forme la valeur doit prendre.

Le panneau, lui, existe : c’est celui qu’un client ouvre en cliquant une section dans l’éditeur de site, et c’est là que le type décide de tout. Un range y devient un curseur, un color une pastille, un icone une grille de vignettes ; un type inconnu n’y devient rien du tout, et le réglage disparaît du panneau. Le client ne peut alors le régler qu’en écrivant le JSON à la main — ce que le thème ne voulait probablement pas.

Les valeurs peuvent aussi s’écrire à la main : dans templates/*.json pour les sections d’une page, dans config/settings_data.json pour les réglages globaux et les sections de la coquille. On les édite dans l’éditeur de code du CMS, ou depuis sa machine avec le CLI.

Le seul effet mécanique d’un réglage est son default : les valeurs par défaut du schéma sont posées sous les valeurs enregistrées. Voir Le bloc schema.

#Les seize types

TypeValeur attenduePorte une valeur
textchaîne courteoui
textareachaîne multiligneoui
richtextchaîne de HTMLoui
numbernombre libreoui
rangenombre bornéoui
checkboxbooléenoui
selectune valeur d’une listeoui
radioune valeur d’une listeoui
segmentedune valeur d’une listeoui
colorcouleur hexadécimaleoui
iconeidentifiant d’icôneoui
imageadresse d’imageoui
urladresseoui
font_pickeridentifiant de familleoui
header—non
paragraph—non

Les deux derniers ne portent pas de valeur : ils structurent le panneau. Ils n’ont donc pas besoin d’id, et rien ne les lit en Liquid.

#Les clés communes

typechaînerequis

L’un des seize ci-dessus. Un type inconnu est écarté du panneau de réglages et signalé, mais jamais rejeté : la page continue de rendre. La dernière partie de cette page dit exactement où le signal apparaît.

idchaînerequis

Le nom sous lequel la valeur est lue : section.settings.<id>. Obligatoire pour tous les types sauf header et paragraph. En minuscules avec des tirets bas, par convention — c’est ce que font les huit thèmes d’origine.

labelchaîne

Le nom lisible du réglage.

default

La valeur de départ. C’est la seule clé que le rendu lit vraiment, avec id.

infochaîne

Une phrase d’explication. Sous-utilisé, et c’est dommage : c’est la seule occasion de dire l’effet d’un réglage là où on le remplit.

placeholderchaîne

Le texte grisé d’un champ vide. Déclaré au format, admis à l’installation, et sans aucun effet au rendu — comme info, unit, min, max et step, il attend le panneau de réglages. À ne pas confondre avec default : un placeholder ne devient jamais une valeur.


#text — une chaîne courte

Le type le plus utilisé, et de loin : les huit thèmes en déclarent plus de huit cents. Titres, surtitres, libellés de boutons, numéros de téléphone.

Déclarationjson
{
  "type": "text",
  "id": "cta_label",
  "label": "Bouton",
  "default": "Nous contacter"
}
Lectureliquid
{%- if section.settings.cta_label != blank -%}
  <a class="bouton" href="{{ section.settings.cta_href }}">
    {{ section.settings.cta_label }}
  </a>
{%- endif -%}

Le HTML n’est pas échappé automatiquement, ici comme partout dans Liquid. Pour du contenu saisi par le client dans un endroit sensible, passe par escape.

#textarea — une chaîne multiligne

Chapeaux, adresses postales, listes courtes. Deux cent soixante occurrences dans les huit thèmes.

Déclarationjson
{
  "type": "textarea",
  "id": "compris",
  "label": "Ce qui est compris",
  "info": "Une ligne par élément.",
  "default": "Déplacement\nDiagnostic\nGarantie 2 ans"
}

Les sauts de ligne sont conservés dans la valeur, mais le HTML les ignore. Deux façons de les rendre visibles :

Avec le filtre lignesliquid
<p>{{ section.settings.adresse | lignes }}</p>
Avec split, pour une vraie listeliquid
{%- assign lignes = bloc.settings.compris | split: '
' -%}
<ul class="liste-coches">
  {%- for ligne in lignes -%}<li>{{ ligne }}</li>{%- endfor -%}
</ul>

Le second est tiré de themes/origo/sections/tarifs.liquid. Le saut de ligne littéral dans le split: est volontaire — Liquid n’interprète pas \n dans une chaîne.

Astuce

lignes échappe le HTML avant de convertir les sauts de ligne en br. C’est le seul filtre maison qui échappe : il est fait pour du texte saisi par le client.

#richtext — du HTML mis en forme

Pour un paragraphe avec du gras, de l’italique et des liens. La valeur est une chaîne de HTML, insérée telle quelle.

Déclarationjson
{
  "type": "richtext",
  "id": "presentation",
  "label": "Présentation",
  "default": "<p>Notre atelier travaille le bois depuis <strong>1998</strong>.</p>"
}
Lectureliquid
<div class="prose">{{ section.settings.presentation }}</div>
Danger

Neuf réglages seulement l’emploient dans le dépôt — quatre dans aplomb, trois dans cadence, deux dans scene — et rien n’assainit ce HTML. La valeur est écrite dans la page telle quelle, balise script comprise. C’est sans conséquence tant que le thème et ses valeurs viennent du propriétaire du site — il ne peut s’attaquer qu’à lui-même — mais c’est à savoir avant de l’exposer. Voir Le bac à sable.

#number — un nombre libre

Un entier ou un décimal sans borne. À réserver aux cas où une borne n’aurait pas de sens ; sinon, range est plus sûr.

Déclarationjson
{
  "type": "number",
  "id": "annee_creation",
  "label": "Année de création",
  "default": 1998
}
Lectureliquid
<p>{{ 'now' | date: '%Y' | minus: section.settings.annee_creation }} ans d'expérience</p>

Aucun des huit thèmes ne l’utilise : ils préfèrent tous range, qui rend une valeur absurde impossible à saisir.

#range — un nombre borné

Le type des largeurs, des arrondis, des échelles, du nombre d’articles à afficher. Cent trente-quatre occurrences dans les huit thèmes.

themes/origo/config/settings_schema.jsonjson
{
  "type": "range",
  "id": "densite",
  "label": "Espace entre les sections",
  "info": "Plus haut, le site respire ; plus bas, il tient sur moins d'écrans.",
  "min": 60,
  "max": 160,
  "step": 10,
  "unit": "%",
  "default": 100
}
minnombre

Borne basse.

maxnombre

Borne haute.

stepnombre

Le pas. 10 ici : on ne peut choisir que 60, 70, 80…

unitchaîne

L’unité affichée à côté du curseur — %, px. Elle ne fait pas partie de la valeur : le réglage vaut 100, pas "100%".

Lecture — l’unité s’écrit dans le CSSliquid
:root {
  --largeur: {{ settings.largeur }}px;
  --densite: {{ settings.densite }};
}

Un range sert aussi à borner une boucle :

liquid
{%- for article in articles limit: section.settings.nombre -%}

#checkbox — un booléen

Afficher ou masquer. Cent quarante et une occurrences dans les huit thèmes.

Déclarationjson
{
  "type": "checkbox",
  "id": "mentions",
  "label": "Afficher les mentions légales",
  "default": true
}
Lectureliquid
{%- if section.settings.mentions -%}
  <a href="/legal/mentions-legales">Mentions légales</a>
  <a href="/legal/confidentialite">Confidentialité</a>
{%- endif -%}
Attention

Pas de comparaison à true : {% if section.settings.mentions %} suffit. Et surtout, pas de comparaison à la chaîne "true" — la valeur JSON est un booléen, false == "false" est faux en Liquid, et la condition serait toujours vraie.

#select — une valeur parmi une liste

Le deuxième type le plus utilisé : quatre cent deux occurrences. Fond de section, nombre de colonnes, hauteur d’un bandeau, forme des boutons.

themes/origo/sections/prestations.liquidjson
{
  "type": "select",
  "id": "colonnes",
  "label": "Colonnes",
  "options": [
    { "value": "2", "label": "Deux" },
    { "value": "3", "label": "Trois" },
    { "value": "4", "label": "Quatre" }
  ],
  "default": "3"
}

Chaque option porte une value — ce que Liquid lira — et un label — ce qu’un humain lit. Les value sont des chaînes, même quand elles ressemblent à des nombres.

Lecture — la valeur devient une classeliquid
<div class="grille grille-{{ section.settings.colonnes | default: '3' }}">

C’est le motif des thèmes d’origine : le select ne pilote pas une condition, il compose un nom de classe, et la feuille de style fait le reste. Un select à quatre valeurs vaut alors quatre règles CSS, pas quatre {% if %}.

Quand une condition est inévitable :

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

Le | default: '3' protège du cas où la valeur n’a jamais été enregistrée et où le schéma n’a pas pu être lu. Sans lui, la classe serait grille- et la grille s’effondrerait sur une colonne.

#radio — une valeur parmi une liste, toutes visibles

Même contenu qu’un select, autre présentation : les options sont affichées côte à côte au lieu d’être repliées dans une liste. À préférer à deux ou trois options courtes.

Déclarationjson
{
  "type": "radio",
  "id": "position",
  "label": "Position du texte",
  "options": [
    { "value": "gauche", "label": "À gauche" },
    { "value": "centre", "label": "Au centre" }
  ],
  "default": "gauche"
}

La lecture est identique à celle d’un select. Aucun des huit thèmes ne l’utilise : ils déclarent tout en select, y compris les choix à deux options.

#segmented — deux ou trois choix, tous visibles

Le même contenu qu’un select, une autre présentation : les options sont posées côte à côte dans le panneau, en boutons, au lieu d’être repliées dans une liste déroulante. Propre à Webcosa — Shopify ne l’a pas.

Déclarationjson
{
  "type": "segmented",
  "id": "alignement",
  "label": "Alignement du texte",
  "options": [
    { "value": "gauche", "label": "Gauche" },
    { "value": "centre", "label": "Centre" },
    { "value": "droite", "label": "Droite" }
  ],
  "default": "gauche"
}
Lecture — identique à celle d’un selectliquid
<div class="bloc bloc--{{ section.settings.alignement | default: 'gauche' }}">

La valeur est une chaîne, les options s’écrivent comme celles d’un select, et Liquid ne voit aucune différence entre les deux types. Le choix ne se joue qu’à l’affichage du panneau.

Astuce

segmented jusqu’à trois options aux libellés courts : le client voit d’un coup d’œil ce qui est possible, sans ouvrir de liste. Au-delà, ou dès qu’un libellé dépasse une dizaine de caractères, revenir au select : les boutons se serrent et deviennent illisibles à la largeur du panneau.

#color — une couleur

Cinquante-six occurrences, toutes dans les settings_schema.json : les couleurs sont des réglages globaux, jamais des réglages de section. C’est ce qui permet de changer l’accent d’un site d’un seul geste.

themes/origo/config/settings_schema.jsonjson
{
  "type": "color",
  "id": "accent",
  "label": "Accent",
  "info": "Les boutons, les liens, les chiffres mis en avant. Il sert aussi de TEXTE (surtitres, prix) : garde-le assez foncé sur fond clair, assez clair sur fond sombre.",
  "default": "#3a55cf"
}
Lecture — dans la coquille, en variables CSSliquid
:root {
  --accent: {{ settings.accent }};
  --accent-sombre: {{ settings.accent | teinte: -18 }};
  --accent-clair: {{ settings.accent | teinte: 88 }};
}
Attention

Le filtre teinte n’accepte que la forme hexadécimale à six chiffres, avec ou sans #. rgb(…), oklch(…) ou un nom de couleur sont rendus tels quels, sans transformation ni erreur. Écris tes default en #rrggbb.

teinte: -18 assombrit de 18 %, teinte: 88 éclaircit de 88 %. Sans lui, chaque nuance devrait être un réglage de plus — et un panneau de vingt sélecteurs de couleur ne se remplit jamais.

#icone — une icône du jeu de la plateforme

La valeur est l’identifiant d’une icône du jeu fermé de Webcosa. Le panneau en fait une grille de vignettes, et non une liste déroulante : un nom d’icône ne dit pas de quoi elle a l’air. Propre à Webcosa, lui aussi.

Déclarationjson
{
  "type": "icone",
  "id": "icone",
  "label": "Icône",
  "default": "check"
}
Lecture — le nom passe au snippet d’icôneliquid
{% render 'icone', nom: block.settings.icone %}

Le jeu par défaut est celui de src/lib/theme/icones.tsx : check, bouclier, etoile, recompense, horloge, camion, euro, cadenas, lieu, telephone, agenda, marteau, cle, regle, feuille, recyclage, appareil… plus une entrée vide, « Aucune ». Il est choisi pour les métiers que Webcosa sert : artisans, coachs, photographes, restaurants, professions réglementées.

Un réglage icone peut porter ses propres options, aux mêmes value et label que celles d’un select ; elles remplacent alors le jeu par défaut dans la grille. C’est ainsi qu’on n’offre que les cinq icônes qui ont un sens dans une section donnée.

Attention

Un thème ne dessine jamais son icône, il en nomme une. Du SVG écrit dans un réglage et injecté dans un site publié serait une surface d’attaque, et des tracés dessinés au coup par coup n’auraient ni la même graisse ni la même grille que les autres — le site perdrait sa tenue à chaque ajout.

#image — une image

La valeur est l'adresse d’une image de la bibliothèque du site, pas un fichier du thème. Quatre-vingt-onze occurrences dans les huit thèmes.

themes/piazza/sections/bandeau.liquidjson
{ "type": "image", "id": "image", "label": "Photographie de fond" }
Lecture — toujours sous conditionliquid
{%- if section.settings.image != blank -%}
  <img src="{{ section.settings.image }}" alt="" loading="lazy">
{%- endif -%}

Ou en image de fond, comme le bandeau d’origo :

liquid
<section class="hero"
  {%- if section.settings.image != blank %} style="background-image:url({{ section.settings.image }})"{% endif -%}>
Attention

Le nom Webcosa est image, et les huit thèmes livrés s’y tiennent.

Ils ne s’y sont pas toujours tenus : origo déclarait quinze de ses réglages d’image en image_picker, forge et vela un chacun — le nom Shopify, qui ne fait pas partie des types admis. Ces dix-sept déclarations rendaient correctement, puisqu’un type inconnu est écarté et que la lecture ne dépend pas du type ; elles étaient simplement invisibles pour tout outil qui lit les schémas, à commencer par le panneau de réglages. Elles ont été renommées.

Les dimensions ne sont pas connues du thème : l’image vient de la bibliothèque du client. Le ratio se pose donc en CSS, jamais en attributs width/height — c’est ce que fait snippets/image.liquid d’origo.

#url — une adresse

Un lien interne (/contact), une ancre (#prestations) ou une adresse externe. Cent vingt-quatre occurrences, réparties sur sept des huit thèmes.

themes/forge/sections/bandeau.liquidjson
[
  { "type": "text", "id": "cta_label", "label": "Bouton principal", "default": "Demander un devis" },
  { "type": "url", "id": "cta_href", "label": "Lien", "default": "/contact" },
  { "type": "text", "id": "cta2_label", "label": "Bouton secondaire", "default": "Voir les réalisations" },
  { "type": "url", "id": "cta2_href", "label": "Lien", "default": "#realisations" }
]
Lectureliquid
{%- render 'bouton', label: section.settings.cta_label, href: section.settings.cta_href -%}

Rien ne valide la forme de l’adresse : c’est une chaîne comme une autre. origo déclare d’ailleurs les siennes en text, ce qui rend exactement la même chose. url dit l’intention, et c’est déjà utile à la relecture.

#font_picker — une famille de police, NOMMÉE

La valeur est l’identifiant d’une famille de src/lib/polices.ts — vingt-trois, toutes sous SIL Open Font License, servies en sous-ensemble latin depuis /polices/. systeme ne télécharge rien et retombe sur la pile du système : c’est le défaut, et le plus rapide qui existe.

Attention

Un thème ne transporte pas de police, il en nomme une. assets/ n’accepte que .css et .svg, et cette liste ne s’ouvre pas — c’est elle qui empêche un thème importé de déposer du binaire, ou du script, sous le domaine d’un client.

Deux filtres la rendent utilisable, et il faut les deux : font_face écrit la règle @font-face, font_famille rend la pile CSS complète — la famille, puis son repli système.

Quand la famille a un vrai italique (Crimson Pro, aujourd’hui), font_face déclare les DEUX faces, romaine et italique, sous le même nom de famille : un <em> s’écrit alors dans le dessin penché de la police, au lieu d’un romain cisaillé par le navigateur. L’italique n’est téléchargé que si la page en contient.

layout/theme.liquidliquid
{%- comment -%} dans <head>, une fois par famille employée {%- endcomment -%}
<style>
  {{ settings.police_titres | font_face }}
  {{ settings.police_texte | font_face }}
</style>

{%- comment -%} puis, dans les variables {%- endcomment -%}
{%- assign pile = settings.police_titres | font_famille -%}
{%- if pile != blank -%}--f-titre: {{ pile }};{%- endif -%}

Les deux filtres rendent du vide pour un identifiant inconnu : la valeur ne sert qu’à chercher dans un catalogue fermé, écrit par nous. Elle ne peut donc rien injecter dans la feuille de style servie aux visiteurs.

Aucun appel à Google Fonts, et ce n’est pas une préférence : une police servie par un tiers lui transmet l’adresse IP du visiteur, ce qui a déjà valu des condamnations en France.

#header — un intertitre dans le panneau

Ne porte aucune valeur. Il coupe une longue liste de réglages en sections lisibles.

themes/forge/sections/bandeau.liquidjson
[
  { "type": "textarea", "id": "texte", "label": "Sous-titre" },
  { "type": "image", "id": "image", "label": "Image de fond" },
  { "type": "header", "id": "h_boutons", "label": "Boutons" },
  { "type": "text", "id": "cta_label", "label": "Bouton principal" },
  { "type": "url", "id": "cta_href", "label": "Lien" }
]

Rien à lire en Liquid : {{ section.settings.h_boutons }} rend du vide. L’id n’est pas requis — cent trois des cent soixante-quatorze header du dépôt en portent un, dont cent pour le seul aplomb ; ça ne coûte rien et ça évite deux entrées strictement identiques.

#paragraph — une note dans le panneau

Ne porte pas de valeur non plus. Sert à expliquer un parti pris à celui qui remplit les réglages. Son texte est dans content, et non dans label — c’est la convention Shopify, que header suit aussi. Les thèmes du dépôt ne s’y tiennent pas partout, et l’écart est plus large côté header : dix-neuf paragraph sur quarante-deux mettent leur texte dans label, et cent trois header sur cent soixante-quatorze. Le type Reglage déclare les deux champs, et l’éditeur retient le premier qu’il trouve — label, puis content, puis id. Les deux graphies s’affichent donc, mais c’est une tolérance et non la convention : écris content.

themes/vela/sections/temoignage.liquidjson
{
  "type": "paragraph",
  "content": "Un seul témoignage, en grand. Trois avis alignés se lisent comme un argumentaire ; un seul se lit comme une phrase."
}

C’est le bon endroit pour justifier une contrainte : pourquoi la section n’accepte qu’un seul bloc, pourquoi le texte doit rester court, ce que le réglage d’à côté va casser si on le pousse.


#Un type inconnu est écarté, pas rejeté

Le format n’échoue jamais sur un type qu’il ne connaît pas. Un thème écrit pour une version plus récente de Webcosa — ou importé depuis Shopify, avec ses image_picker, collection, product_list — continue de rendre sa page.

Le raisonnement est le même que pour les sections en échec : refuser tout un thème pour un réglage inconnu casserait un site en ligne pour un détail d’éditeur. Le rendu, lui, ne lit jamais le type : section.settings.<id> rend la valeur enregistrée, et le default s’applique, quel que soit le type déclaré.

Ce qui se perd, c’est le champ du panneau : le réglage n’y apparaît pas, donc le client ne peut plus le modifier autrement qu’en écrivant le JSON.

#Où le défaut se dit, maintenant

Il ne se disait nulle part : le réglage disparaissait sans un mot, à l’installation comme à l’édition. Trois endroits le signalent désormais, du plus dur au plus discret.

OùQuandCe qui arrive
node scripts/verifier-theme.mjs <thème>avant toute mise en ligneéchec, code 1 — sections et settings_schema.json
Le panneau de réglagesà l’ouverture de l’éditeurle champ est écarté, et une ligne part au journal du serveur
Le rendu de la pagejamaisrien : la page est juste

Le refus DUR n’existe qu’au vérificateur, et c’est délibéré : là, il ne coûte qu’à qui écrit le thème. Plus loin dans la chaîne, il coûterait au client dont le site est déjà publié — on punirait la victime.

Attention

Une faute de frappe dans un type reste muette au rendu. Un chekbox rend exactement comme un checkbox : le default s’applique, la valeur écrite dans le gabarit remonte, la page est juste. Seuls le vérificateur et le panneau la verront.

Lancez node scripts/verifier-theme.mjs <thème> avant de livrer : c’est le seul endroit qui refuse net, et il lit la liste des types dans le code du moteur plutôt que d’en garder une copie — une copie finit toujours par mentir.

#Où les valeurs sont écrites

Dans templates/<gabarit>.json, sous l’instance de la section.

json
{
  "sections": {
    "nos-services": {
      "type": "prestations",
      "settings": { "titre": "Nos services", "colonnes": "2" }
    }
  },
  "order": ["nos-services"]
}

Toute clé de current qui n’est pas sections devient un réglage global. Voir Réglages globaux.

#Pages voisines