> ## Documentation Index
> Fetch the complete documentation index at: https://substrate.docs.unknowncreatives.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuring Motion Duration and Easing in Substrate

> A brand declares one base duration and one easing curve. The runtime scales duration by a continuous motion factor that users control and brands bound.

Motion in Substrate is two authored values and one runtime multiplier. There is no duration ladder — no `fast`, `normal`, `slow` set to choose between — because a discrete ladder cannot express a user sitting halfway between full motion and none. A brand declares its base duration and its curve; the runtime scales the duration continuously.

## The motion block

Both fields are required. An example, from the fictional Acme brand's `config.global.yaml`:

```yaml theme={null}
motion:
  duration-base: 200
  easing: "cubic-bezier(0.25, 0.1, 0.25, 1)"    # ease — refined, not flashy
```

* **`duration-base`** — a bare number in milliseconds. This is the brand's full-strength transition duration.
* **`easing`** — a CSS easing function, as a string. It reaches the browser as `--easing` on the brand selector.

Different brands can sit far apart on both, which is the point — motion is brand character, not a system default. Here is how a fictional airline family, Aurora Airways, might spread its sub-brands:

| Brand            | `duration-base` | `easing`                           |
| ---------------- | --------------- | ---------------------------------- |
| Aurora (family)  | `75`            | `linear`                           |
| Aurora Cargo     | `100`           | inherits `linear`                  |
| Aurora Premium   | `250`           | `cubic-bezier(0.25, 0.1, 0.25, 1)` |
| Aurora Corporate | `300`           | `cubic-bezier(0.25, 0.1, 0.25, 1)` |
| Acme             | `200`           | `cubic-bezier(0.25, 0.1, 0.25, 1)` |

Aurora's 75ms linear is a deliberate choice for a transactional booking flow; Aurora Corporate quadruples it to 300ms and swaps in an eased curve for a slower, more formal surface. Cargo overrides only `duration-base` and keeps the family's `linear` easing — see [Inheritance](/multi-brand/inheritance) for how partial overrides merge.

## The continuous model

The authored base is emitted per brand, and one global expression scales it. The shape of a brand's generated `css/brand.gen.css`:

```css theme={null}
@layer ucs.tokens {
  [data-brand="aurora-rewards"] {
    --duration-base: 75ms;
    --easing: linear;
  }
}
```

And from `generated/global/css/tokens.gen.css`:

```css theme={null}
:root {
  --duration: calc(var(--duration-base) * var(--motion-factor));
}
```

That is the entire motion system. Components reference `--duration` (through `--surface-transition-duration`) and `--easing`; nothing references a named speed tier, because none exists.

## The motion factor

`motionFactor` is one of the seven axes on the runtime preference vector, running from 0 (instant) to 1 (the brand's full authored duration). Two things about it are worth knowing:

**It defaults to 0.75, not 1.** The shipped default preference set deliberately runs motion at three-quarters strength. A brand that authors `duration-base: 200` sees 150ms out of the box.

**It is user-adjustable, within brand-set bounds.** The `flexibility.motion` block is the brand's policy on how far the slider may travel:

```yaml theme={null}
flexibility:
  motion: { min: 0, max: 1 }
```

Every bundled demo brand allows the full range. A brand could narrow it — `{ min: 0.5, max: 1 }` would prevent users from disabling motion entirely — though allowing 0 is what makes the reduced-motion path work.

## Reduced motion

Substrate does not emit a `prefers-reduced-motion` media block. The handling lives in the web runtime, which reads the media query and sets `motionFactor` to 0:

```ts theme={null}
const mql = window.matchMedia('(prefers-reduced-motion: reduce)');
if (mql.matches) {
  prefs.motionFactor = 0;
}
```

Because `--duration` is `--duration-base` times the factor, zeroing the factor zeroes every duration in the system at once — no per-token overrides, and no component-level checks. The runtime also listens for changes, so a user toggling the OS setting with the page open takes effect immediately rather than on next load.

<Note>
  Handling reduced motion through the preference vector rather than a media query is what lets the same mechanism serve a user who wants *less* motion rather than none. A media block is binary; the factor is continuous, so 0.3 is a reachable state.
</Note>

<Tip>
  If a transition feels wrong across the whole brand, change `duration-base` — it moves everything coherently. If one interaction feels wrong, the fix belongs in that component's descriptor, not in a new brand-level duration value.
</Tip>

<CardGroup cols={2}>
  <Card title="Brand Config Overview" icon="sliders" href="/brand-config/overview">
    The full schema, including the `flexibility` policy block that bounds every preference axis.
  </Card>

  <Card title="Core Concepts" icon="atom" href="/core-concepts">
    The preference vector in full, and why values are computed at runtime.
  </Card>
</CardGroup>
