---
title: ConsentDialog
description: Add the c15t preference dialog to an Astro site as an island that
  downloads only when a visitor opens it, and control when it loads, which
  framework renders it, how it opens and which legal links it shows.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## Add the dialog to your layout

Render one `ConsentDialog` in the layout that wraps every page:

```astro title="src/layouts/base.astro"
---
import { ClientRouter } from 'astro:transitions';
import {
	ConsentBanner,
	ConsentDialog,
	ConsentDialogLink,
	ConsentScript,
} from 'c15t/astro/components';

interface Props {
	title: string;
}

const { title } = Astro.props;
---

<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta content="width=device-width, initial-scale=1" name="viewport" />
		<title>{title}</title>
		<ConsentScript />
		<ClientRouter />
	</head>
	<body>
		<slot />
		<footer>
			<ConsentDialogLink>Privacy settings</ConsentDialogLink>
		</footer>
		<ConsentBanner />
		<ConsentDialog />
	</body>
</html>
```

`ConsentDialog` renders an empty host element on the server. The preference
dialog mounts into the page the first time something opens it:

* The banner's **Customize** button, or a "Manage preferences" or "Do not sell
  or share my data" button on a notice.
* A [`ConsentDialogLink`](/docs/frameworks/astro/components/consent-dialog-link).
* A link to `#c15t-preferences`, when the page loads with that hash.
* `openDialog()` from [`c15t/astro/client`](/docs/frameworks/astro/client-api#open-and-close-dialogs).

## Props

|Prop|Type|Default|Effect|
|--|--|--|--|
|`preload`|`boolean`|`false`|Downloads the dialog once the browser is idle, on every page that renders it|
|`legalLinks`|`('privacyPolicy' \|'cookiePolicy' \|'termsOfService')[] \|null`|None|Which links from the integration's `legalLinks` the dialog shows, the same list `ConsentBanner` takes|

## When the dialog downloads

A visitor who only accepts or rejects from the banner never downloads the
dialog. The download starts before the click, when the pointer moves over a
control that opens the dialog or keyboard focus reaches one. On a touch
screen, the tap itself starts it, so the first open can wait for the download.

With `preload`, the dialog downloads in the browser's first idle period, or
after two seconds in browsers without `requestIdleCallback`. Use it when the
first open must feel instant for every visitor. It costs a background download
on each page, including for visitors who never open the dialog.

To download the dialog from your own code, call `preloadDialog()` from
`c15t/astro/client`.

## Which framework renders it

The dialog is the `ConsentDialog` component of the framework that the
integration's `ui` option names:

|`ui`|Renders with|Astro integration to install|
|--|--|--|
|`'svelte'` (default)|`@c15t/svelte`|`@astrojs/svelte` and `svelte`|
|`'react'`|`c15t/react`|`@astrojs/react`, `react` and `react-dom`|
|`'vue'`|`c15t/vue`|`@astrojs/vue` and `vue`|

Pick the framework your site already ships, so visitors do not download a
second one for a single dialog. See
[Dialog islands and your own islands](/docs/frameworks/astro/islands).

## What the dialog contains

The dialog lists the categories the policy covers, each with a switch, and
offers Accept All, Reject All and Save Settings. `necessary` is always on. The dialog
follows the integration's `theme`, `presentation.preferences` and
`consentCategories`. With `vendors` declared, each category also lists its
vendors with a switch per vendor. See
[vendor consent](/docs/frameworks/astro/vendor-consent).

To link your policies from the dialog, define them in the integration's
`legalLinks` option and list the keys in the `legalLinks` prop:

```astro title="src/layouts/base.astro (partial)"
<ConsentDialog legalLinks={['privacyPolicy', 'cookiePolicy']} />
```

c15t passes the list to the Svelte, React or Vue dialog island. A key renders
only when the integration defines it. A link without a `label` reads as the
translated name for its type, such as "Privacy Policy" in English.

Save Settings, Accept All and Reject All record a choice and close the dialog. Turning
off a category that was allowed reloads the page, unless the integration sets
`reloadOnConsentRevoked: false`. See
[withdraw consent without reloading](/docs/frameworks/astro/scripts#withdraw-consent-without-reloading).

The dialog does not open while no policy is resolved, or under a rule that owes
no consent interface. An `openDialog()` call made while `/init` is still in
flight waits for the answer.

## Stylesheets

The page carries only the banner's rules. The first time a visitor reaches
for the dialog, c15t links the dialog's rules from
`@c15t/ui/styles/sheets/dialog.css`, plus the primitives stylesheet for the
Svelte dialog, and waits for them to load before the dialog paints. On a
Tailwind CSS 3 site, the dialog's rules come with `c15t/astro/styles.css` on
every page, and c15t links only the primitives stylesheet. With
`styles: false`, import `c15t/astro/styles.css` yourself, which holds the
dialog's rules too, and `c15t/astro/primitives.css` for the Svelte dialog. See
[load the stylesheet yourself](/docs/frameworks/astro/customize#load-the-stylesheet-yourself).

## Across `ClientRouter` navigation

The dialog host is part of `<body>`, which `ClientRouter` replaces. When a
visitor navigates with the dialog open, c15t mounts it again on the new page,
so it stays open. A stylesheet c15t linked for the dialog carries over to the
new page too.

## Accessibility

The dialog keeps the focus and keyboard behavior of its framework's
`ConsentDialog`. It opens as a modal dialog, and Escape closes it. Whether it
locks page scroll follows `presentation.preferences` in the integration
options. Every framework renders the same markup and labels, so the dialog
behaves the same whichever `ui` you pick.

## Next steps

* [ConsentDialogLink](/docs/frameworks/astro/components/consent-dialog-link)
  reopens the dialog from your footer.
* [IABConsentDialog](/docs/frameworks/astro/components/iab-consent-dialog) is
  the IAB TCF preference center.
* [Integration options](/docs/frameworks/astro/integration#ui) documents `ui`
  and `requireUIIntegration`.
