refract v0.1 · beta
Subsystems / Animation

Animation

Durations, easings and keyframes as tokens; motion recipes bind them into a real animation shorthand.

raw slice the input
live render ▶ replay
refracted: animation.recipes.motion.enter lowers to one class whose animation: shorthand joins a duration + easing token with a named @keyframes. Both — the keyframes and the class — are real emitted output.

Overview

The animation subsystem owns three things: motion tokens (duration / easing / delay), keyframes (a distinct Model primitive — named, ordered step lists), and animation-shorthand recipes that name a keyframe plus motion tokens and lower to one class carrying an animation: shorthand. Transitions live in the effects subsystem, not here.

Motion tokens

Regular properties (base + named variants), emitted as :root variables. Durations and delays are numbers (ms); easings are strings.

PropertyValue typeEmitted variableDescription
durationnumber (ms)--dt-animation-duration-<v>Animation durations.
easingstring--dt-animation-easing-<v>Timing functions (cubic-bezier(…), steps(…), …).
delaynumber (ms)--dt-animation-delay-<v>Start delays.

Keyframes

Keyframes are a first-class Model primitive — neither a token nor a rule-set. Author a name → a map of stops → declarations. A stop is from / to / a percentage ("50%") / a grouped selector ("0%, 100%"); authoring order is preserved. A declaration value is a literal or a token ref ({ ref: "colors.surface" }) resolved late — so a keyframe can animate a themed value. Each emits an @keyframes <name> at-rule.

animation.keyframes.ts
animation.keyframes: {
  fadeUp: { from: { opacity: 0, transform: "translateY(14px)" },
            to:   { opacity: 1, transform: "translateY(0)" } },
  pulse:  { "0%,100%": { opacity: 1 }, "50%": { opacity: 0.5 } },
}

Recipes — animation shorthand

An animation recipe names a keyframe plus motion tokens; the adapter composes the animation-* longhands into a single animation: shorthand. keyframes names a keyframe; duration / easing / delay name a token variant; the rest are literal animation-* sub-properties.

Recipe propContributesValue
keyframesanimation-namea keyframe name
durationanimation-durationa duration variant
easinganimation-timing-functionan easing variant
delayanimation-delaya delay variant
iterationCountanimation-iteration-countliteral ("infinite", 3)
directionanimation-directionliteral ("alternate", …)
fillModeanimation-fill-modeliteral ("both", "forwards", …)
playStateanimation-play-stateliteral ("paused", …)

Recipes also take states and a responsive list (with variant: swaps) — see the Concepts pages.

animation.recipes.ts
animation.recipes: {
  motion: {
    enter: { keyframes: "fadeUp", duration: "base", easing: "out", fillMode: "both" },
    beat:  { keyframes: "pulse", duration: "slow", easing: "base", iterationCount: "infinite" },
  },
}
dist/theme.css · emitted
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}
.dt-animation-motion-enter {
  animation: var(--dt-animation-duration) var(--dt-animation-easing-out) both fadeUp;
}
.dt-animation-motion-beat {
  animation: var(--dt-animation-duration-slow) var(--dt-animation-easing) infinite pulse;
}
Technical documentation for @theme-registry/refract · all output is compiled client-side by the real library (CSS adapter). The chrome is theme-aware; the render panes carry each preset's own world. · MIT licensed.