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": {}
}
ChampCe que c'est
slugLettres minuscules, chiffres et tirets. Il est définitif, et il préfixe les types de section de votre thème.
versionSemver. Voir Versions et revue pour ce que chaque type de version a le droit de changer.
pluginsLes plugins dont le thème compose les pages, avec un intervalle de versions. Voir Plugins plus bas.
tokensVos valeurs par défaut pour les jetons de design.
settingsDes choix valables pour tout le thème, que le marchand fait une fois par boutique.
migrationsLes 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.

JetonVariable CSSPar défaut
colorBgvar(--shop-bg)#ffffff
colorFgvar(--shop-fg)#0f1115
colorAccentvar(--shop-accent)#0b0f19
colorMutedvar(--shop-muted)#6b7280
fontHeadingvar(--shop-font-heading)"Inter", system-ui, sans-serif
fontBodyvar(--shop-font-body)"Inter", system-ui, sans-serif
fontSizeBodyvar(--shop-font-size-body)16px
fontSizeHeadingvar(--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. slot dessine ses enfants une fois par bloc, et c'est ce qui fait que block.text dé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 } }
]
IdentifiantPage
headerHeader
homeHome
collectionCategory page
productProduct page
cartCart page
searchSearch results
loginSign in page
registerCreate account page
footerFooter
blogLa liste des articles du blog. Demande que le plugin blog soit déclaré.
blog-postUn 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.index et blog.post, et ses composants, comme blog-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.0 ou *. 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.

Les noms de classe des sections doivent être uniques entre sections. Toutes les sections d'un thème partagent cet unique préfixe. Si deux sections stylent toutes les deux .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 @import dans 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": true on its img node. 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 mobileSrc field for phones, a crop that works on a narrow screen, so a phone does not download a desktop banner.
  • Set sizes on any img that 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.css lean: 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 each limits 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, big box-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.

SuivantVersions et revue →
Format du thème · WhizzyCommerce