---
title: Motion and animation
description: Change how fast c15t's banner and dialog animate with duration and
  easing tokens, turn animations off per surface with disableAnimation, and
  check reduced-motion behavior in each framework.
group: customization
lastModified: "2026-10-10T16:01:45+01:00"
---
## Change the speed with motion tokens

c15t animates its surfaces with three durations and five easing curves. Set
them in the theme's `motion` object, or as CSS variables:

|CSS variable|Theme key|Default|Used by|
|--|--|--|--|
|`--c15t-duration-fast`|`motion.duration.fast`|`80ms`|Banner fade out, buttons, tabs|
|`--c15t-duration-normal`|`motion.duration.normal`|`150ms`|Preference dialog, late banner fade in, switches, accordions|
|`--c15t-duration-slow`|`motion.duration.slow`|`200ms`|Legal links, floating trigger|
|`--c15t-easing`|`motion.easing`|`cubic-bezier(0.4, 0, 0.2, 1)`|Switches, accordions|
|`--c15t-easing-out`|`motion.easingOut`|`cubic-bezier(0.215, 0.61, 0.355, 1)`|Preference dialog, late banner fade in, floating trigger hover|
|`--c15t-easing-in`|`motion.easingIn`|`cubic-bezier(0.55, 0.055, 0.675, 0.19)`|Banner fade out|
|`--c15t-easing-in-out`|`motion.easingInOut`|`cubic-bezier(0.645, 0.045, 0.355, 1)`|Floating trigger snapping to a corner|
|`--c15t-easing-spring`|`motion.easingSpring`|`cubic-bezier(0.34, 1.56, 0.64, 1)`|The dialog's scale in|

This theme slows the dialog down and removes its overshoot:

```ts title="consent-theme.ts"
export const theme = {
	motion: {
		duration: { fast: '120ms', normal: '220ms' },
		easingSpring: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
	},
};
```

The banner never slides in. When it shows with the page, it is part of the
first paint and appears on its first frame. When it arrives more than 100ms
after the page first painted, such as after a slow script or a client-side
init, it fades in over `--c15t-duration-normal` instead, and its root and
backdrop carry `data-entry="late"`. A banner rendered on the server always
shows at once. When it hides, Vue, Svelte and the script tag fade it out over
`--c15t-duration-fast`.

The theme goes where your framework's other tokens go. See
[theme tokens](/docs/customization/tokens) and your framework's customize page.
In Vue and Nuxt, set `tokens` with the variable name without the leading
`--`, such as `'c15t-duration-normal': '220ms'`.

## Turn animations off

`disableAnimation` removes the banner's fades, the enter and exit
transitions of the dialogs, and the hover and snap transitions of the floating
`ConsentDialogTrigger`. Set it once for every surface, then override it for one
surface where your framework allows:

|Framework|For every surface|For one surface|
|--|--|--|
|Next.js, TanStack Start|`options.disableAnimation` in Next.js `c15t.config.ts`, or on `ConsentRoot`|`disableAnimation` prop on `ConsentBanner`, `ConsentDialog`, `IABConsentBanner`, `IABConsentDialog`|
|React|`disableAnimation` in the `ConsentProvider` options|The same component props|
|Nuxt|`disableAnimation` in the `c15t` module options|`disableAnimation` prop on `consent-banner.vue`, `consent-manager.vue` and the IAB banner and dialog, when you render them yourself|
|Vue|`disableAnimation` in the `c15tVue` options|The same component props|
|Astro|`disableAnimation` in the `c15t()` integration options|`disableAnimation` prop on `ConsentBanner`, `ConsentDialog`, `IABConsentBanner`, `IABConsentDialog`|
|Svelte, SvelteKit|`disableAnimation` on `ConsentProvider` (Svelte) or `ConsentRoot` (SvelteKit)|`disableAnimation` prop on `ConsentBanner`, `ConsentDialog`, `IABConsentBanner`, `IABConsentDialog`|
|HTML|`data-disable-animation` on the script tag, or `ui.disableAnimation` in `config`|`ui.banner.disableAnimation`, `ui.dialog.disableAnimation`|
|JavaScript|`ui.disableAnimation` in `init()`|`ui.banner.disableAnimation`, `ui.dialog.disableAnimation`|

A value on one surface wins over the value for every surface.

Vue's `ConsentRoot` renders the banner and dialog without props, so in Vue and
Nuxt a per-surface value only applies when you render the surface components
yourself.

## Follow the visitor's reduced motion setting

c15t's stylesheet stops the banner, dialog, floating trigger, switches, tabs
and accordions from animating while the visitor asks for reduced motion. The
rules sit in a `prefers-reduced-motion: reduce` media query, so they apply in
every framework and follow the setting as it changes, with no option to set.

`disableAnimation: false` does not bring the animations back for these
visitors, because the stylesheet rule applies whatever the option says.

## Animate your own rules on dialog state

The preference dialog marks its open state with `data-state`, `open` or
`closed`, so a rule can animate your own additions to it. Which element
carries the attribute depends on the framework:

|Framework|Elements with `data-state`|
|--|--|
|HTML, JavaScript|Dialog overlay, positioner and content|
|Svelte, SvelteKit|Dialog backdrop, positioner and content|
|Vue, Nuxt|Dialog content and overlay. The dialog unmounts when it closes, so you only see `open`|
|React, Next.js, TanStack Start|Not on `ConsentDialog`. The `Dialog` primitive's trigger, overlay, content and close parts carry it|

Banners do not carry `data-state`. Read `data-prompt`, `data-variant` and the
other attributes in [component parts](/docs/customization/slots) instead.

## Stop transitions while you switch themes

Every c15t stylesheet has a `c15t-no-transitions` class that sets
`transition` and `animation` to `none` on an element and its children. Add it
to `<html>` while your app swaps themes, so colors change in one frame. Force
a style and layout pass before you remove it. Otherwise the browser computes
the new theme only after the class is gone, and the change animates:

```ts
const root = document.documentElement;
root.classList.add('c15t-no-transitions');
applyYourTheme();
// Reading layout applies the new theme while transitions are off.
root.getBoundingClientRect();
root.classList.remove('c15t-no-transitions');
```

This assumes `applyYourTheme` changes classes or custom properties
synchronously. If your framework applies the theme in a later render, run the
last two lines after that render commits.

## Check the result

1. In DevTools, open the Rendering panel and emulate
   `prefers-reduced-motion: reduce`. Reload with site data cleared.
2. The banner appears without sliding in.
3. Open the preference dialog and toggle a switch. The switch changes without
   animating.
4. Turn the emulation off, set a slower `motion.duration.normal`, and open the
   dialog again. It fades in at the new speed.
