---
title: Preference dialog
description: The stock c15t preference dialog on a plain HTML site, where
  visitors change each consent category, with how it opens, which categories it
  lists, its options, keyboard behavior and styling hooks.
group: frameworks
lastModified: "2026-10-10T16:01:45+01:00"
---
## What the dialog shows

The preference dialog lists each consent category with a switch and a short
description. Visitors use it to allow some categories and not others, and to
change their choice later. `c15t.js` renders it. You add no markup.

The dialog opens only when something asks for it:

* the banner's **Customize** button;
* a [`data-c15t-action="customize"` button](/docs/frameworks/html/components/action-buttons);
* a [link to `#c15t-preferences`](/docs/frameworks/html/components/preferences-link);
* the [floating trigger](/docs/frameworks/html/components/trigger);
* `c15t.openDialog()` from your own script.

Opening it records nothing and changes no permission.

## Which categories it lists

The dialog lists `necessary` first, always on and not switchable, then the
optional categories this page uses:

* the categories on your `<script type="text/plain" data-c15t-category>` tags,
  `<iframe data-category>` embeds, `scripts` entries and network blocker
  rules;
* the categories in `data-categories` on the tag, or `consentCategories` in
  `config`.

Only categories inside the policy's scope appear. When the page names no
category at all, the dialog lists every category the policy covers. Name a
category in `data-categories` when a vendor you gate in another way, such as
a tag manager, needs it:

```html
<script
  src="https://your-project.inth.app/c15t.js"
  data-categories="measurement,marketing"
  defer
></script>
```

[Consent categories](/docs/concepts/consent-categories) describes all five.

When you declare `vendors`, each category row also lists its vendors, with a
switch per vendor. See [vendor consent](/docs/frameworks/html/vendor-consent).

## What the switches start at

Each switch shows, in order of precedence:

1. what the visitor switched in this dialog and has not saved yet;
2. the visitor's recorded choice for that category;
3. `presentation.preferences.defaults` for that category, if you set it;
4. on under an opt-out policy or for a category the policy preselects,
   otherwise off.

Switching does not save. Closing the dialog without saving drops the unsaved
switches.

## Buttons

The dialog shows Reject All, Accept All and Save Settings. Save records every
listed category as its switch shows it. Accept and reject record every listed
category at once, whatever the switches show. Each closes the dialog, and the
banner too if it was open underneath.

`presentation.preferences` sets the layout of these buttons, as
`presentation.prompt` does for the banner. The policy requires all three, so a
layout that drops one gets it back.

## Options

## Dialog options

These go under `ui.dialog`.

|Option|Type|Default|What it does|
|--|--|--|--|
|`legalLinks`|list of `privacyPolicy`, `cookiePolicy`, `termsOfService`, or `null`|none|Which configured legal links to show after the description.|
|`hideBranding`|`boolean`|`false`|Hide the "Secured by" tag at the bottom of the dialog.|

The dialog's title, description, category names and button labels come
from translations. Change them through `i18n`.

`ui: { dialog: false }` renders no dialog. The banner's Customize button and
every page hook that opens preferences then open nothing, so only turn it off
when your page renders its own dialog with [headless](/docs/frameworks/html/headless).

## Keyboard and screen readers

* The dialog is a `role="dialog"` named by its title and described by its
  description.
* By default it blocks the page. c15t moves focus to the first control, keeps
  Tab inside the dialog and stops the page scrolling. Set
  `presentation.preferences.blocking: false` to leave the page usable behind
  it.
* Escape closes it without saving, wherever focus is. That includes a
  non-blocking dialog while focus is on the page behind it. If the policy
  still owes a choice, the banner comes back. Clicking the
  backdrop does not close it.
* The dialog has no close button by design. A visitor using a mouse closes it
  by saving, accepting or rejecting.
* Each category row has a button that expands its description and a
  `role="switch"` button named after the category. The `necessary` switch is
  disabled.
* The dialog sets `lang` and `dir` from the resolved language.

## Style it

Theme tokens apply to the dialog as to the banner; see
[customize](/docs/frameworks/html/customize). For single parts, use these
`data-testid` values in `ui.css`:

|`data-testid`|Element|
|--|--|
|`consent-dialog-root`|The dialog element.|
|`consent-dialog-card`, `consent-dialog-header`, `consent-dialog-content`|The card and its sections.|
|`consent-dialog-title`, `consent-dialog-description`|The heading and body text.|
|`consent-widget-accordion-item-measurement` and the other category names|One category row.|
|`consent-widget-switch-measurement` and the other category names|One category's switch.|
|`consent-widget-vendor-list-measurement` and the other category names|One category's vendor list.|
|`consent-widget-vendor-switch-measurement-posthog`|One vendor's switch, by category and vendor id.|
|`consent-widget-footer`|The button row.|
|`consent-widget-footer-accept-all-button`, `consent-widget-reject-button`, `consent-widget-footer-save-button`|Each button.|
|`consent-dialog-branding`|The "Secured by" tag.|
|`consent-dialog-overlay`|The backdrop.|

## Check it works

1. Click a Privacy settings link. The dialog opens with focus on its first
   control.
2. Turn on **Analytics** (the `measurement` category) only and click **Save Settings**. Measurement vendors
   load and marketing vendors stay blocked.
3. Open the dialog again. **Analytics** is on and **Marketing** is off.
4. Turn Marketing on, press Escape and open the dialog again. Marketing is
   off, because the change was not saved.
