Transitions
Transition utilities animate a change of value, such as a background colour on hover, instead of switching at once. One class chooses which properties animate and brings a default duration and easing; optional classes change the duration, the easing and the delay.
How transitions work
Section titled “How transitions work”- Property classes.
transitionanimates every property;transition-colors,transition-opacity,transition-shadowandtransition-transformanimate one group.transition-noneturns transitions off. - Defaults included. Each property class also sets the default duration (
200, 200ms) and easing (in-out), so one class is enough. - Overridable defaults. The defaults sit inside
:where(), which has no specificity, soduration-*andease-*override them wherever they appear in the stylesheet. - Tokens. Durations output
var(--nu-duration-{key})and easingsvar(--nu-ease-{key}). Delays are plain times, not tokens.
| Class | Animates |
|---|---|
transition |
all |
transition-colors |
color, background-color, border-color, text-decoration-color, fill, stroke |
transition-opacity |
opacity |
transition-shadow |
box-shadow |
transition-transform |
transform |
transition-none |
Nothing (transition-property: none) |
Property classes
Section titled “Property classes”Hover each button. The colour change animates over the default 200ms.
<div class="flex wrap gap-3"> <div class="transition-colors p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer">transition-colors</div> <div class="transition-shadow p-3 rounded border shadow-sm hover:shadow-lg cursor-pointer">transition-shadow</div> <div class="transition-opacity p-3 rounded bg-brand-subtle opacity-50 hover:opacity-100 cursor-pointer">transition-opacity</div> <div class="p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer">No transition</div></div>.button { @include apply(bg(brand), transition-colors, bg(brand-hover) hover);}.card { @include apply(shadow(sm), transition-shadow, shadow(lg) hover);}Prefer a specific property class over transition. Animating all also animates properties you did not mean to animate, such as a width that changes when the layout reflows.
Default duration and easing
Section titled “Default duration and easing”The class CSS keeps the defaults in a separate :where() rule:
.transition { transition-property: all;}:where(.transition, .transition-colors, .transition-opacity, .transition-shadow, .transition-transform) { transition-duration: var(--nu-duration-200); transition-timing-function: var(--nu-ease-in-out);}.transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;}A rule inside :where() has a specificity of zero, so any class that sets transition-duration or transition-timing-function wins over the default, even one emitted earlier in the stylesheet.
Duration
Section titled “Duration”| Key | Token | Value |
|---|---|---|
0 | --nu-duration-0 | 0ms |
75 | --nu-duration-75 | 75ms |
100 | --nu-duration-100 | 100ms |
150 | --nu-duration-150 | 150ms |
200 | --nu-duration-200 | 200ms |
300 | --nu-duration-300 | 300ms |
500 | --nu-duration-500 | 500ms |
700 | --nu-duration-700 | 700ms |
1000 | --nu-duration-1000 | 1000ms |
<div class="flex wrap gap-3"> <div class="transition-colors duration-75 p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer">duration-75</div> <div class="transition-colors duration-300 p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer">duration-300</div> <div class="transition-colors duration-1000 p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer">duration-1000</div></div>.panel { @include apply(transition, duration(300));}Easing
Section titled “Easing”| Key | Token | Value |
|---|---|---|
linear | --nu-ease-linear | linear |
in | --nu-ease-in | cubic-bezier(0.4, 0, 1, 1) |
out | --nu-ease-out | cubic-bezier(0, 0, 0.2, 1) |
in-out | --nu-ease-in-out | cubic-bezier(0.4, 0, 0.2, 1) |
| Key | Use |
|---|---|
linear |
Constant speed: progress bars, spinners, colour fades |
in |
Starts slow, ends fast: elements leaving the screen |
out |
Starts fast, ends slow: elements entering the screen |
in-out |
Slow at both ends: elements moving within the screen (the default) |
<div class="group space-y-2 p-3 bg-alternate rounded cursor-pointer"> <div class="transition-transform duration-1000 ease-linear group-hover:translate-x-20 w-16 p-2 bg-brand text-on-brand rounded">linear</div> <div class="transition-transform duration-1000 ease-in group-hover:translate-x-20 w-16 p-2 bg-brand text-on-brand rounded">in</div> <div class="transition-transform duration-1000 ease-out group-hover:translate-x-20 w-16 p-2 bg-brand text-on-brand rounded">out</div> <div class="transition-transform duration-1000 ease-in-out group-hover:translate-x-20 w-16 p-2 bg-brand text-on-brand rounded">in-out</div> <div class="text-sm text-muted">Hover this panel</div></div>.menu { @include apply(transition-transform, ease(out));}Delay classes wait before the transition starts. Keys come from $delays and give plain times: delay-150 is transition-delay: 150ms. There is no delay token.
| Class | Value |
|---|---|
delay-0, delay-75, delay-100, delay-150, delay-200 |
0ms to 200ms |
delay-300, delay-500, delay-700, delay-1000 |
300ms to 1000ms |
<div class="flex wrap gap-3"> <div class="transition-colors delay-0 p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer">delay-0</div> <div class="transition-colors delay-300 p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer">delay-300</div> <div class="transition-colors delay-700 p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer">delay-700</div></div>.tooltip { @include apply(transition-opacity, delay(150));}A delay applies both when the change starts and when it reverses. Keep delays short on hover effects, or the interface feels slow.
Responsive variants
Section titled “Responsive variants”Every class on this page has a responsive variant with an @{breakpoint} suffix: transition-none@md, duration-300@lg. Variants apply from their breakpoint upwards.
<div class="transition-colors duration-1000 duration-150@md p-3 rounded bg-alternate hover:bg-brand hover:text-white cursor-pointer"> duration-1000 duration-150@md: slow on small screens, quick from md</div>| Suffix | Media query |
|---|---|
@xs | @media (min-width: 480px) |
@sm | @media (min-width: 640px) |
@md | @media (min-width: 768px) |
@lg | @media (min-width: 1024px) |
@xl | @media (min-width: 1280px) |
@2xl | @media (min-width: 1536px) |
Transition classes have no state variants: a transition is set on the element’s resting state and applies to every change.
Each class has a mixin. duration(), ease() and delay() take a key or a raw value.
@use "@nuvoui/core" as *;
.button { @include apply(transition-colors, duration(150), ease(out), bg(brand), bg(brand-hover) hover);}.button { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-duration: var(--nu-duration-200); transition-timing-function: var(--nu-ease-in-out); transition-duration: var(--nu-duration-150); transition-timing-function: var(--nu-ease-out); background-color: var(--nu-brand);}.button:hover { background-color: var(--nu-brand-hover);}The property mixins write the default duration and easing as normal declarations. Include duration() or ease() after them to change one: the later declaration wins.
| Mixin | Output |
|---|---|
transition |
transition-property: all plus the default duration and easing |
transition-colors, transition-opacity, transition-shadow, transition-transform |
Their properties plus the default duration and easing |
transition-none |
transition-property: none |
duration($time) |
transition-duration |
ease($name), ease-linear, ease-in, ease-out, ease-in-out |
transition-timing-function |
delay($time) |
transition-delay |
Keys and custom values
Section titled “Keys and custom values”| Mixin | Accepts |
|---|---|
duration() |
A $durations key (gives the token), a time in s or ms, calc() or var() |
delay() |
A $delays key (gives the time), a time in s or ms, calc() or var() |
ease() |
A $timing-functions key (gives the token), cubic-bezier(), steps(), linear(), var() or a CSS keyword (ease, ease-in, ease-out, ease-in-out, step-start, step-end) |
Keys win over keywords: ease(linear) gives var(--nu-ease-linear), while ease(ease-in) is the CSS keyword ease-in, not the in key.
.sheet { @include apply(transition-transform, duration(427ms), ease(cubic-bezier(0.34, 1.56, 0.64, 1)));}Custom timing functions
Section titled “Custom timing functions”To use a timing function in classes and tokens, add it to $timing-functions. The map replaces the default map, so include the keys you keep. A new key gets a class (.ease-spring), a token (--nu-ease-spring) and works in ease(spring).
@use "@nuvoui/core" with ( $generate-utility-classes: true, $timing-functions: ( "linear": linear, "in": cubic-bezier(0.4, 0, 1, 1), "out": cubic-bezier(0, 0, 0.2, 1), "in-out": cubic-bezier(0.4, 0, 0.2, 1), "spring": cubic-bezier(0.34, 1.56, 0.64, 1) ));Tokens in your own CSS
Section titled “Tokens in your own CSS”.drawer { transition: transform token(duration-300) token(ease-out);}Configuration
Section titled “Configuration”| Variable | Default | Purpose |
|---|---|---|
$durations |
0, 75, 100, 150, 200, 300, 500, 700, 1000 (ms) |
Duration keys, classes and tokens |
$delays |
Same keys and values as $durations |
Delay keys and classes |
$timing-functions |
linear, in, out, in-out |
Easing keys, classes and tokens |
$transition-default-duration |
"200" |
Duration key used by the property classes and mixins |
$transition-default-ease |
"in-out" |
Easing key used by the property classes and mixins |
The defaults must be keys of their maps. They also apply to hover-ready and the tooltips. The base button and field styles read the keys 200 and in-out directly, so keep those keys in custom maps while the base feature flag is on.
@use "@nuvoui/core" with ( $transition-default-duration: "150", $transition-default-ease: "out");Turn the classes off with the transitions feature flag. Tokens and mixins stay available.
Errors
Section titled “Errors”| Input | Error |
|---|---|
duration(250) |
Unknown duration: 250. Use a key (0, 75, 100, 150, 200, 300, 500, 700, 1000), a time in s or ms, calc() or var(). |
duration(fast) |
Unknown duration: fast. Use a key (0, 75, 100, 150, 200, 300, 500, 700, 1000), a time in s or ms, calc() or var(). |
delay(250) |
Unknown delay: 250. Use a key (0, 75, 100, 150, 200, 300, 500, 700, 1000), a time in s or ms, calc() or var(). |
ease(bounce) |
Unknown timing function: bounce. Use a key (linear, in, out, in-out), cubic-bezier(), steps(), linear(), var() or a keyword (linear, ease, ease-in, ease-out, ease-in-out, step-start, step-end). |
token(delay-150) |
token(delay-150): unknown token. Valid names are the --nu- custom properties in :root, without --nu-. |
$timing-functions without in-out |
$transition-default-ease: in-out is not a configured key. Valid keys: ... |
CSS variables
Section titled “CSS variables”| Variable | Description |
|---|---|
--nu-duration-{key} |
One token per key of $durations. |
--nu-ease-{key} |
One token per key of $timing-functions. |
Delays have no tokens. All transition tokens are fixed across themes.
Class reference
Section titled “Class reference”Generated from the compiled core build. Responsive variants are not listed.
Property (6 rules)
| Selector | Declarations | Default value |
|---|---|---|
.transition-none | transition-property: none | none |
.transition | transition-property: all | all |
.transition-colors | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke | color, background-color, border-color, text-decoration-color, fill, stroke |
.transition-opacity | transition-property: opacity | opacity |
.transition-shadow | transition-property: box-shadow | box-shadow |
.transition-transform | transition-property: transform | transform |
Duration (9 rules)
| Selector | Declarations | Default value |
|---|---|---|
.duration-0 | transition-duration: var(--nu-duration-0) | 0ms |
.duration-75 | transition-duration: var(--nu-duration-75) | 75ms |
.duration-100 | transition-duration: var(--nu-duration-100) | 100ms |
.duration-150 | transition-duration: var(--nu-duration-150) | 150ms |
.duration-200 | transition-duration: var(--nu-duration-200) | 200ms |
.duration-300 | transition-duration: var(--nu-duration-300) | 300ms |
.duration-500 | transition-duration: var(--nu-duration-500) | 500ms |
.duration-700 | transition-duration: var(--nu-duration-700) | 700ms |
.duration-1000 | transition-duration: var(--nu-duration-1000) | 1000ms |
Easing (4 rules)
| Selector | Declarations | Default value |
|---|---|---|
.ease-linear | transition-timing-function: var(--nu-ease-linear) | linear |
.ease-in | transition-timing-function: var(--nu-ease-in) | cubic-bezier(0.4, 0, 1, 1) |
.ease-out | transition-timing-function: var(--nu-ease-out) | cubic-bezier(0, 0, 0.2, 1) |
.ease-in-out | transition-timing-function: var(--nu-ease-in-out) | cubic-bezier(0.4, 0, 0.2, 1) |
Delay (9 rules)
| Selector | Declarations | Default value |
|---|---|---|
.delay-0 | transition-delay: 0ms | 0ms |
.delay-75 | transition-delay: 75ms | 75ms |
.delay-100 | transition-delay: 100ms | 100ms |
.delay-150 | transition-delay: 150ms | 150ms |
.delay-200 | transition-delay: 200ms | 200ms |
.delay-300 | transition-delay: 300ms | 300ms |
.delay-500 | transition-delay: 500ms | 500ms |
.delay-700 | transition-delay: 700ms | 700ms |
.delay-1000 | transition-delay: 1000ms | 1000ms |
The default duration and easing rule, :where(.transition, .transition-colors, .transition-opacity, .transition-shadow, .transition-transform), is not listed above because it is not a class selector.
Accessibility and RTL
Section titled “Accessibility and RTL”- Reduced motion. The reset sets
transition-duration: 0.01ms !importantunderprefers-reduced-motion: reduce, so every transition finishes at once for users who ask for less motion. Because the reset is a cascade layer, its!importantwins over utility classes. - Do not hide meaning in motion. A state change must be clear without the animation, for example by a colour, an icon or text.
- Keep it short. Interface transitions read best between 100ms and 300ms. Long durations and delays make controls feel unresponsive.
- RTL. Durations, easing and delays do not depend on direction. A transition on a transform animates the transform as written; mirror the transform itself for right-to-left layouts.