À retenir
- Vous n’avez pas besoin d’une app pour une bannière compte à rebours. Un timer n’est qu’un simple fichier de section : un peu de Liquid, de CSS et un petit script inline. Les apps vous facturent un abonnement mensuel pour ça et ajoutent le poids de leur propre script sur chaque page.
- La barre d’annonces du thème Dawn n’a pas de compte à rebours. Elle supporte jusqu’à 12 annonces textuelles rotatives, des icônes sociales et des sélecteurs de langue, mais rien de basé sur le temps. Une section custom est donc la solution sans app.1
- Le piège de la timezone (timezone gotcha) est bien réel. Une date sans décalage UTC est parsée dans la timezone locale de chaque visiteur, donc votre promo “se termine” à un instant différent dans chaque pays. Fixez la deadline avec un offset explicite.
- Une bannière est une cause fréquente de CLS (layout shift). Réservez sa hauteur en CSS et donnez aux chiffres des boîtes à largeur fixe, sinon le timer décalera toute votre page vers le bas après le chargement.
Une bannière compte à rebours Shopify sans app (countdown banner) est un simple fichier de section que vous collez dans votre thème : un message, quatre chiffres qui défilent, et une deadline définie depuis l’éditeur de thème. Ce guide vous fournit la section complète, puis détaille les trois points sur lesquels la plupart des snippets copiés-collés se trompent : les timezones, le layout shift, et les lecteurs d’écran (screen readers).
Cet article est écrit pour le BFCM, car c’est la période où la plupart des boutiques cherchent à ajouter un timer, mais cette section fonctionnera pour n’importe quelle promo avec une vraie date de fin.
Pourquoi vous pouvez nous faire confiance
Nous évoluons dans l’écosystème Shopify depuis plus de quatre ans et avons travaillé avec des centaines de marques sur leurs boutiques. Jacques a plus de 15 ans d’expérience en développement. Nous concevons Fudge, un éditeur de boutique (storefront editor) IA qui a une note de 4.9 sur le Shopify App Store et le statut Built for Shopify, donc coder des sections de thème comme celle-ci est littéralement ce que notre produit fait à longueur de journée.
Pourquoi une bannière compte à rebours fonctionne si bien pour le BFCM
Une deadline répond à la seule question que chaque acheteur du BFCM se pose en silence : est-ce que je peux y réfléchir plus tard ? Quand la réponse honnête est non, un timer bien visible le dit beaucoup mieux que n’importe quel superlatif. C’est l’aversion à la perte (loss aversion) qui vend à votre place.
Le mot-clé ici est honnête. Faites un compte à rebours avec une vraie date de fin, et arrêtez la promo quand il atteint zéro. Les timers “evergreen” qui se remettent à zéro tout seuls (reset) à chaque rafraîchissement habituent vos visiteurs réguliers à ignorer l’urgence, et ils vous placent du mauvais côté de la loi sur les dark patterns dans un nombre croissant de juridictions.
Une bannière est aussi l’endroit le moins cher pour créer de l’urgence. Elle se place au-dessus du header sur toutes les pages, ne nécessite de redesigner quoi que ce soit, et disparaît au moment même où la promo se termine.
Si vous préparez le reste de votre stratégie BFCM, pensez aussi à la partie analytics : notre guide associé sur le tracking Meta côté serveur (server-side) avec l’API Conversions vous explique comment vous assurer que le trafic converti par votre bannière vous soit bien attribué.
Avez-vous vraiment besoin d’une app de compte à rebours ?
Non, et pour une bannière aussi petite, recourir à une app est un mauvais calcul sur toute la ligne.
Les apps coûtent de l’argent pour des choses qu’un thème peut faire nativement. Les apps de bannières compte à rebours vous facturent un abonnement pour ce qui revient à un simple fichier de section. C’est tout à fait justifié pour des fonctionnalités complexes ; un timer n’est pas une fonctionnalité complexe.
Les apps ralentissent vos pages, toute l’année. Les app embeds injectent leur propre JavaScript et CSS dans votre boutique, et la plupart restent chargés une fois la promo terminée, ou même après leur désinstallation si l’app a modifié votre thème. Pendant le BFCM, où chaque visiteur compte, le poids d’un script tiers est la dernière chose dont vous avez besoin pour votre plus grosse semaine de trafic de l’année.
Une section native, en revanche, ne pèse que quelques kilo-octets servis depuis le CDN de Shopify avec votre thème, se modifie dans le même éditeur de thème que le reste, et se supprime en un clic quand le mois de décembre arrive.
Que peut faire l’éditeur de thème sans aucun code ?
Il est bon de vérifier avant de coder, car la réponse a changé en 2025-2026.
Dawn et la plupart des thèmes gratuits : pas de compte à rebours. La section “barre d’annonces” de Dawn supporte jusqu’à 12 blocs d’annonces rotatifs avec des liens, un auto-rotate, des icônes sociales, une ligne de séparation et des sélecteurs de pays et de langue. Il n’y a aucun paramètre basé sur le temps dans son schéma (schema).1 Le mieux que vous puissiez faire est un message statique de type “La promo se termine lundi”.
Horizon et les blocs générés par IA : oui, en quelque sorte. L’éditeur de thème de Shopify peut désormais générer un bloc custom à partir d’un prompt textuel via Shopify Magic. Vous cliquez sur “Ajouter un bloc” (Add block), choisissez “Générer” (Generate), et décrivez le timer que vous voulez. Cela fonctionne sur les thèmes du Theme Store avec la plupart des forfaits, mais avec une mise en garde qu’il vaut mieux lire deux fois : le support Shopify ne vous aidera pas à customiser ou à débugger le code généré, et la qualité relève explicitement de votre responsabilité.2
Cette mise en garde explique pourquoi une section vérifiée à la main reste la meilleure option pour le BFCM. Vous obtenez un code que vous avez pu relire, avec les paramètres exacts dont vous avez besoin, sans mauvaises surprises le soir de Thanksgiving à 23h. Si vous n’avez jamais ajouté de section auparavant, survolez d’abord notre guide sur comment ajouter des sections sur la page d’accueil Shopify ; la mécanique est identique.
À vous de jouer : la section complète de la bannière compte à rebours
Dans votre admin, allez dans Boutique en ligne → Thèmes → ⋯ → Modifier le code (Edit code). Dupliquez d’abord le thème par sécurité, puis créez un nouveau fichier dans le dossier sections/ nommé countdown-banner.liquid et copiez-collez tout ce qui suit à l’intérieur.
<div
id="CountdownBanner-{{ section.id }}"
class="countdown-banner"
data-end="{{ section.settings.end_date | escape }}T{{ section.settings.end_time | escape }}:00{{ section.settings.utc_offset | escape }}"
data-expiry="{{ section.settings.expiry_behavior | escape }}"
style="background: {{ section.settings.bg_color }}; color: {{ section.settings.text_color }};"
>
<div class="countdown-banner__inner">
<p class="countdown-banner__message">
{%- if section.settings.link != blank -%}
<a href="{{ section.settings.link }}">{{ section.settings.message | escape }}</a>
{%- else -%}
{{ section.settings.message | escape }}
{%- endif -%}
</p>
<div class="countdown-banner__timer" role="timer" aria-live="off">
<span class="countdown-banner__sr-only">
Offer ends {{ section.settings.end_date }} at {{ section.settings.end_time }}
</span>
<span class="countdown-banner__unit" aria-hidden="true">
<span class="countdown-banner__digits" data-unit="days">–</span>
<small>days</small>
</span>
<span class="countdown-banner__unit" aria-hidden="true">
<span class="countdown-banner__digits" data-unit="hours">–</span>
<small>hrs</small>
</span>
<span class="countdown-banner__unit" aria-hidden="true">
<span class="countdown-banner__digits" data-unit="minutes">–</span>
<small>min</small>
</span>
<span class="countdown-banner__unit" aria-hidden="true">
<span class="countdown-banner__digits" data-unit="seconds">–</span>
<small>sec</small>
</span>
</div>
<p class="countdown-banner__expired" hidden>
{{ section.settings.expired_message | escape }}
</p>
</div>
</div>
{% style %}
#CountdownBanner-{{ section.id }} {
min-height: 3.25rem; /* réserve l'espace : pas de layout shift au chargement du JS */
display: flex;
align-items: center;
}
#CountdownBanner-{{ section.id }} .countdown-banner__inner {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.25rem;
align-items: center;
justify-content: center;
width: 100%;
padding: 0.5rem 1.5rem;
text-align: center;
}
#CountdownBanner-{{ section.id }} .countdown-banner__message {
margin: 0;
font-size: 0.9rem;
letter-spacing: 0.03em;
}
#CountdownBanner-{{ section.id }} .countdown-banner__message a {
color: inherit;
}
#CountdownBanner-{{ section.id }} .countdown-banner__timer {
display: flex;
gap: 0.75rem;
}
#CountdownBanner-{{ section.id }} .countdown-banner__unit {
display: inline-flex;
flex-direction: column;
line-height: 1.1;
}
#CountdownBanner-{{ section.id }} .countdown-banner__digits {
font-variant-numeric: tabular-nums; /* chaque chiffre a la même largeur : pas de tremblement */
min-width: 2ch;
font-weight: 700;
font-size: 1rem;
}
#CountdownBanner-{{ section.id }} .countdown-banner__unit small {
font-size: 0.6rem;
text-transform: uppercase;
letter-spacing: 0.08em;
opacity: 0.75;
}
#CountdownBanner-{{ section.id }} .countdown-banner__expired {
margin: 0;
width: 100%;
}
.countdown-banner__sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
@media (prefers-reduced-motion: no-preference) {
#CountdownBanner-{{ section.id }} .countdown-banner__digits {
transition: opacity 150ms ease;
}
}
{% endstyle %}
<script>
(function () {
function initCountdown() {
var banner = document.getElementById('CountdownBanner-{{ section.id }}');
if (!banner || banner.dataset.initialized) return;
banner.dataset.initialized = 'true';
var timer = banner.querySelector('.countdown-banner__timer');
var expired = banner.querySelector('.countdown-banner__expired');
var end = new Date(banner.dataset.end).getTime();
if (isNaN(end)) {
// Date manquante ou incorrecte : on masque le timer et on affiche le message statique
timer.hidden = true;
expired.hidden = false;
return;
}
var units = {};
banner.querySelectorAll('[data-unit]').forEach(function (el) {
units[el.dataset.unit] = el;
});
function pad(n) {
return n < 10 ? '0' + n : String(n);
}
var tick;
function render() {
if (!banner.isConnected) {
clearInterval(tick);
return;
}
var diff = end - Date.now();
if (diff <= 0) {
clearInterval(tick);
if (banner.dataset.expiry === 'hide') {
banner.hidden = true;
} else {
timer.hidden = true;
expired.hidden = false;
}
return;
}
var s = Math.floor(diff / 1000);
units.days.textContent = String(Math.floor(s / 86400));
units.hours.textContent = pad(Math.floor((s % 86400) / 3600));
units.minutes.textContent = pad(Math.floor((s % 3600) / 60));
units.seconds.textContent = pad(s % 60);
}
render();
tick = setInterval(render, 1000);
}
initCountdown();
// Éditeur de thème : ré-init quand la section se recharge
document.addEventListener('shopify:section:load', function (event) {
if (event.detail && event.detail.sectionId === '{{ section.id }}') {
initCountdown();
}
});
})();
</script>
{% schema %}
{
"name": "Countdown banner",
"settings": [
{
"type": "text",
"id": "message",
"label": "Message",
"default": "Black Friday: up to 40% off sitewide"
},
{
"type": "url",
"id": "link",
"label": "Link (optional)"
},
{
"type": "text",
"id": "end_date",
"label": "End date (YYYY-MM-DD)",
"default": "2026-11-30"
},
{
"type": "text",
"id": "end_time",
"label": "End time, 24h (HH:MM)",
"default": "23:59"
},
{
"type": "text",
"id": "utc_offset",
"label": "Store UTC offset",
"default": "-05:00",
"info": "Fixe l'échéance à un moment global unique. Utilisez l'offset de la timezone de votre boutique, par ex. -05:00 pour New York en novembre, +01:00 pour Paris/Berlin."
},
{
"type": "select",
"id": "expiry_behavior",
"label": "When the countdown ends",
"options": [
{ "value": "hide", "label": "Hide the banner" },
{ "value": "message", "label": "Show the expired message" }
],
"default": "message"
},
{
"type": "text",
"id": "expired_message",
"label": "Expired message",
"default": "The sale has ended. See you next year!"
},
{
"type": "color",
"id": "bg_color",
"label": "Background",
"default": "#111111"
},
{
"type": "color",
"id": "text_color",
"label": "Text",
"default": "#ffffff"
}
],
"presets": [{ "name": "Countdown banner" }]
}
{% endschema %}
Sauvegardez, ouvrez l’éditeur de thème, et ajoutez Countdown banner au groupe de sections En-tête (Header) pour qu’elle s’affiche tout en haut, sur toutes les pages. Paramétrez la date, l’heure, le décalage horaire (offset), choisissez le comportement à l’expiration, et le tour est joué. Cette même méthode (placement dans le groupe de sections d’en-tête) fonctionne parfaitement pour une barre de livraison gratuite, qui se marie très bien avec un compte à rebours pour le BFCM.
Le piège de la timezone (timezone gotcha)
C’est le bug classique dans la majorité des timers copiés-collés. La valeur new Date('2026-11-30T23:59:00'), quand elle n’a pas d’offset, est parsée dans la timezone locale du visiteur. Un client de Sydney verrait ainsi votre promo se terminer 16 heures avant un client basé à Los Angeles… et il y aura toujours quelqu’un pour prendre une capture d’écran.
La section ci-dessus évite ce problème en concaténant le paramètre utc_offset à la chaîne de caractères de la date. De cette façon, 2026-11-30T23:59:00-05:00 correspond au même instant absolu, partout sur Terre. Il n’y a que deux choses à vérifier quand vous le configurez :
- Utilisez l’offset que la timezone de votre boutique aura à la date de fin, et non pas à la date d’aujourd’hui. Par exemple, fin novembre, New York est à -05:00 (EST), et non plus à -04:00 comme en été.
- Si votre promo chevauche un changement d’heure (DST), vérifiez bien l’offset pour cette semaine-là en particulier. Pour le BFCM 2026 (le Black Friday tombe le 27 novembre), le passage à l’heure d’hiver en Amérique et en Europe aura déjà eu lieu, donc c’est l’offset d’hiver qu’il faut utiliser.
Éviter le layout shift (optimisation CLS)
Une bannière injectée tout en haut de l’écran est la cause la plus classique d’un mauvais score de Cumulative Layout Shift (CLS) : dès que la bannière s’affiche, tout le reste de la page saute d’un coup vers le bas. Cette section vous en protège de deux manières différentes :
La hauteur est réservée avant même que le JavaScript ne s’exécute. Le min-height: 3.25rem sur le container principal garantit que l’espace existe dès le premier affichage (first paint). Quand le JavaScript remplit les chiffres, la mise en page (layout) reste intacte. Si vous grossissez la taille des polices, n’oubliez pas de remesurer et de mettre à jour cette hauteur minimale réservée.
Les chiffres ne peuvent pas “trembler” ni sauter. La propriété CSS font-variant-numeric: tabular-nums donne la même largeur à chaque chiffre, et le min-width: 2ch sur la boîte de chaque chiffre empêche le timer de changer de taille lorsque “10” se transforme en “09”. Sans cela, sur certaines polices, la bannière ferait un re-flow du layout toutes les secondes.
Un compromis assumé : l’option cacher à l’expiration (hide on expiry) supprime la bannière et, de fait, remonte toute la page, mais cela n’arrive qu’une seule fois. Si votre promo se termine alors que vous avez un fort trafic, privilégiez plutôt l’option du message d’expiration, qui maintient la hauteur et évite ce saut. Le layout shift peut venir de nombreux autres éléments que des bannières ; pour régler les autres problèmes de ce type, vous pouvez consulter notre guide expliquant comment corriger le CLS sur Shopify.
Accessibilité : un timer qui reste silencieux
Un compteur qui change toutes les secondes est probablement la pire chose à faire écouter en boucle à un lecteur d’écran (screen reader). Cette section a donc été construite pour annoncer l’information une seule fois, puis se taire :
- Le timer utilise
role="timer"couplé àaria-live="off". Les mises à jour ne sont donc jamais annoncées automatiquement. C’est l’inverse strict du fameuxaria-live="polite"que beaucoup de snippets utilisent aveuglément (cargo-cult), ce qui viendrait interrompre la navigation des utilisateurs de lecteurs d’écran toutes les secondes. - Les chiffres du compte à rebours sont en
aria-hidden="true". Ce qui est lu par les technologies d’assistance, à la place, est une seule phrase statique, cachée visuellement : “L’offre se termine le 2026-11-30 à 23:59.” - La seule petite animation, une transition subtile sur le changement des chiffres, est encapsulée dans une media query
prefers-reduced-motion: no-preference. Les utilisateurs ayant configuré leur appareil pour réduire les animations n’en auront donc aucune. Si vous souhaitez rajouter un effet clignotant plus tard, veillez à bien le garder à l’intérieur de cette media query.
Avant de publier
Testez le rendu de la bannière sur ordinateur et sur mobile (le retour à la ligne du texte et du timer ne se comporte pas de la même façon sur un petit écran). Configurez une date de fin passée pour vous assurer que l’état “expiré” (hide ou message) s’active correctement comme vous l’avez paramétré, puis remettez la vraie date de fin. Ouvrez la section dans l’éditeur de thème et changez une option au hasard pour vérifier que le timer redémarre bien une fois la section rechargée par l’éditeur.
Quand un page builder est-il plus approprié ?
Une simple bannière ne justifie l’installation d’aucune application ou app embed. Néanmoins, l’équation n’est plus la même si le compte à rebours s’inscrit dans un déploiement de campagne beaucoup plus vaste : une landing page dédiée au BFCM avec des timers un peu partout, des offres par paliers (tiered offers), des éléments en mouvement à travers plusieurs templates, et une équipe marketing qui a besoin d’itérer toutes les heures sans jamais toucher au code. Dans ce cas, un page builder ou un outil dédié aux campagnes de promos mérite amplement son abonnement, au moins pour le mois de novembre.
La voie du milieu, c’est ce pour quoi nous avons développé Fudge. Vous décrivez la bannière, ou l’intégralité de la section de votre campagne, en langage naturel, et Fudge l’écrit en Liquid natif, CSS et JavaScript directement dans votre thème (paramètres de section inclus) avec un diff que vous pouvez relire de bout en bout avant de publier quoi que ce soit. Vous obtenez un résultat parfait sans devoir le coder à la main et sans ajouter d’app tierce. La page de notre éditeur de boutique Shopify montre comment ça fonctionne en détail. Et en décembre, il n’y a rien à désinstaller, puisque ce n’était pas une couche applicative (app layer) au départ.
FAQ
Oui. Une bannière de compte à rebours n'est rien d'autre qu'une section custom : du code Liquid, un schema avec un paramètre de date de fin, un peu de CSS et un petit script inline. Vous copiez-collez tout cela dans le dossier sections/ via l'éditeur de code, vous l'ajoutez depuis l'éditeur de thème, et vous configurez la deadline comme n'importe quel autre paramètre de section. Pas d'abonnement, pas de script tiers qui ralentit la boutique.
Non. La barre d'annonces de Dawn peut supporter jusqu'à 12 textes rotatifs avec des liens, un défilement automatique, des icônes sociales et des sélecteurs de pays et de langue, mais aucune fonctionnalité liée au temps n'y est incluse. Pour avoir un timer dans Dawn, il faut ajouter une section custom ou utiliser le générateur de bloc IA de l'éditeur de thème (dont le support de Shopify ne prend pas en charge le debugging en cas de problème).
Parce que la chaîne de date n'a pas d'offset UTC. En JavaScript, une date de type 2026-11-30T23:59:00 est parsée selon la timezone locale du visiteur. L'échéance de votre promo peut donc varier de 24h à l'autre bout du monde. Ajoutez un offset explicite, comme -05:00, pour que la fin des soldes soit fixée à un seul instant global sur Terre.
Seulement si elle est injectée dans la page sans espace réservé. Si vous donnez à la bannière un min-height fixe en CSS, l'espace de la page s'agencera correctement dès le premier affichage (first paint). Utilisez la propriété tabular-nums accompagnée d'un min-width sur vos numéros pour que le timer n'entraîne aucun re-flow à chaque seconde. Si vous la construisez ainsi, la bannière minimisera tout layout shift.
Choisissez l'un des deux comportements et faites-en un paramètre : masquer entièrement la bannière, ou bien remplacer le timer par un message d'expiration. L'option du message est souvent préférable si la promotion se termine pendant un pic de trafic sur le site, car cela préserve la hauteur de la bannière et évite un layout shift tardif. Peu importe ce que vous choisissez, mettez réellement fin à la promo.
Non. Un timer qui se réinitialise à chaque visite habitue vos clients fréquents à complètement ignorer l'urgence. De plus, ces fausses deadlines sont désormais considérées comme des pratiques commerciales trompeuses (dark patterns) selon la réglementation sur la protection des consommateurs dans un grand nombre de pays. Utilisez de vraies dates de fin et tenez-vous-y ; c'est l'urgence honnête qui convertit et fidélise le mieux.
Ajoutez simplement la section au groupe de sections de votre En-tête (Header) depuis l'éditeur de thème, plutôt que sur un template spécifique de page. Sur un thème Online Store 2.0, les sections qui se trouvent dans le groupe En-tête s'affichent au-dessus du header sur absolument toutes les pages du site. Un seul placement couvrira tout votre site, et il suffira d'un seul clic pour la supprimer une fois la promotion terminée.
Footnotes
-
Shopify, code source du thème Dawn,
sections/announcement-bar.liquid- le schema de la section supporte des blocs d’annonces (jusqu’à 12) avec texte et lien, un auto-rotate (vitesse de 3 à 10s), un schéma de couleurs (color scheme), une ligne de séparation, des icônes sociales et un sélecteur de pays/langue ; aucun paramètre temporel ou de compte à rebours n’existe. https://github.com/Shopify/dawn/blob/main/sections/announcement-bar.liquid ↩ ↩2 -
Centre d’aide Shopify, “Générer automatiquement des blocs de thème dans l’éditeur de thème” - Shopify Magic génère un bloc de thème autonome à partir d’un prompt textuel via Ajouter un bloc → Générer, et fonctionne avec les thèmes du Theme Store sur les forfaits Essai gratuit, Basic, Growth, Advanced et Plus, et “L’assistance pour la personnalisation ou le dépannage du code des blocs générés par Shopify Magic n’est pas prise en charge par l’Assistance Shopify.” https://help.shopify.com/en/manual/online-store/themes/customizing-themes/theme-editor/shopify-magic/generate-blocks ↩