In c15t 3.2, consent no longer makes your scripts late. When a visitor already gave consent, the scripts they allowed now start before the page hydrates.
This release also adds a shadcn registry, 18 new languages and 29 new consent-gated integrations. Each visitor now gets a subject ID on the first page load, and the backend records consent only from the sites you trust.
Allowed scripts start before hydration
When a visitor already gave consent, c15t starts their allowed scripts from the server HTML. The scripts do not wait for hydration. This works in Next.js, TanStack Start, Nuxt and SvelteKit.
Vendor helpers in @c15t/integrations that support early starts run before hydration. Other helpers load after hydration, as before.
Nothing starts early in two cases:
- The policy asks the visitor to make a choice.
- The page comes from prerendered or cached HTML.
To turn the early start off, set serverStart to false:
const options = {
scriptLoader: { serverStart: false },
};Browser manifest mode also does less work. With manifest({ resolve: 'browser' }), c15t does not call /manifest when the server state already holds the visitor's policy. It also skips the policy resolver, which is about 20 KB gzipped.
See Start allowed scripts before hydration.
Read how we built it in We built a cookie banner that makes your scripts start sooner.
Build the banner with shadcn
Your consent banner can now use the same components as the rest of your app. Add it with the shadcn CLI:
npx shadcn add @c15t/next-consent-managerEach item renders the shadcn components in your project. You own the markup and can change it. c15t keeps the consent logic, so you still get its fixes when you update c15t.
| Item | What it gives you |
|---|---|
consent-manager | The provider setup for your framework |
consent-banner | The banner, also as a consent wall that blocks the page |
consent-dialog, consent-sheet | The preference center in a dialog or a sheet |
consent-preferences | The category and vendor switches |
consent-dialog-trigger | A button that floats on the page and opens the preference center again |
consent-widget | The preference center inline on a page |
consent-gate | A gate that holds an embed until the visitor accepts its category |
consent-branding | The "Secured by" link and the trigger mark |
Each item has a next-, react- and tanstack-start- version. The registry has a build for all 27 shadcn styles, so Radix, Base UI and React Aria projects all work.
The registry has no IAB TCF items yet. Under an IAB policy, the TCF banner from <IABProvider> shows instead of the shadcn banner.
Follow the Next.js shadcn guide to connect your Inth project and add the components.
Build your own banner. The registry items use public headless hooks. A banner in any design system can use them too.
useBannerCopy() and useConsentLabels() return the same copy and button labels as the stock banner, in the active language. useConsentGate(), useScrollLock(), useConsentTextDirection() and StreamedConsentSurface cover the rest of the stock UI. See headless UI.
Vue, Svelte and the script tag get the same copy as plain functions: resolvePromptCopy, resolveActionLabels and resolvePreferenceControlLabels.
Show the banner in 53 languages
More of your visitors can now read the banner in their own language. c15t ships consent copy in 53 languages, up from 35 in 3.1. The new languages are:
- Arabic (
ar), Urdu (ur) and Yiddish (yi), which read right to left - Bangla (
bn), Haitian Creole (ht), Korean (ko) and Russian (ru) - Japanese (
ja), Thai (th), Malay (ms), Vietnamese (vi) and Turkish (tr) - Tamil (
ta), Telugu (te), Marathi (mr), Kannada (kn), Malayalam (ml) and Punjabi in Gurmukhi script (pa)
With these, c15t covers all 12 languages that New York State's language access law names for state agencies.
manifest() loads each language on demand, and the backend serves them from /init. To bundle them, import one language or all of them:
import '@c15t/translations/ko';
// or every language
import '@c15t/translations/all';Banners, dialogs and preference centers also read right to left for Central Kurdish (ckb), Uyghur (ug), Syriac (syr) and N'Ko (nqo). A script subtag now sets the direction, so ku-Arab reads right to left. See translations.
Connect 29 more tools
New consent-gated helpers in @c15t/integrations cover 29 tools:
| Group | Integrations |
|---|---|
| Analytics and visitor identification | FullStory, Gainsight PX, Lucky Orange, Pendo, Statable, RB2B, HubSpot, Default and Clay |
| Experiments and personalization | Adobe Target, Appcues, Dynamic Yield, Optimizely Web Experimentation and Wisepops |
| Email and CRM | ActiveCampaign, Attentive, Customer.io, Insider, Marketo Munchkin, Oracle Eloqua and Salesforce Account Engagement |
| Ads and pixels | AdRoll, Amazon Ads, LiveIntent, The Trade Desk and Vector |
| Chat and support | Ada, Gorgias Chat and Zendesk Messaging |
Default has separate Pixel and Forms SDK helpers. Choose one per page.
Three integrations from earlier releases also changed:
- Databuddy now waits for measurement consent. It also supports optional session replay and tracking feature switches. See the Databuddy guide.
- Shopify.
createShopifyCustomerPrivacysends each c15t choice to Shopify's Customer Privacy API. Hydrogen holds analytics events until the visitor answers the c15t banner. See the Shopify guide. - Google Tag Manager. A community template loads the c15t script tag from GTM and sends each choice to Consent Mode. See the Google Tag Manager guide.
Get a subject ID from the first page load
snapshot.subject.subjectId now exists before the visitor makes a choice. The first save keeps that ID. c15t also keeps it when a visitor clears their choices or revokes consent.
The ID alone grants no consent, so the banner still shows.
Next.js, TanStack Start, Nuxt, Astro and SvelteKit can set the c15t-sid cookie when the visitor first loads a page. The server and the browser then start with the same ID. Other servers can use resolveRequestSubject() from c15t/server.
identify() now works before the first consent save. The backend creates the subject and links the identity in one transaction. When a visitor acknowledges a notice that offers only necessary categories, c15t also keeps their ID across reloads.
See subject IDs.
Record consent only from sites you trust
The backend now records consent only for sites in trustedOrigins. A save that names any other site gets 400 DOMAIN_NOT_ALLOWED and writes nothing. In 3.1, any site could create a row in the domain table.
Only the host of an entry counts, so localhost:3000 covers localhost. * still covers every site. Without trustedOrigins, the backend does not check saves.
identityToken.requireVerifiedLinks is a new option, off by default.
When you turn it on, the backend stores a link to your user ID only when an API key or identity token proves it. Without proof, identify() gets 401 IDENTITY_TOKEN_REQUIRED, and the backend records the consent save without the user ID.
Other changes
Keep fixed elements still during a scroll lock. A banner or preference dialog that blocks the page can add space for the scrollbar. c15t now exposes that width as --c15t-scroll-lock-gap.
Add it to the right offset or padding of fixed headers and chat buttons. The padding also accounts for CSS zoom.
.site-header {
position: fixed;
inset-inline: 0;
top: 0;
padding-right: var(--c15t-scroll-lock-gap, 0px);
}
.chat-button {
position: fixed;
right: calc(1rem + var(--c15t-scroll-lock-gap, 0px));
bottom: 1rem;
}Svelte and SvelteKit through c15t. Install c15t and import from c15t/svelte, as you do for the other frameworks. SvelteKit helpers are at c15t/svelte/kit, server resolution is at c15t/svelte/server and the Vite plugin is at c15t/svelte/vite. c15t setup writes these imports for you.
offline() is now local(). The new name makes it clear that choices stay in the browser.
Development builds now show the limits of local mode once per page. To hide that message, set acknowledgeNoRecords:
import { local } from 'c15t/react';
const mode = local({ acknowledgeNoRecords: true });offline() still works until v4.
Clearer backend errors. When the backend refuses a call, the error now says why. In 3.1, an error over HTTP/2 could stop at /init responded 402.
Now the error includes the backend's message and code, with status and code properties to match.
After a 402 response, c15t no longer sends the consent save again. The choice stays in the browser.
Fixes
- A preference center that a visitor opens before the backend answers now stays open when the answer arrives.
- Under an IAB policy, c15t now creates the IAB CMP once per page load. In 3.1, it could create the CMP up to 100 times.
- The backend now records the tenant on
identify_useraudit entries. - The script-tag banner and DevTools panel stay visible on pages whose CSS hides empty elements, such as Shopify's Dawn theme.
- The c15t tab now supports Nuxt DevTools 4. Popup windows work too.
- The dialog close-button offset and the small and large trigger labels now scale with the visitor's text size setting.
- The backend now accepts consent timestamps up to 10 minutes ahead of its clock and stores them unchanged. A visitor clock that runs slightly fast no longer makes a consent save fail.
- Invalid identity links return a validation error before the backend writes anything. A retry of an unchanged link no longer adds duplicate audit entries.
Check these details before you upgrade
- Packages: Update
c15tand all the@c15t/*packages you use to 3.2 together. trustedOrigins: If you set it, make sure it lists every site that saves consent. Also list the site that hosts the backend. In 3.1, a same-origin save worked without it. In 3.2, a save from a site not on the list gets400 DOMAIN_NOT_ALLOWED. c15t keeps a refused save and retries it up to 10 times over 7 days. If you add the site in that time, the backend still records its consent.- Cookie policy: If it lists c15t cookies, add
c15t-sidandc15t-notice-subject. Their prefixes follow your customstorageKey. - Shared caches: If you use the Next.js proxy or SvelteKit handle, set
subjectCookie: falsefor routes whose responses enter a shared cache. Also turn off server subject cookies when browser persistence is off or aconsentSourceowns consent. Give the server helper and the browser the same storage settings. - Your own API calls: If your code calls
POST /subjectsorPATCH /subjects/:id, sendContent-Type: application/json. Both routes now answer other body types with415 UNSUPPORTED_MEDIA_TYPE, as 2.x did. A save'sdomainmust also be 1 to 253 characters with no control characters. The c15t clients and@c15t/node-sdkalready meet both rules. - Right-to-left banners: The banner now stays in its corner for right-to-left languages. In 3.1, a
bottom-leftbanner moved to the bottom right for Hebrew. To put it on the right for right-to-left pages, setposition. - Kurdish: Plain
kunow reads left to right, because it usually means Kurmanji. For Sorani, useckborku-Arab.
Read the complete 3.2.0 release notes for every link and detail.
