A Svelte 5 port of the Klaro cookie consent manager. Fully typed, reactive, tree-shakeable, and localized (25 languages).
pnpm add svelte-klaro
<script lang="ts">
import { Klaro, type KlaroConfigInterface } from 'svelte-klaro';
import 'svelte-klaro/styles';
const config: KlaroConfigInterface = {
acceptAll: true,
services: [
{
name: 'google-analytics',
title: 'Google Analytics',
purposes: ['analytics'],
cookies: [/^_ga/, '_gid']
},
{
name: 'hotjar',
title: 'Hotjar',
purposes: ['analytics']
}
]
};
</script>
<Klaro {config} />
The config prop accepts a KlaroConfigInterface object. Key options:
| Option | Type | Default | Description |
|---|---|---|---|
services |
KlaroServiceInterface[] |
required | Third-party services to manage |
acceptAll |
boolean |
false |
Show "Accept all" button |
mustConsent |
boolean |
false |
Block page until user consents |
hideDeclineAll |
boolean |
false |
Hide the decline button |
groupByPurpose |
boolean |
true |
Group services by purpose in modal |
storageMethod |
'cookie' | 'localStorage' | 'sessionStorage' |
'cookie' |
How to persist consent |
storageName |
string |
'klaro' |
Cookie/storage key name |
cookieExpiresAfterDays |
number |
120 |
Cookie expiry |
htmlTexts |
boolean |
false |
Allow HTML in translation strings |
noticeAsModal |
boolean |
false |
Show notice as modal overlay |
embedded |
boolean |
false |
Render inline instead of fixed |
privacyPolicy |
string | Record<string, string> |
-- | Privacy policy URL (or per-language) |
styling |
object |
-- | Theme and CSS variable overrides |
translations |
object |
-- | Translation overrides (merged with defaults) |
purposeOrder |
string[] |
-- | Sort order for purposes |
Each service in the services array:
{
name: 'google-analytics', // unique identifier
title: 'Google Analytics', // display name
purposes: ['analytics'], // groups in modal
description: 'Web analytics', // shown in modal
cookies: [/^_ga/, '_gid'], // deleted on consent withdrawal
required: false, // cannot be disabled
optOut: false, // enabled by default, user can opt out
onlyOnce: false, // execute handler only once
onAccept: (opts) => {}, // called when consent granted
onDecline: (opts) => {}, // called when consent withdrawn
callback: (consent, service) => {} // called on any change
}
English translations are bundled by default. Import additional languages from svelte-klaro/translations and pass them via the translations prop or inside config.translations:
<script>
import { Klaro } from 'svelte-klaro';
import { de, fr } from 'svelte-klaro/translations';
</script>
<!-- Via translations prop (useful when config is loaded remotely) -->
<Klaro {config} translations={{ de, fr }} />
<!-- Or via config.translations -->
<Klaro config={{ ...config, translations: { de, fr }, lang: 'de' }} />
Available languages: ca, cs, da, de, el, en, es, fi, fr, gl, hr, hu, it, nl, no, oc, pl, pt, ro, ru, sr, sr_cyrl, sv, tr, zh
Override individual keys:
const config = {
translations: {
en: {
consentNotice: { description: 'We use cookies for {purposes}.' },
purposes: { analytics: 'Analytics & Metrics' }
}
}
// ...
};
// Compiled CSS (recommended)
import 'svelte-klaro/styles';
// Or raw SCSS (requires a Sass compiler)
import 'svelte-klaro/styles/scss';
Compose themes via config.styling.theme:
const config = {
styling: { theme: ['top', 'wide', 'light'] }
// ...
};
| Theme | Effect |
|---|---|
top |
Notice at top |
bottom |
Notice at bottom (default) |
left |
Notice on the left |
right |
Notice on the right (default) |
wide |
Notice spans full viewport width |
light |
Light color scheme (default is dark) |
Override via config.styling or plain CSS:
const config = {
styling: {
theme: ['bottom'],
green1: '#0a6e5c',
'border-radius': '8px',
'font-size': '13px'
}
// ...
};
.klaro {
--green1: #0a6e5c;
--dark1: #333;
--light1: #fafafa;
--font-size: 14px;
--border-radius: 4px;
--notice-max-width: 400px;
}
Svelte 5 callback props on the <Klaro> component. onconsentchange fires once per service after the user commits (accept/decline/save), not on individual toggles in the modal. It also fires on initial load with the current consent state.
| Prop | Signature | When |
|---|---|---|
onconsentchange |
(consents, service, value) => void |
Per service after save, and on initial load |
onsave |
(manager, eventType) => void |
User saves (accept/decline/save) |
onapply |
(manager, changedCount) => void |
Consents applied to DOM |
onshow |
() => void |
Notice/modal becomes visible |
onhide |
() => void |
Notice/modal is hidden |
oninit |
(manager) => void |
Manager initialized (client-side) |
<Klaro
{config}
onconsentchange={(consents, service, value) => {
console.log(`${service}: ${value}`);
}}
onsave={(manager, eventType) => {
// eventType: 'accept' | 'decline' | 'save'
analytics.track('consent_' + eventType);
}}
/>
<script>
import { Klaro, showKlaro, hideKlaro, getManager } from 'svelte-klaro';
</script>
<!-- showKlaro() re-shows the notice, showKlaro(true) opens the modal directly -->
<button onclick={() => showKlaro()}>Cookie Settings</button>
<Klaro {config} />
Also available globally:
window.klaro.show(); // show notice
window.klaro.show(true); // show modal directly
window.klaro.hide();
window.klaro.getManager();
Load config from the KIProtect API. Two approaches:
// +layout.ts
import { loadKlaroConfig } from 'svelte-klaro';
export async function load({ fetch }) {
const config = await loadKlaroConfig('your-privacy-manager-id', { fetch });
return { config };
}
<!-- +layout.svelte -->
<script>
import { Klaro } from 'svelte-klaro';
import { de } from 'svelte-klaro/translations';
const { data } = $props();
</script>
<Klaro config={data.config} translations={{ de }} />
<Klaro klaroId="your-privacy-manager-id" />
When klaroId is set, svelte-klaro automatically submits consent receipts to the KIProtect API — matching the behavior of the original Klaro script. Each time the user saves their choices, a POST to /v1/privacy-managers/{id}/submit is sent with:
To disable this while still using klaroId for config loading:
<Klaro klaroId="your-privacy-manager-id" disableConsentTracking />
You can control whether the page pathname is included via config.records:
const config = {
records: { savePathname: false }, // default: true
// ...
};
svelte-klaro is SSR-safe. During server-side rendering it outputs nothing (the consent manager requires browser APIs). The consent notice appears on client hydration.
The original klaro ships Preact as a runtime dependency. svelte-klaro has almost no runtime framework overhead since Svelte compiles to vanilla JS:
| Minified | Gzipped | |
|---|---|---|
| Original klaro (Preact) | 216 KB | 66 KB |
| svelte-klaro (en only) | 39 KB | 12 KB |
| svelte-klaro + 3 languages | 46 KB | 14 KB |
Translations are tree-shakeable — only imported languages are bundled. Each additional language adds ~2 KB minified / <1 KB gzipped.
All types are exported:
import type { KlaroConfigInterface, KlaroServiceInterface, ConsentManager } from 'svelte-klaro';
import type { LoadKlaroConfigOptions } from 'svelte-klaro';
MIT. Based on Klaro by KIProtect.