CSS is Awesome

motion · live

Animation

Every keyframe in the system reads from --duration-* and --ease, so swapping themes changes the feel — Terminal snaps, Glass floats, Press drifts — without a single code change.

Try it now: open the theme dropdown in the header (or the floating ThemePicker in the bottom-right) and watch every preview below retime in place.

Live grid

Every animation in the library, rendered as a card. One-shot animations play on hover or click; looping animations run continuously so you can read the rhythm. The utility class shown on each card is the easiest way to trigger the animation in your own markup.

Hover a card's preview area, click it, or focus it with the keyboard to replay one-shot animations. Loops never stop.

One-shot animations

cia-fade-in

normal

Opacity 0 to 1. The default reveal for content arriving after mount.

<div class="cia-anim-fade-in">…</div>

cia-fade-out

normal

Opacity 1 to 0. Pair with unmount to dismiss toasts and overlays.

<div class="cia-anim-fade-out">…</div>

cia-slide-up

normal

Rises 8px while fading in. Reach for it on dropdowns and popovers.

<div class="cia-anim-slide-up">…</div>

cia-slide-down

normal

Drops 8px while fading in. Header banners and inline alerts.

<div class="cia-anim-slide-down">…</div>

cia-slide-left

normal

Slides in from the right while fading in.

<div class="cia-anim-slide-left">…</div>

cia-slide-right

normal

Slides in from the left while fading in.

<div class="cia-anim-slide-right">…</div>

cia-scale-in

normal

Grows from 0.96 to 1 while fading in. Modal bodies, tooltip content.

<div class="cia-anim-scale-in">…</div>

cia-pop

normal

Brief beat 1 to 1.06 to 1. Notification badges, success ticks.

<div class="cia-anim-pop">…</div>

cia-wiggle

normal

Rotation jitter. Invalid-input feedback, attention nudges.

<div class="cia-anim-wiggle">…</div>

Looping animations

cia-pulse

slow

Opacity loop 1 to 0.55 to 1. Live indicators, subtle awaits.

<div class="cia-anim-pulse">…</div>

cia-spin

slow

Continuous 360 degree rotation, linear timing. Spinners, refresh.

<div class="cia-anim-spin">…</div>

cia-shimmer

slow

Sweeps a gradient across the element. Skeleton loaders.

<div class="cia-anim-shimmer">…</div>

Motion tokens

The whole animation system reads from four CSS custom properties. Swap the theme and every transition, hover, and keyframe retimes without any code change.

TokenRoleSketchbook default
--duration-fastHover, focus, button press — interactions that must feel immediate.180ms
--duration-normalEnter / exit transitions, tooltip + popover reveals.240ms
--duration-slowLooping ambients — pulse, spin, shimmer, wiggle.380ms
--easeDefault easing curve for every transition and non-linear keyframe.cubic-bezier(0.33, 0.66, 0.33, 1)

Reach for var(--duration-*) in your own SCSS or inline styles — never hard-code 180ms. A button that reads the token is a button that ships with every theme.

.my-btn {
  transition: background var(--duration-fast) var(--ease),
              color      var(--duration-fast) var(--ease);
}
.my-btn:hover {
  background: var(--brand-primary);
  color:      var(--text-inverse);
}

Utility classes

Every keyframe is exposed as a .cia-anim-<name> utility, with -fast and -slow speed variants. Three loopers — spin, pulse, shimmer — ship with an infinite iteration count built in, because that is how they are used 99% of the time.

<div class="cia-anim-fade-in"></div>
<div class="cia-anim-slide-up-fast"></div>
<span class="cia-anim-pulse">live</span>
<div class="skeleton cia-anim-shimmer"></div>

Hover utilities

Four hover primitives cover the interactions the system needs most. Each is a token-driven micro-transition — no motion values are hard-coded, so they adopt the active theme's timing curve.

ClassEffect on hoverReads from
.cia-hover-lifttranslateY(-2px) + medium shadow.--duration-fast, --shadow-md
.cia-hover-glowMedium glow via box-shadow.--duration-fast, --glow-md
.cia-hover-presstranslateY(1px) scale(0.99) — button-press feel.--duration-fast
.cia-hover-fadeOpacity dips to 0.7.--duration-fast
liftpressfade
<a class="cia-card cia-hover-lift"></a>
<button class="cia-btn cia-hover-press"></button>
<img class="cia-hover-fade" src="/thumb.jpg" alt="">

The animate() mixin

For authored components, _animations.scss exposes an animate() mixin that wires duration, easing, iteration, and reduced-motion in one call.

// signature
@mixin animate($name, $speed: normal, $delay: 0s, $iteration: 1, $fill: both, $timing: var(--ease));

// usage
.toast {
  @include animate(slide-up);
}

.spinner {
  @include animate(spin, $speed: slow, $iteration: infinite, $timing: linear);
}

The mixin validates animation names at compile time — pass an unknown name and the build fails with a list of the legal ones.

Reduced motion

Every animation on this page — both the live cards above and the utility classes you copy into your own markup — respects prefers-reduced-motion: reduce. Users with that setting enabled see the final state of each animation immediately: no fade, no slide, no pulse, no spin. Hover-driven transitions are suppressed too. Nothing is hidden, nothing is moved off-screen — the content is simply rendered without motion.

The guarantee comes from three layers in _animations.scss: the animate() mixin collapses its own animation to 0.01ms, the transition() mixin drops transitions entirely, and a global * rule defuses any animation or transition declared anywhere in the document.

// scss/_animations.scss — global safety net
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Writing your own

Theme