Construisez votre bannière cookies sur le catalogue de tarteaucitron
zeste charge les 247 services que gère tarteaucitron.js, comme Google Analytics, YouTube, Matomo ou Hotjar, seulement après l’accord du visiteur. Vous écrivez la bannière avec vos propres composants, en Svelte, React ou Vue. Sur un site sans framework, une balise <script> ajoute une bannière toute faite.
Les textes de la bannière existent en 36 langues. Les tests vérifient qu’aucune requête ne part vers un tiers avant le consentement.
Pourquoi ne pas garder l’interface de tarteaucitron
tarteaucitron.js fait le travail difficile. Il sait charger des centaines de services après consentement, et ses mainteneurs tiennent cette liste à jour. Son interface, elle, s’adapte mal. Il génère son propre HTML, et pour que la bannière ressemble au site, il faut surcharger son CSS, souvent avec !important.

tarteaucitronzesteÀ gauche, un site d’exemple avec la bannière et les placeholders de tarteaucitron.
À droite, le même site passé à zeste. Il utilise sa propre bannière, ses boutons et sa modale, et charge les mêmes services.
Comment zeste est construit
Le moteur ne dessine rien
zeste enregistre le choix du visiteur dans un cookie, charge chaque script une fois accepté et met à jour Google Consent Mode. Vos composants lisent son état et appellent ses actions.
Un module par service
Les 247 services viennent de tarteaucitron.js. Vous n’importez que ceux que vous utilisez, et le navigateur télécharge le code d’un service quand le visiteur l’accepte.
Les composants vous appartiennent
npx @zeste/cli add écrit une bannière et une fenêtre de préférences dans votre projet, avec votre framework et votre lib d’UI. Vous les modifiez comme n’importe quel fichier.
@zeste/cli add en écrit une première version.<ConsentBanner> et <ConsentGate>, rendu serveur et garde-fou de conformité.Installer zeste en quatre étapes
Déclarer ses services
npx @zeste/cli init lit le projet et déclare les services qu’il utilise déjà. Il reconnaît une configuration tarteaucitron et les scripts Google Analytics, Matomo et Hotjar ou les vidéos YouTube collés dans les pages. Son rapport donne le fichier et la ligne de chaque script à retirer.
import { gtag, youtube, matomocloud } from '@zeste/services';
export const services = () => [
gtag({ gtagUa: 'G-XXXXXXX' }),
youtube(),
matomocloud({ matomoId: 1, matomoHost: 'https://example.matomo.cloud/' }),
];window.zesteConfig = {
services: {
gtag: { gtagUa: 'G-XXXXXXX' },
youtube: {},
matomocloud: { matomoId: 1, matomoHost: 'https://example.matomo.cloud/' },
},
privacyUrl: '/confidentialite',
};Lire le choix côté serveur
Le serveur lit le cookie de consentement, donc le premier rendu montre déjà le bon état. Un visiteur qui a déjà choisi ne voit pas la bannière apparaître puis disparaître.
import { parseConsentCookie } from '@zeste/svelte';
export const load = ({ cookies }) => ({
consent: parseConsentCookie(cookies.get('tarteaucitron')),
});import { cookies } from 'next/headers';
import { parseConsentCookie } from '@zeste/react';
export default async function RootLayout({ children }) {
const consent = parseConsentCookie((await cookies()).get('tarteaucitron')?.value);
return <html lang="fr"><body><Consent initial={consent}>{children}</Consent></body></html>;
}// useCookie lit aussi le cookie pendant le rendu serveur
const cookie = useCookie('tarteaucitron', { decode: (v) => v });
const consent = parseConsentCookie(cookie.value);Il n’y a rien à faire ici. Le script lit le cookie dans le navigateur avant d’afficher quoi que ce soit.
Générer l’interface
La CLI détecte votre framework et votre lib d’UI. Elle propose deux styles, zeste par défaut et pelure.
npx @zeste/cli add # bannière, préférences et placeholder
npx @zeste/cli add --style pelure # le style en aplat, contours épaisLe script apporte sa propre bannière, ses préférences, ses placeholders et un bouton pour changer d’avis, dans la langue de la page. Choisissez le style avec style: 'pelure'.
Ajouter zeste à votre layout
<script lang="ts">
import { createZeste, ZesteProvider } from '@zeste/svelte';
import { messages } from '@zeste/services/i18n/fr';
import { CookieBanner, CookiePreferences } from '$lib/components/zeste';
import { services } from '$lib/consent';
let { data, children } = $props();
const zeste = createZeste({ services: services(), initial: data.consent, messages });
</script>
<ZesteProvider {zeste}>
{@render children()}
<CookieBanner />
<CookiePreferences />
<!-- un lien permanent pour changer d’avis -->
<button {@attach zeste.actions.openPreferences}>Gestion des cookies</button>
</ZesteProvider>'use client';
import { useState } from 'react';
import { createZeste, ZesteProvider } from '@zeste/react';
import { messages } from '@zeste/services/i18n/fr';
import { CookieBanner, CookiePreferences } from '@/components/zeste';
import { services } from './consent';
export function Consent({ initial, children }) {
const [zeste] = useState(() => createZeste({ services: services(), initial, messages }));
return (
<ZesteProvider zeste={zeste}>
{children}
<CookieBanner />
<CookiePreferences />
{/* un lien permanent pour changer d’avis */}
<button {...zeste.actions.openPreferences}>Gestion des cookies</button>
</ZesteProvider>
);
}<script setup lang="ts">
import { createZeste, parseConsentCookie, ZesteProvider } from '@zeste/vue';
import { messages } from '@zeste/services/i18n/fr';
import { CookieBanner, CookiePreferences } from '~/components/zeste';
import { services } from './consent';
const cookie = useCookie('tarteaucitron', { decode: (v) => v });
const zeste = createZeste({ services: services(), initial: parseConsentCookie(cookie.value), messages });
</script>
<template>
<ZesteProvider :zeste="zeste">
<NuxtPage />
<CookieBanner />
<CookiePreferences />
<!-- un lien permanent pour changer d’avis -->
<button v-bind="zeste.actions.openPreferences">Gestion des cookies</button>
</ZesteProvider>
</template><script src="/zeste.config.js"></script>
<script src="/zeste/zeste.js"></script>
<!-- n’importe où dans la page, un lien pour changer d’avis -->
<a href="#zeste">Gestion des cookies</a>Essayer la démo
La démo ci-dessous fait tourner zeste avec les composants que zeste add génère pour Bits UI, Ark UI et shadcn-svelte. Les services sont simulés. Au lieu de contacter Google ou YouTube, ils écrivent dans le journal ce qu’ils auraient chargé. La démo tourne en Svelte, et les composants React et Vue passent les mêmes tests.

Chargement de la démo…
Ouvrez « Personnaliser » pour choisir service par service, ou cliquez sur le bouton du placeholder vidéo. « Gestion des services », en bas du site, rouvre les préférences. « Casser la bannière » rapetisse le bouton « Tout refuser », et le garde-fou le signale dans le journal.
Les vidéos et les cartes attendent le consentement
<ConsentGate> affiche votre placeholder jusqu’à ce que le visiteur accepte le service, puis la vidéo ou la carte. Le serveur rend le placeholder. Ajouter une vidéo à la page ne recharge pas celles déjà en lecture.
<ConsentGate service="youtube" embed={{ videoID: 'dQw4w9WgXcQ' }}>
{#snippet placeholder({ service, allow })}
<CookiePlaceholder {service} {allow} />
{/snippet}
</ConsentGate><ConsentGate
service="youtube"
embed={{ videoID: 'dQw4w9WgXcQ' }}
placeholder={(props) => <CookiePlaceholder {...props} />}
/><!-- le balisage de tarteaucitron, inchangé -->
<div class="youtube_player" videoID="dQw4w9WgXcQ"></div><ConsentGate service="youtube" :embed="{ videoID: 'dQw4w9WgXcQ' }">
<template #placeholder="props"><CookiePlaceholder v-bind="props" /></template>
</ConsentGate>Ce que zeste applique
- zeste n’envoie aucune requête vers un tiers avant le consentement. Les tests le vérifient au niveau réseau du navigateur pour les 247 services. Les modes « sans cookie » de tarteaucitron contactent un tiers avant le consentement, par exemple avec Matomo Cloud ou le Consent Mode avancé. Ils restent désactivés tant que vous ne les activez pas.
- Dans les bannières générées, « Tout refuser » et « Tout accepter » ont le même style.
- Rien n’est pré-coché dans les préférences. Un service que le visiteur n’a pas choisi reste refusé.
- Le visiteur peut retirer son consentement à tout moment. zeste supprime alors les cookies du service, remet les vidéos derrière leur placeholder et propose de recharger la page pour les scripts déjà lancés.
- Google Consent Mode v2 tient en une ligne,
googleConsentMode(). La version balise script l’active par défaut. - Le consentement dure 13 mois. zeste le redemande quand vous changez la liste des services.
- Le RGPD demande de pouvoir montrer qu’un visiteur a consenti. zeste transmet à votre code une trace de chaque choix, avec la date, l’état de chaque service, la version de votre politique et un identifiant aléatoire gardé dans le cookie. Vous choisissez où l’envoyer, et zeste n’envoie rien lui-même.
Des avertissements pendant le développement
En développement, zeste vérifie le rendu réel de votre bannière et prévient dans la console quand elle enfreint une règle mesurable. Cela marche avec n’importe quel design system. Les messages sont en anglais, comme les autres messages destinés aux développeurs.

⚠ [zeste/consent] "Deny all" is much smaller than "Accept all" (area 24%, text 9px vs 14px). Both choices must be equally prominent.
Le garde-fou prévient aussi quand « Tout refuser » manque, est caché ou difficile à lire, et quand rien ne permet de rouvrir les préférences. Ces vérifications ne remplacent pas un avis juridique.
Garder une trace de chaque choix
createZeste({
services,
policyVersion: '2026-10',
onRecord: (record) =>
navigator.sendBeacon('/consent', JSON.stringify(record)),
});
// record = {
// id: '9f2c…', date: '2026-10-09T14:03:12Z',
// action: 'denyAll', policyVersion: '2026-10',
// choices: { gtag: false, youtube: false },
// }L’identifiant est aléatoire et lié à un navigateur. Quand le choix d’un visiteur qui revient est relu dans le cookie, rien n’est envoyé. Sans framework, mettez la même option dans window.zesteConfig.
Passer un site de tarteaucitron à zeste
Nous avons passé à zeste un site d’exemple qui a son propre design system, construit sur Ark UI. Ses visiteurs ont gardé leurs choix, parce que zeste lit le même cookie que tarteaucitron.
Sur un site sans framework, remplacez la balise <script> de tarteaucitron par celle de zeste, au même endroit. Gardez tarteaucitron.init, tarteaucitron.user et tarteaucitron.job tels quels. Les liens vers #tarteaucitron et le balisage youtube_player continuent de fonctionner.
- Retirer tarteaucitron et déclarer les mêmes services
Le site perd deux balises
<script>. La configuration, commegtagUa, passe en TypeScript. L’ancien balisage.youtube_playercontinue de fonctionner. - Lancer
npx @zeste/cli addLa bannière et les préférences générées fonctionnent tout de suite.
- Utiliser les composants du design system
Les
Attention aux composants bouton qui ne transmettent pas leurs props à l’élément HTML. Le clic ne fait alors rien, et seul le garde-fou le signale.Button,ModaletToggledu site remplacent les éléments générés. - Mettre les vidéos derrière
<ConsentGate>Le serveur rend le placeholder, dans le style du site.

Comment zeste est testé
Le projet compte 625 tests. Playwright pilote Chromium sur les apps d’exemple compilées, et rien n’est simulé dans la page. Le comportement est testé une fois par lib d’UI, l’apparence une fois par style et par thème.
| Ce qui est testé | Comment | Tests |
|---|---|---|
| Les 247 services du catalogue | Avec le réseau intercepté, aucune requête ne part avant le consentement, et chaque service a un effet visible une fois accepté. | 247 |
| GA4, YouTube, Matomo Cloud | Sur le vrai réseau, avec les vrais scripts. Les cookies _ga apparaissent à l’acceptation et disparaissent au retrait. | 6 |
| Les composants générés | Svelte, React et Vue avec 3 libs d’UI chacun. Clavier, préférences et retrait une fois par lib. Accessibilité avec axe, thème et boutons égaux dans les deux styles, en clair et en sombre. Le garde-fou une fois par framework. | 174 |
| La même app, six fois | SvelteKit, Next.js, Nuxt, et chaque framework sans serveur, dont React 18. Navigation, retrait et garde-fou partout, rendu serveur dans les trois apps qui en ont un. | 81 |
| Le site migré | Les choix faits sous tarteaucitron sont conservés et le design system fonctionne toujours. | 15 |
| La version balise script | Un site HTML sans framework. Accessibilité, clavier, retrait, garde-fou, configuration tarteaucitron reprise telle quelle, téléchargements limités aux services déclarés, trace envoyée à chaque choix. | 13 |
| La langue automatique | Un navigateur en allemand, en norvégien, et dans une langue sans traduction. Une seule langue est téléchargée. | 5 |
| Moteur, catalogue, bindings, CLI | Tests unitaires, dont @zeste/cli init sur un vieux site PHP et une vérification que les exemples correspondent à ce que génère @zeste/cli add. | 84 |
État du projet
La bibliothèque fonctionne, est testée et publiée sur npm.
Fait
- Le moteur, le catalogue complet et les textes en 36 langues.
- Les bindings Svelte 5, React 18 et 19, et Vue 3, avec ou sans rendu serveur (SvelteKit, Next.js, Nuxt).
- Une CLI qui génère les composants pour 3 frameworks et 3 libs d’UI en deux styles, et
@zeste/cli init, qui trouve les services déjà utilisés par un projet. - Une version balise script pour les sites sans framework, qui remplace tarteaucitron.js et garde sa configuration.
- La langue automatique, d’après la page puis le navigateur, parmi 36.
- Une trace de chaque choix, prête à envoyer à votre serveur comme preuve de consentement.
- Le garde-fou de conformité en développement.
- La migration depuis tarteaucitron sans perdre les choix des visiteurs.
- Les packages et la CLI publiés sur npm sous le scope @zeste.
À venir
- Des boutons de bannière qui fonctionnent sans JavaScript, pour les connexions très lentes.
- Des tests sur Firefox et Safari.
Questions
Faut-il encore charger tarteaucitron.js ?
Non. zeste reprend son catalogue de services et ses traductions, et remplace son moteur et son interface. Le script de tarteaucitron n’est jamais chargé.
Mes visiteurs vont-ils revoir la bannière après la migration ?
Non. zeste lit et écrit le même cookie que tarteaucitron, donc les choix déjà faits restent valables.
Mon design system n’utilise ni Bits UI, ni Ark UI, ni shadcn. Puis-je utiliser zeste ?
Oui. Partez de la variante générée la plus proche et remplacez les imports par vos composants. Les actions de zeste fonctionnent sur tout bouton qui transmet ses props à son élément HTML : {@attach zeste.actions.denyAll} en Svelte, {...zeste.actions.denyAll} en React et v-bind="zeste.actions.denyAll" en Vue. Un simple gestionnaire de clic sur zeste.denyAll fonctionne aussi, mais le garde-fou ne peut pas vérifier ce bouton.
zeste rend-il mon site conforme ?
Pas à lui seul, et aucun outil ne le peut. zeste applique des réglages stricts par défaut et vérifie ce qui se mesure, comme les requêtes avant consentement, la visibilité des boutons et le lien pour changer d’avis. Vos textes, votre politique de confidentialité et la finalité de chaque cookie restent à faire valider.
Quelle différence avec tarteaucitron Pro ?
tarteaucitron Pro est un service hébergé, avec un tableau de bord et un journal des consentements. zeste est une bibliothèque qui tourne dans votre projet. Pour tenir un journal, zeste transmet chaque choix à votre code avec onRecord, et vous l’envoyez à votre propre serveur.
Mon site n’a pas de framework. Puis-je utiliser zeste ?
Oui. Une balise <script> ajoute une bannière, des préférences et des placeholders, en deux styles. Sur un site qui utilise déjà tarteaucitron, remplacez sa balise et zeste lit la configuration existante.
Ça fonctionne avec React et Vue ?
Oui. @zeste/react gère React 18 et 19 et Next.js, et @zeste/vue gère Vue 3 et Nuxt. Les deux ont la même API que @zeste/svelte, et zeste add génère les composants pour Radix, Reka UI, Ark UI et shadcn. Pour les autres frameworks, le moteur (@zeste/core) et le catalogue fonctionnent sans.
Quelle est la licence ?
MIT, comme tarteaucitron.js. Le catalogue de services et les traductions viennent de tarteaucitron.js, Copyright AmauriC.