Développeurs · Format du thème
Le format du thème
Un thème, c'est du JSON et du CSS. Pas d'étape de build, pas de framework, pas de serveur à tenir. La fonction même qui décide si votre thème s'installe de notre côté tourne dans la CLI et dans l'éditeur : une vérification propre là-bas est donc un envoi propre. Pour chaque nœud, composant et source de données, voir la référence des thèmes.
Deux contraintes
À savoir avant de commencer
Les deux expliquent pourquoi le thème de quelqu'un que nous n'avons jamais rencontré peut être installé sur une boutique en service.
- Un thème n'embarque pas de JavaScript. Aucun, sous aucune forme. Une section, ce sont des données, et c'est notre boutique qui la dessine : il n'y a donc rien à isoler. Tout ce qui est interactif est un composant que vous placez par son nom, et plusieurs prennent leur disposition de votre thème, donc un accordéon dans votre thème ressemble à votre accordéon. S'il vous faut un comportement qui n'est pas dans la liste, dites-le-nous : c'est un manque de notre côté, et c'est ainsi que la liste grandit.
- Le style, c'est du CSS simple sur nos jetons, pas du Tailwind. Écrivez
var(--shop-accent),var(--shop-font-heading),var(--shop-radius). Votre theme.json fixe les valeurs par défaut, et le marchand peut en changer n'importe laquelle sans toucher à votre CSS.
Structure
Le dossier
C'est ce qu'utilisent l'espace de travail, la CLI et un envoi en zip. Tout garder dans le seul theme.json fonctionne aussi ; les dossiers sont pour votre confort, pas un second format.
harbour/ theme.json identity, tokens, theme settings, plugins theme.css styling for the whole theme sections/<name>.json one file per section: its form, its data, its markup, its CSS templates/<page>.json one file per page: which sections, in which order AGENTS.md the performance rules, for coding assistants README.md yours
Aucun autre fichier n'est accepté : ni images, ni polices, ni scripts dans le dossier. Un thème contient au plus 60 sections et 2 Mo au total.
Identité
theme.json
La tête du thème. La ligne $schema vous donne l'autocomplétion et l'aide en ligne dans VS Code et la plupart des éditeurs, depuis un schéma généré par le validateur lui-même.
{
"$schema": "https://whizzycommerce.com/schema/theme-v1.json",
"dsl": 1,
"slug": "harbour",
"name": "Harbour",
"version": "1.0.0",
"description": "Big photography and quiet type for homeware shops.",
"author": { "name": "Tide Studio", "url": "https://tide.example", "email": "help@tide.example" },
"docsUrl": "https://tide.example/harbour",
"specs": { "responsive": true, "rtl": false },
"plugins": { "blog": "^2.0.0" },
"tokens": { "colorAccent": "#1f3a5f", "radius": "0" },
"settings": [],
"migrations": {}
}| Champ | Ce que c'est |
|---|---|
slug | Lettres minuscules, chiffres et tirets. Il est définitif, et il préfixe les types de section de votre thème. |
version | Semver. Voir Versions et revue pour ce que chaque type de version a le droit de changer. |
plugins | Les plugins dont le thème compose les pages, avec un intervalle de versions. Voir Plugins plus bas. |
tokens | Vos valeurs par défaut pour les jetons de design. |
settings | Des choix valables pour tout le thème, que le marchand fait une fois par boutique. |
migrations | Les renommages de sections, de réglages et de jetons, pour les boutiques qui viennent d'une version antérieure. |
Design
Jetons et réglages
Les jetons sont les couleurs, la typographie et les formes de la boutique. Les réglages sont des choix de disposition. Les deux ont vos valeurs par défaut, et le marchand peut changer les deux dans l'écran Design de la boutique.
| Jeton | Variable CSS | Par défaut |
|---|---|---|
colorBg | var(--shop-bg) | #ffffff |
colorFg | var(--shop-fg) | #0f1115 |
colorAccent | var(--shop-accent) | #0b0f19 |
colorMuted | var(--shop-muted) | #6b7280 |
fontHeading | var(--shop-font-heading) | "Inter", system-ui, sans-serif |
fontBody | var(--shop-font-body) | "Inter", system-ui, sans-serif |
fontSizeBody | var(--shop-font-size-body) | 16px |
fontSizeHeading | var(--shop-font-size-heading) | 60px |
Ce sont les 8 premiers sur 28. La référence les liste tous.
"settings": [
{ "key": "homeLayout", "label": "Home layout", "type": "select", "default": "roomy",
"options": [{ "value": "roomy", "label": "Roomy" }, { "value": "compact", "label": "Compact" }] },
{ "key": "showNote", "label": "Show the delivery note", "type": "boolean", "default": true }
]Une section lit un réglage du thème sous la forme { "get": "theme.homeLayout" } et les siens sous la forme { "get": "settings.heading" }. Utilisez un réglage de thème quand la réponse est unique par boutique, pour que le marchand la change une fois plutôt que sur chaque section.
Briques
Sections
Une section, c'est ce qu'un marchand ajoute à une page. Elle déclare un formulaire, les données dont elle a besoin, son CSS et son balisage sous forme d'arbre de nœuds de rendu. Vous ne construisez jamais le formulaire : déclarer des champs et des blocs est ce qui en donne un au marchand.
{
"type": "harbour.ticker",
"label": "Ticker",
"category": "layout",
"description": "Short phrases across the page, in the accent colour.",
"fields": [
{ "key": "speed", "label": "Speed", "type": "select", "default": "slow",
"options": [{ "value": "slow", "label": "Slow" }, { "value": "fast", "label": "Fast" }] }
],
"blocks": [{
"type": "phrase", "label": "Phrase", "max": 6,
"fields": [
{ "key": "text", "label": "Text", "type": "text", "default": "Free delivery" }
]
}],
"css": ".ticker-band { display: flex; gap: 3rem; background: var(--shop-accent) }",
"render": [
{ "node": "box", "as": "section", "cls": "ticker-band", "children": [
{ "node": "slot", "children": [
{ "node": "text", "as": "span", "value": { "get": "block.text" } }
]}
]}
]
}- type est toujours
<your slug>.<name>. Le nom du fichier est la partie après le point. - blocks donne au marchand un bouton “Ajouter une phrase”, plafonné par
max.slotdessine ses enfants une fois par bloc, et c'est ce qui fait queblock.textdésigne cette phrase-là. - render est une liste de nœuds, de ces types : box, text, rich, img, link, each, if, slot, icon, money, component. Les valeurs sont des expressions comme
{ "get": "product.title" }ou{ "lit": "Sale" }.
Sources de données
Une section déclare ce dont elle a besoin, et la page va le chercher avant de dessiner quoi que ce soit. Deux sections qui demandent la même liste coûtent une requête. Un thème ne peut pas écrire de requête à lui.
"data": [
{ "as": "picks", "source": "collection-products",
"collection": { "get": "settings.collection" }, "limit": 8 }
],
"render": [
{ "node": "box", "cls": "picks-grid", "children": [
{ "node": "each", "in": { "get": "data.picks" }, "as": "item", "limit": 8,
"children": [
{ "node": "component", "use": "product-card", "props": { "product": { "get": "item" } } }
],
"empty": [
{ "node": "text", "as": "p", "value": { "lit": "Nothing to show here yet." } }
] }
]}
]Composants
Les composants sont les parties interactives et qui connaissent la boutique : product-card, shop-logo, shop-menu, cart-link, search-box et d'autres. Vous les placez et vous stylez autour, avec props là où un composant en prend. Vous ne pouvez pas en écrire un.
Le panier, la page produit, la recherche, la connexion et les pages d'un plugin arrivent sous forme de données dans la portée, plus de petites parties qui font chacune une chose, comme sheet, cart-remove, product-form, product-gallery, search-form et login-form. Les versions toutes faites sont construites avec les mêmes parties : un thème peut donc composer n'importe laquelle autrement, un panier en fenêtre plutôt qu'en tiroir, un bloc d'achat dans son propre ordre. La référence liste chaque portée et chaque partie.
Pages
Gabarits
Un gabarit est la disposition de départ d'une page : une liste de sections placées avec leurs réglages. Les marchands la réarrangent ensuite.
[
{ "id": "hero", "type": "hero", "settings": { "heading": "Made to last" } },
{ "id": "picks", "type": "harbour.picks", "settings": { "heading": "New in", "limit": 4 } }
]| Identifiant | Page |
|---|---|
header | Header |
home | Home |
collection | Category page |
product | Product page |
cart | Cart page |
search | Search results |
login | Sign in page |
register | Create account page |
footer | Footer |
blog | La liste des articles du blog. Demande que le plugin blog soit déclaré. |
blog-post | Un article de blog. Demande que le plugin blog soit déclaré. |
Un gabarit portant tout autre nom (header, home, collection, product, cart, search, login, register, footer, ou les pages d'un plugin déclaré) est signalé lors de la validation, plutôt que de s'installer en silence et de ne rien dessiner. Une page contient au plus 40 sections.
Dépendances
Plugins
Certaines pages appartiennent à un plugin, comme le blog. Pour composer ces pages, déclarez le plugin dans theme.json avec l'intervalle de versions contre lequel vous avez construit.
"plugins": { "blog": "^2.0.0" }- Vos gabarits peuvent alors utiliser les pages et les types de section de ce plugin, comme
blog.indexetblog.post, et ses composants, commeblog-post-list. - Installer votre thème installe d'abord le plugin, pour que les pages que vous avez composées existent.
- Les intervalles prennent les formes
^2.0.0,~2.1.0,>=2.0.0,2.1.0ou*. Une soumission est bloquée si le catalogue n'a aucune version dans votre intervalle. - Si vous ne faites que styler les pages d'un plugin en CSS, ne déclarez rien. Votre CSS les atteint déjà.
Style
Le CSS, et comment il est cloisonné
Écrivez des sélecteurs de classe ordinaires. À l'installation, votre CSS est cloisonné à votre thème et chaque nom de classe reçoit un préfixe valable pour tout le thème : le .card de votre thème ne peut donc pas entrer en collision avec celui d'un autre.
.grid, les règles de l'une restylent l'autre, et une soumission avec une telle collision est bloquée. Nommez les classes d'après leur section : .picks-grid, .lookbook-grid. Les règles destinées à toutes les sections vont dans theme.css, qui est partagé exprès.- Les propriétés sont vérifiées contre une liste. Tout ce qui est refusé est signalé à la validation, jamais écarté en silence.
url()ne peut pointer que vers notre CDN de médias. Les polices viennent des jetons de police : nommez-y une police Google et elle est récupérée pour vous, sans aucun@importdans votre CSS.- Le CSS d'une section fait au plus 40 ko, et celui du thème entier au plus 200 ko.
Images
Logos et images
Les marchands téléversent leur propre logo, et un logo SVG reste vectoriel. Il est filtré jusqu'à son dessin au téléversement et toujours placé dans une balise img : il est donc sûr sur n'importe quel thème.
Placez le logo de la boutique avec { "node": "component", "use": "shop-logo" }. Quand votre design a besoin d'une seconde version, comme un logo blanc sur une barre sombre, ajoutez un réglage de thème de type image et passez-le en src :
{ "node": "component", "use": "shop-logo", "props": { "src": { "get": "theme.logoOnDark" } } }Dimensionnez en CSS la boîte où l'image se trouve, pas l'image. Les photographies de votre démo viennent du catalogue d'exemple sur la boutique du thème : vous n'en livrez donc aucune.
Vitesse
Règles de performance
Google mesure une boutique sur un téléphone de milieu de gamme et un réseau lent. Ces règles sont le texte même que la CLI écrit dans AGENTS.md, et les vérifications de publication pèsent votre page d'accueil.
Rules for anyone editing this theme, including an AI assistant. Google measures the shop on a mid-range phone on a slow network, so that is the device to build for.
Pictures
- The picture at the top of the page (hero, first slide) gets
"priority": trueon itsimgnode. It is what Largest Contentful Paint times, and without it the browser waits to load it. Only that one: every other picture stays lazy. - Give every full-width picture a
mobileSrcfield for phones, a crop that works on a narrow screen, so a phone does not download a desktop banner. - Set
sizeson anyimgthat is not full width, for example"(min-width: 768px) 33vw, 50vw"for a three-column grid, so the browser picks a smaller file. - Give the box a picture sits in a fixed shape in CSS (
aspect-ratio), so the page does not jump when it arrives. - Do not put the main picture in a CSS
background-image: the browser finds it late and cannot pick a size for it.
Fonts
- At most two families, heading and body. Each one is a separate download.
- Leave the font tokens at the core stack if the design does not need a web font: that costs nothing at all.
Weight
- Keep
theme.csslean: no unused rules, no copies of the same rule per section. - Above the first screen, place only what is needed there. A long home page is fine; a first screen with a slideshow, a carousel and a video is not.
- Keep
eachlimits realistic. Twenty-four product cards on a home page is twenty-four pictures and a long page to render; eight is usually enough. - Avoid heavy CSS effects on large areas (
backdrop-filter, bigbox-shadow,filter: blur), which are slow to draw on a phone.
Checking
Run https://pagespeed.web.dev on the shop's home page and one product page, on Mobile. Aim for Largest Contentful Paint under 2.5 s and Cumulative Layout Shift under 0.1. Fix the first item Lighthouse lists before anything else.
Langues
Écrivez-le pour qu'il puisse être traduit
Tout ce qu'un marchand pourrait vouloir réécrire devrait être un champ avec une valeur par défaut, pas une chaîne en dur dans le balisage.
Un champ apparaît dans l'éditeur du marchand et dans son écran de traduction : votre titre anglais devient donc le sien en portugais. Une valeur lit reste coincée en anglais sur chaque boutique qui installe votre thème. Gardez lit pour ce qui n'est pas des mots, comme un nom de classe ou une valeur de disposition.