Skip to content

Motion

Quick to respond, soft to settle. Motion explains what changed and never makes the reader wait.

Principles

Respond at once

Pressed controls react within 100 milliseconds. Feedback never waits for a network round trip.

Settle softly

Entrances start quickly and ease into place. Exits are shorter than entrances and accelerate away.

Physical when touched

Springs move what the reader drags, toggles, or opens. Color and opacity never overshoot.

Quiet at rest

Looping motion means an AI is working. Idle interfaces hold still so real activity stands out.

Easing

Select a curve to play it. Springs are sampled from a physical model into CSS linear() curves, and each has a paired duration equal to its settling time.

Duration

Smaller movements are faster. Exits use the next shorter step than their entrance.

--duration-instant

Press feedback

100ms
--duration-fast

Hover, color, and exits

160ms
--duration-base

State changes and popovers

240ms
--duration-slow

Sheets, dialogs, and layout

360ms
--duration-deliberate

Reveals and first paint

560ms
--duration-ambient

Shimmer and breathing loops

2000ms

Animations

Shared keyframes for entrances and ambient loops.

Fade in

animate-fade-in

Rise in

animate-rise-in

Blur in

animate-blur-in

Scale in

animate-scale-in

Breathe

animate-breathe

Float

animate-float

In CSS

The theme overrides Tailwind's ease-out, ease-in, and ease-in-out, and adds ease-spring variants, so every component shares the same curves.

<!-- Utilities read the theme tokens -->
<div class="transition-transform duration-(--duration-base) ease-out hover:-translate-y-0.5">
<div class="transition-[scale] duration-(--duration-spring) ease-spring data-[state=open]:scale-100">

<!-- Entrances, staggered by index -->
{#each items as item, index (item.id)}
  <li class="animate-rise-in stagger" style="--index: {index}">{item.title}</li>
{/each}

<!-- Working state -->
<span class="text-shimmer animate-shimmer">Searching the web</span>

In Svelte

Install the Motion item for transitions, scroll reveals, pointer effects, and JavaScript copies of every token.

<script lang="ts">
  import { fade } from 'svelte/transition';
  import { blurIn, pop, reveal, magnetic, duration, stagger } from '$lib/components/ui/motion';
</script>

<!-- Svelte transitions that fall back to a crossfade for reduced motion -->
{#if answer}
  <p in:blurIn>{answer}</p>
{/if}
{#if open}
  <div in:pop out:fade={{ duration: duration.fast }}>...</div>
{/if}

<!-- Reveal children in sequence the first time they scroll into view -->
<ul {@attach reveal({ children: true, stagger })}>...</ul>

<!-- Lean toward a fine pointer -->
<button {@attach magnetic({ strength: 0.3 })}>Generate</button>

Reduced motion

When a reader turns on reduced motion, the theme finishes every CSS animation and transition immediately. Svelte transitions from the Motion item become a 160 millisecond crossfade, so a change is still visible without movement.

Scroll reveals never hide content, pointer effects switch off, and ambient loops such as the voice orb stop drawing frames. Streaming text shows the complete answer at once.