Skip to main content

Svelte Components

ConsentDialog

Render the dialog

Render ConsentDialog once, inside ConsentProvider. The banner's Customize button and ConsentDialogLink open it:

src/App.svelte
<script lang="ts">
	import { posthog } from '@c15t/integrations/posthog';
	import {
		ConsentBanner,
		ConsentDialog,
		ConsentDialogLink,
		ConsentProvider,
		manifest,
	} from '@c15t/svelte';

	const scripts = [
		posthog({
			id: 'phc_your_project_key',
			initOptions: { cookieless_mode: 'never' },
			loadMode: 'after-consent',
		}),
	];
</script>

<ConsentProvider mode={manifest()} {scripts}>
	<main>
		<h1>c15t + Svelte</h1>
		<p>Your app goes here.</p>
	</main>
	<footer>
		<ConsentDialogLink>Privacy settings</ConsentDialogLink>
	</footer>
	<ConsentBanner />
	<ConsentDialog />
</ConsentProvider>

How the dialog loads

ConsentDialog is not part of the first page load. The component you render is a small loader; the dialog itself is a separate chunk that it imports:

  • In browser idle time after the page's load event, while a button that opens the dialog is mounted. Those buttons are the banner's Customize button, ConsentDialogLink, ConsentDialogTrigger, the ConsentGate placeholder and ConsentButton with action="open-consent-dialog".
  • When such a button is hovered or focused.
  • At the latest, when the dialog opens.

Set the provider's preloadDialog to 'intent' to skip the idle load and load only on hover, focus or open. Idle loading is also skipped when the browser asks to save data, on 2G connections and offline. Every ConsentDialog on the page shares one import. If the import fails, the surface the dialog replaced comes back, and the next hover, focus or open retries. The dialog's CSS travels with that chunk and goes into <head> before the dialog renders. With styles={false}, it comes from your imported @c15t/svelte/styles.css.

Props

PropTypeDefaultBehavior
openbooleanfollows the consent stateControls visibility yourself. While open is true, the dialog stays open, even after Escape.
showTriggerboolean or trigger propsfalseRenders a floating ConsentDialogTrigger with the dialog. Pass an object for defaultPosition, persistPosition, showWhen, size, ariaLabel, noStyle and class.
legalLinksarray of LegalLinks keys, or nullevery configured linkWhich of the provider's legalLinks to show after the description.
hideBrandingbooleanfalseHides the "Secured by" tag.
modelsModel[]['opt-in', 'opt-out', 'iab', 'none']Policy models the dialog opens for. none is included so a policy with no prompt but a right to change preferences still opens it.
noStylebooleanprovider's noStyleDrops c15t's classes.
classstringnoneExtra class on the dialog content.

ConsentDialog has no text props. Its title and description come from the consentManagerDialog translations; see translations.

With showTrigger, the dialog loads when it mounts, because the trigger needs it at once. The trigger appears right after hydration. To keep a trigger in server HTML, render ConsentDialogTrigger next to the dialog instead.

What the dialog contains

The dialog renders the heading, the description with legal links, and a ConsentWidget with one switch per category and the save buttons the policy allows. Categories come from the provider's consentCategories, limited to the policy's scope. necessary is always on and cannot be switched off.

Switches change an unsaved draft. The save button records the draft; the accept and reject buttons record every category at once. The dialog closes in the same task the choice is recorded. If the policy changes while the dialog is open, the widget shows an alert and asks the visitor to review before saving.

Open and close the dialog

Anything that sets the active surface to 'dialog' opens it: the banner's Customize button, ConsentDialogLink, ConsentDialogTrigger, getConsentManager().setActiveUI('dialog') or getHeadlessConsent().openDialog().

Escape closes the dialog. Clicks outside it do not, so a visitor cannot lose unsaved switches by accident. Closing without saving records nothing. If the policy still owes a choice, the banner comes back.

With the open prop set, your component owns visibility. Escape then moves the active surface off 'dialog', to 'banner' while a choice is still owed or 'none' otherwise, and the dialog stays mounted until you set open to false. Watch getConsentManager().activeUI to close it.

Accessibility

  • The dialog content is labelled by its title (aria-labelledby) and described by its description (aria-describedby).
  • While the dialog is blocking, which is the default, focus is trapped inside it and the page behind does not scroll. The provider's presentation can turn blocking off for the dialog.
  • Each category switch is a switch role labelled with the category title.
  • The root sets dir from the active language.

Style the dialog

ConsentDialog reads these theme slots: consentDialog, consentDialogCard, consentDialogHeader, consentDialogTitle, consentDialogDescription, consentDialogContent and consentDialogTag. The widget inside it reads the ConsentWidget slots. The content element carries data-testid="consent-dialog-root" and data-blocking="true" while blocking.