Docs
Next

Motion tokens

The `easings` and `transition` collections and how components consume them.

On this page

Systhema ships a built-in table of easing curves, and it stays one. Two optional token collections put motion in Figma's hands on top of it:

CollectionsLink to this section

CollectionHoldsEmitted as
easings$type: 'easing' curves and $type: 'duration' speeds at its root--ease-<name minus its leading "ease"> and --duration-<name>
transitionPer-component values, one branch per component (button.primary.* today)--transition-<kebab dot-path>

So easeOutCubic becomes --ease-out-cubic, defaultTransitionSpeed becomes --duration-default-transition-speed, and button.primary.transitionDuration becomes --transition-button-primary-transition-duration. All three land on :root in the base layer, from the same plugin — there is exactly one declaration site for --ease-*.

What Systhema's own export ships is easings, curves only. There is no shipped transition collection and no shipped duration: those are values a project's designer authors, and generating them would hand every project a motion language nobody chose. Both are read whenever a project's Figma file declares them — export a transition collection or a Timing variable and the custom properties appear, with no code change on either side. Until then every component computes the fallback written into its own var(--transition-…, <initial>) reference, which is what it computed before the collections existed.

A transition token that aliases an easings one keeps the reference: the variable is written as var(--ease-out-cubic) / var(--duration-default-transition-speed), mapped by the target token's declared type, not by its name.

The built-in fallbackLink to this section

The built-in table is the fallback, per curve. A project's easings collection is merged OVER it, so:

  • no easings collection at all → exactly the built-in curves, unchanged;
  • a collection that declares easeOutCubic → that curve wins for both .ease-out-cubic and --ease-out-cubic;
  • a collection that is missing one (the shipped export has no easeInOutQuad) → the built-in curve still ships.

Wiring a componentLink to this section

Only the button reads these today. The pattern for any other component is the same three declarations, each with the value the component computed BEFORE the collection existed as its fallback:

transitionProperty: `var(--transition-button-${type}-transition-property, color, background-color)`,
transitionDuration: `var(--transition-button-${type}-transition-duration, 0s)`,
transitionTimingFunction: `var(--transition-button-${type}-transition-timing-function, ease)`,

0s and ease are the CSS initial values, so a project with no transition collection — which is every project that has not exported one — computes exactly what it did before. A consumer utility (duration-300 ease-out-cubic) still wins over all of it: utilities are unlayered inside utilities, and these sit in the utilities.systhema sub-layer.

Note that a transitionProperty token replaces the curated property list outright.