Skip to content

ExpressiveCSS

Browse documentation

Transitions

Animate content in and out with a few CSS classes.

View as Markdown

These classes transition your content with only CSS. Each transition has a base class that applies the styles it needs, plus state classes that control the transition.

Scale

Add the base class scale-transition to scale an element in and out. Then add scale-out to scale the element down until it is hidden. To start an element hidden, add scale-out first. Then add scale-in to scale the element up until it shows. With reduced motion requested, these state changes happen at once.

Scaling out only hides the element visually. When you use it for disclosure, move focus and manage hidden controls yourself.

<!-- Scaled in -->
<button id="scale-demo" type="button" class="button extra circle scale-transition" aria-label="Add"><span class="material-symbols" aria-hidden="true">add</span></button>
<!-- Scaled out -->
<button id="scale-demo" type="button" class="button extra circle scale-transition scale-out" aria-label="Add"><span class="material-symbols" aria-hidden="true">add</span></button>

Motion tokens

Components take their easing curves and on-scale durations from the Material 3 md.sys.motion tokens. Override one on :root to change every component that uses it, or on a subtree to change only what is inside it. Off-scale times, such as the 2.1s progress loop, stay literal.

TokenValue
--md-sys-motion-easing-standardcubic-bezier(0.2, 0, 0, 1)
--md-sys-motion-easing-standard-acceleratecubic-bezier(0.3, 0, 1, 1)
--md-sys-motion-easing-standard-deceleratecubic-bezier(0, 0, 0, 1)
--md-sys-motion-easing-emphasizedcubic-bezier(0.2, 0, 0, 1)
--md-sys-motion-easing-emphasized-acceleratecubic-bezier(0.3, 0, 0.8, 0.15)
--md-sys-motion-easing-emphasized-deceleratecubic-bezier(0.05, 0.7, 0.1, 1)
--md-sys-motion-easing-linearcubic-bezier(0, 0, 1, 1)
--md-sys-motion-duration-short1 to short450, 100, 150, 200ms
--md-sys-motion-duration-medium1 to medium4250, 300, 350, 400ms
--md-sys-motion-duration-long1 to long4450, 500, 550, 600ms
--md-sys-motion-duration-extra-long1 to extra-long4700, 800, 900, 1000ms

CSS cannot draw Material's emphasized path, so emphasized uses the standard curve, as Material's own web components do.

When the user asks for reduced motion, every duration token and every spring -duration token becomes 0s. Each component that reads them then changes state without moving. The override has no !important, so a page can opt an element back in. Off-scale literal times are not tokens. Their components cancel them in their own reduced-motion rules.

<style>
  /* Every 200ms transition runs at 150ms instead. */
  :root { --md-sys-motion-duration-short4: 150ms; }
</style>

Springs

Material 3 Expressive describes motion as springs: a damping ratio and a stiffness. CSS has no spring timing function. The framework solves each spring when it compiles the sheet and samples it into a linear() easing. That easing runs for the time the spring takes to settle. Every spring has an easing token and a -duration token. Use the two together.

TokenStandard (damping, stiffness)Expressive
--md-sys-motion-spring-fast-spatial0.9, 14000.6, 800
--md-sys-motion-spring-default-spatial0.9, 7000.8, 380
--md-sys-motion-spring-slow-spatial0.9, 3000.8, 200
--md-sys-motion-spring-fast-effects1, 3800same
--md-sys-motion-spring-default-effects1, 1600same
--md-sys-motion-spring-slow-effects1, 800same

Set motion="expressive" on the root or on any subtree to switch the spatial springs to the bouncier expressive ones. Set motion="standard" to switch a subtree back. The standard motion scheme is the default. Effects springs, for color and opacity, never overshoot and are the same in both schemes.

<style>
.panel {
  transition: translate var(--md-sys-motion-spring-default-spatial-duration)
    var(--md-sys-motion-spring-default-spatial);
}
</style>
<main motion="expressive">…</main>

In Sass, spring($damping, $stiffness) and spring-duration($damping, $stiffness) turn any spring into the same pair.

.drawer {
  transition: translate spring-duration(0.8, 380) spring(0.8, 380);
}

A linear() easing always starts from rest, so a transition interrupted part way restarts without the speed it had. This shows most on a dragged bottom or side sheet. So the sheet solves its spring when the user releases it instead. It springs back from where the finger left it, at the finger's speed, and a press while it moves catches it where it is. If a page overrides a sheet's motion tokens, the sheet keeps the CSS transition they ask for.

Each spring also has -damping and -stiffness tokens that hold its two numbers and follow motion. Scripts that solve a spring themselves can read them.

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow