#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
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
| Type | Valeur attendue | Porte une valeur |
|---|---|---|
text | chaîne courte | oui |
textarea | chaîne multiligne | oui |
richtext | chaîne de HTML | oui |
number | nombre libre | oui |
range | nombre borné | oui |
checkbox | booléen | oui |
select | une valeur d’une liste | oui |
radio | une valeur d’une liste | oui |
segmented | une valeur d’une liste | oui |
color | couleur hexadécimale | oui |
icone | identifiant d’icône | oui |
image | adresse d’image | oui |
url | adresse | oui |
font_picker | identifiant de famille | oui |
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înerequisL’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înerequisLe 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îneLe nom lisible du réglage.
defaultLa valeur de départ. C’est la seule clé que le rendu lit vraiment, avec
id.
infochaîneUne 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îneLe 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.
{
"type": "text",
"id": "cta_label",
"label": "Bouton",
"default": "Nous contacter"
}{%- 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.
{
"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 :
<p>{{ section.settings.adresse | lignes }}</p>{%- 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.
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.
{
"type": "richtext",
"id": "presentation",
"label": "Présentation",
"default": "<p>Notre atelier travaille le bois depuis <strong>1998</strong>.</p>"
}<div class="prose">{{ section.settings.presentation }}</div>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.
{
"type": "number",
"id": "annee_creation",
"label": "Année de création",
"default": 1998
}<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.
{
"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
}minnombreBorne basse.
maxnombreBorne haute.
stepnombreLe pas. 10 ici : on ne peut choisir que 60, 70, 80…
unitchaîneL’unité affichée à côté du curseur — %, px. Elle ne fait pas partie de
la valeur : le réglage vaut 100, pas "100%".
:root {
--largeur: {{ settings.largeur }}px;
--densite: {{ settings.densite }};
}Un range sert aussi à borner une boucle :
{%- 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.
{
"type": "checkbox",
"id": "mentions",
"label": "Afficher les mentions légales",
"default": true
}{%- if section.settings.mentions -%}
<a href="/legal/mentions-legales">Mentions légales</a>
<a href="/legal/confidentialite">Confidentialité</a>
{%- endif -%}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.
{
"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.
<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 :
--bouton-rayon: {% case settings.boutons %}{% when 'droit' %}0px{% when 'doux' %}8px{% else %}999px{% endcase %};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.
{
"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.
{
"type": "segmented",
"id": "alignement",
"label": "Alignement du texte",
"options": [
{ "value": "gauche", "label": "Gauche" },
{ "value": "centre", "label": "Centre" },
{ "value": "droite", "label": "Droite" }
],
"default": "gauche"
}<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.
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.
{
"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"
}:root {
--accent: {{ settings.accent }};
--accent-sombre: {{ settings.accent | teinte: -18 }};
--accent-clair: {{ settings.accent | teinte: 88 }};
}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.
{
"type": "icone",
"id": "icone",
"label": "Icône",
"default": "check"
}{% 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.
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.
{ "type": "image", "id": "image", "label": "Photographie de fond" }{%- if section.settings.image != blank -%}
<img src="{{ section.settings.image }}" alt="" loading="lazy">
{%- endif -%}Ou en image de fond, comme le bandeau d’origo :
<section class="hero"
{%- if section.settings.image != blank %} style="background-image:url({{ section.settings.image }})"{% endif -%}>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.
[
{ "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" }
]{%- 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.
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.
{%- 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.
[
{ "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.
{
"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ù | Quand | Ce 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’éditeur | le champ est écarté, et une ligne part au journal du serveur |
| Le rendu de la page | jamais | rien : 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.
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.
{
"sections": {
"nos-services": {
"type": "prestations",
"settings": { "titre": "Nos services", "colonnes": "2" }
}
},
"order": ["nos-services"]
}Dans config/settings_data.json, sous current.sections.<nom>.settings.
{
"current": {
"sections": {
"entete": { "settings": { "cta_label": "Nous contacter", "cta_href": "/contact" } }
}
}
}Dans config/settings_data.json, directement sous current.
{
"current": {
"accent": "#3a55cf",
"largeur": 1160
}
}Toute clé de current qui n’est pas sections devient un réglage global. Voir
Réglages globaux.

