# Motion that respects the reader

> Texuli motion (in development): presence, FLIP layout, shared elements, timelines and springs on the Web Animations API, motion tokens and reduced motion.

Language: en
Canonical: https://texuli.com/motion-page/
Markdown: https://texuli.com/motion-page.md (this page). Every texuli.com page has a Markdown twin at its URL plus `.md` (the home page is https://texuli.com/index.md), and any page URL answers `Accept: text/markdown` with its twin instead of HTML.
Last Updated: 2026-11-13

---

Capability

texuli/motion · in development · on npm from 0.6.0

One small runtime moves every Texuli surface: overlays enter and leave, lists glide, a card grows into its detail view, timelines play on one clock, springs settle. The browser does the interpolation through the Web Animations API. Reduced motion (`prefers-reduced-motion`) is instant by construction, and the settings below can force reduced motion on every demo. The motion system is in development and on npm from `0.6.0`, a pre-1.0 release; `0.5.0` and earlier are without it.

- `animate` Web Animations, one run
- `presence` enter and exit, reversible
- `flip` reorder, insert, remove
- `shared` one element, two places
- `timeline` play, pause, seek, reverse
- `stagger` offsets, capped at 1 s
- `spring` a pure solver, linear() curves
- `levels` full, subtle, none

## A capability inside larger systems

`texuli/motion` is an embeddable animation runtime, not a hosted motion product. Solution landings that use it are planned. Until those routes ship, the demos on this page and the capability pages below are the live surface. Your application owns when motion runs; the runtime only paints.

- **Drag and drop** Capability. Same reduced-motion contract on pointer and keyboard moves.[Drag and drop](https://texuli.com/drag-page/)
- **Forms** Schema-driven runtime; step flows can move with the same motion tokens.[Forms](https://texuli.com/forms-page/)
- **Accessibility** Keyboard, labels and WCAG 2.x contrast, including reduced motion.[Accessibility](https://texuli.com/guides/accessibility/)

## Motion level and reduced motion

Every demo on this page reads these two settings through MotionProvider or the runtime options. Lower the level, or turn on reduced motion, and watch the same code commit its end state at once.

Motion level Full Subtle None
 Reduced motion

Your system: **checking**

- `full` Every preset plays at its token duration.
- `subtle` Durations shrink to 60%, and travel and scale are dropped: a move commits at once, a fade stays.
- `none` Nothing animates: every change commits at once.

Not played yet

Duration tokens at this level

| `fast` | `base` | `slow` | `slower` |
| --- | --- | --- | --- |
| 150 ms | 200 ms | 300 ms | 500 ms |

## Every preset

The five enter and exit presets of the closed vocabulary, the same names a component prop or an agent blueprint uses, plus the edge slides sheets and drawers play. Replay any of them at any duration step; each card shows the keyframes its preset resolves to.

Duration step

- ### `fade` Enter Exit Keyframes load with the runtime.
- ### `rise` Enter Exit Keyframes load with the runtime.
- ### `scale` Enter Exit Keyframes load with the runtime.
- ### `slide-start` Enter Exit Keyframes load with the runtime.
- ### `slide-end` Enter Exit Keyframes load with the runtime.
- ### `edge slide, left` Enter Exit Keyframes load with the runtime.
- ### `edge slide, right` Enter Exit Keyframes load with the runtime.
- ### `edge slide, top` Enter Exit Keyframes load with the runtime.
- ### `edge slide, bottom` Enter Exit Keyframes load with the runtime.

Level full, reduced motion follows your system [Change the settings](https://texuli.com/motion-page/#settings)

## Presence: overlays that enter and leave

Dialog, Sheet and the Toaster animate through the shared runtime. Pick the presets: they are the same closed names an agent can use. A closed overlay gives focus back and lifts inert at once; only the paint waits for the exit.

Presence options Enter Exit Duration

```
motion={{ enter: 'scale', exit: 'fade', duration: 'base' }}
```

Level full, reduced motion follows your system [Change the settings](https://texuli.com/motion-page/#settings)

## Every component that moves

Tooltips, popovers, hover cards and the four menus scale in; the alert dialog scales over its backdrop; drawers, detail panels and responsive modals slide from their edge; the accordion opens its height and the tabs indicator glides. All of them follow the settings above. The table lists every package source that animates, checked against the code.

- ### Tooltip `scale` in, `fade` out, `fast` Hover or focus me
- ### Popover `scale` in and out, `fast` Open popover
- ### Hover card `scale` in, `fade` out, `fast` Lin Example
- ### Dropdown menu and submenu `scale` in, `fade` out, `fast` Actions
- ### Context menu `scale` in, `fade` out, `fast` Right-click here, or focus and press Shift+F10
- ### Menubar `scale` in, `fade` out, `fast` File View
- ### Alert dialog `scale` panel over a `fade` backdrop, `base` Archive board
- ### Drawer slides from its edge (`rise`), `base` Open drawer
- ### Detail panel non-modal, slides from the end, `base` Show details
- ### Responsive modal a dialog on wide screens, a bottom drawer on phones Share order
- ### Accordion height animates (layout), `base` ### When does the beta open? ### What does it cost?
- ### Tabs the indicator glides (layout), `base` Notes Owners Dates The notes of the fictional release.

Level full, reduced motion follows your system [Change the settings](https://texuli.com/motion-page/#settings)

32 package sources that animate through the runtime

| Component | Package | What moves | See it |
| --- | --- | --- | --- |
| Dialog | `texuli` | Panel scale in and out over a fading backdrop, base | [On this page](https://texuli.com/motion-page/#presence) |
| AlertDialog | `texuli` | Panel scale in and out over a fading backdrop, base | [On this page](https://texuli.com/motion-page/#components) |
| Sheet | `texuli` | Slides from its edge the whole way (rise), base | [On this page](https://texuli.com/motion-page/#presence) |
| Sheet overlay | `texuli` | Backdrop fade in and out | [On this page](https://texuli.com/motion-page/#presence) |
| Drawer | `texuli` | Slides from its edge to its snap point, base | [On this page](https://texuli.com/motion-page/#components) |
| DetailPanel | `texuli` | Slides from its edge, base | [On this page](https://texuli.com/motion-page/#components) |
| ResponsiveModal | `texuli` | Dialog or bottom drawer, slides from its edge, base | [On this page](https://texuli.com/motion-page/#components) |
| Popover | `texuli` | Scale in and out, fast | [On this page](https://texuli.com/motion-page/#components) |
| HoverCard | `texuli` | Scale in, fade out, fast | [On this page](https://texuli.com/motion-page/#components) |
| Tooltip | `texuli` | Scale in, fade out, fast | [On this page](https://texuli.com/motion-page/#components) |
| DropdownMenu | `texuli` | Scale in, fade out, fast | [On this page](https://texuli.com/motion-page/#components) |
| Submenu | `texuli` | Scale in, fade out, fast | [On this page](https://texuli.com/motion-page/#components) |
| ContextMenu | `texuli` | Scale in, fade out, fast | [On this page](https://texuli.com/motion-page/#components) |
| Menubar | `texuli` | Scale in, fade out, fast | [On this page](https://texuli.com/motion-page/#components) |
| Toaster | `texuli` | Rise in, fade out; the stack glides (layout), base | [On this page](https://texuli.com/motion-page/#presence) |
| Tabs | `texuli` | The indicator glides to the new tab (layout), base | [On this page](https://texuli.com/motion-page/#components) |
| Accordion | `texuli` | Panel height opens and closes (layout), base | [On this page](https://texuli.com/motion-page/#components) |
| Dashboard | `@texuli/data` | Widgets glide to their new cell after a change (FLIP), in development | [/dashboards-page/](https://texuli.com/dashboards-page/) |
| DataGrid | `@texuli/data` | Rows glide on sort, insert and remove (FLIP), base | Its showcase page is planned |
| Board | `@texuli/data` | Cards glide on a committed move (FLIP), base | [On this page](https://texuli.com/motion-page/#layout) |
| InteractivePlot (every interactive chart) | `@texuli/charts` | Follows the MotionProvider reduced-motion setting and the OS preference (data-reduced-motion) | [On this page](https://texuli.com/motion-page/#settings) |
| Board, DataGrid and Dashboard list motion | `@texuli/data` | Loads texuli/motion list motion only once a change can animate | [On this page](https://texuli.com/motion-page/#layout) |
| SchemaForm | `@texuli/forms` | Each new step fades in, fast | [/forms-page/#flow](https://texuli.com/forms-page/#flow) |
| PageEditor blocks | `@texuli/pages` | Blocks glide on insert, remove and move (FLIP), fast | [/solutions/documents/](https://texuli.com/solutions/documents/) |
| PageEditor | `@texuli/pages` | Takes the motion prop and passes it to its block list | [/solutions/documents/](https://texuli.com/solutions/documents/) |
| Editor context | `@texuli/pages` | Shares the editor’s motion prop with its rows | [/solutions/documents/](https://texuli.com/solutions/documents/) |
| CollabEditor | `@texuli/pages` | The same block motion in collaborative editing | [/solutions/documents/](https://texuli.com/solutions/documents/) |
| Canvas zoom and fit | `@texuli/canvas` | Zoom and fit glide through the runtime | [/solutions/canvas/](https://texuli.com/solutions/canvas/) |
| Player motion settings | `@texuli/player` | The level a blueprint or host sets | [On this page](https://texuli.com/motion-page/#blueprint) |
| Blueprint node motion | `@texuli/player` | Enter, exit, layout and stagger declared as data | [On this page](https://texuli.com/motion-page/#blueprint) |
| Slide transitions and fragments | `@texuli/slides` | Every transition and fragment effect at the deck’s level | [On this page](https://texuli.com/motion-page/#deck) |
| Stage motion (static half) | `@texuli/slides` | Reads fragments before a step change and loads the runtime half | [On this page](https://texuli.com/motion-page/#deck) |

## Layout: lists and boards that glide

FLIP measures every item before a change and plays each one from where it was. The list below uses the same seam as the Board from @texuli/data, so a reorder, an insert or a committed move glides instead of jumping.

### A keyed list

- Write launch notes ↑ ↓ ×
- Refresh pricing page ↑ ↓ ×
- Check status page ↑ ↓ ×
- Send May invoices ↑ ↓ ×

Level full, reduced motion follows your system [Change the settings](https://texuli.com/motion-page/#settings)

### A board

### Release board

Focus a card handle and press Space or Enter to pick it up. Use the arrow keys to choose a position, then Space or Enter to drop. Press Escape to cancel. Or open Move on a card and choose a column and position from the menu.

#### To do

 3 cards

 Audit the forms

 Dark mode pass

 CSV export

#### Doing

 1 card

 Search index

#### Done

 1 card

 Onboarding emails

## Shared elements: one card, two places

Open a card and its preview grows into the detail view; go back and it returns to the grid. The registry measures the element before the change and plays the new one from that box.

- Harbor Notes Open Harbor Notes
- Tide Board Open Tide Board
- Lumen Docs Open Lumen Docs
- Orbit Status Open Orbit Status

Level full, reduced motion follows your system Not played yet [Change the settings](https://texuli.com/motion-page/#settings)

## Timelines and stagger

Declared steps on one clock: play, pause, seek, reverse. Stagger spreads the tiles from the start, the center or the end, and the whole spread is held to 1,000 ms however many items there are.

 Stagger from Offset per tile: 80 ms
 Seek: 0 of … ms

Level full, reduced motion follows your system Not played yet [Change the settings](https://texuli.com/motion-page/#settings)

## Springs

Three spring tokens solved by the owned damped-oscillator solver. Each curve is precomputed as a CSS linear() easing, so the browser plays it on its compositor; the plot below is drawn from the same solver.

Drawn from `springSamples` in `@texuli/core/motion/spring`, the solver that precomputed each token's `linear()` easing.

- **gentle** stiffness 120, damping 20, mass 1; ratio 0.91; settles in 648 ms; peak 1 `--tx-motion-spring-gentle` Play gentle
- **snappy** stiffness 400, damping 30, mass 1; ratio 0.75; settles in 453 ms; peak 1.03 `--tx-motion-spring-snappy` Play snappy
- **bouncy** stiffness 380, damping 18, mass 1; ratio 0.46; settles in 844 ms; peak 1.19 `--tx-motion-spring-bouncy` Play bouncy

Level full, reduced motion follows your system Not played yet [Change the settings](https://texuli.com/motion-page/#settings)

## Spring lab

Any spring, solved live. Move stiffness, damping, mass and the initial velocity and watch the curve, the damping ratio, the settle time and the linear() easing the browser plays. Every spring settles within 1,000 ms; parameters outside the solver’s ranges are clamped.

 Stiffness 170 Damping 26 Mass 1 Initial velocity 0

Damping ratio

1 (critically damped)

Settles in

718 ms

Peak

1

The easing the browser plays: `linear(0, 0.0635, 0.1965, 0.3455, 0.4849, 0.6045, 0.7019, 0.7785, 0.8373, 0.8816, 0.9146, 0.9388, 0.9564, 0.9691, 0.9782, 0.9846, 0.9892,…`

Level full, reduced motion follows your system [Change the settings](https://texuli.com/motion-page/#settings)

## Tokens explorer

Five durations, five easings and three springs, as data in @texuli/tokens/motion and as --tx-motion-* custom properties. Play any token to see it; under reduced motion every duration is 0 ms.

Pick a token to play it

Five duration steps (theme defaults)

| Step | Default | Custom property | Play |
| --- | --- | --- | --- |
| `instant` | 0 ms | `--tx-motion-instant` | Play instant |
| `fast` | 150 ms | `--tx-motion-fast` | Play fast |
| `base` | 200 ms | `--tx-motion-base` | Play base |
| `slow` | 300 ms | `--tx-motion-slow` | Play slow |
| `slower` | 500 ms | `--tx-motion-slower` | Play slower |

Level full, reduced motion follows your system [Change the settings](https://texuli.com/motion-page/#settings)

## Deck motion: transitions, fragments and levels

Slides share the same three levels. Every slide transition and every fragment effect, on the slides stage, at the level chosen in the settings: subtle shortens everything and plays zoom as a fade, none cuts, and reduced motion turns every transition into a short fade. Press the right arrow on the stage to step through the fragments.

Deck motion level: **full**. Change it in the settings at the top of the page.

- Transition: none
- Transition: fade
- Transition: fade-up
- Transition: slide
- Transition: zoom
- Transition: morph
- Fragments 1
- Fragments 2
- Fragments 3

Loading the slides stage…

What each slide transition plays, from `resolveSlideTransition`

| Transition | full | subtle | none | reduced motion |
| --- | --- | --- | --- | --- |
| `none` | cut | cut | cut | cut |
| `fade` | `fade`, 300 ms | `fade`, 180 ms | cut | `fade`, 150 ms |
| `fade-up` | `fade-up`, 300 ms | `fade-up`, 180 ms | cut | `fade`, 150 ms |
| `slide` | `slide`, 400 ms | `slide`, 240 ms | cut | `fade`, 150 ms |
| `zoom` | `zoom`, 350 ms | `fade`, 180 ms | cut | `fade`, 150 ms |
| `morph` | `morph`, 400 ms | `morph`, 240 ms | cut | `fade`, 150 ms |

## Motion as data: an agent declares it, the player paints it

A blueprint names presets and token steps, never keyframes, CSS or milliseconds. The player checks the vocabulary, plays the checked copy through the same runtime, and paints a refused motion without animating it.

### The blueprint

A node may carry `motion` with `enter` and `exit` (`fade`, `rise`, `scale`, `slide-start`, `slide-end`), `layout`, `stagger` (`none`, `fast`, `base`) and `duration` (`fast`, `base`, `slow`); the envelope may set a `level`. Spec `0.6.0` ships in the `0.6.0` release of `@texuli/spec`.

```
{
  "format": "texuli-blueprint",
  "version": 1,
  "spec": "0.6.0",
  "id": "motion-release-brief",
  "catalog": "0000000000000000000000000000000000000000000000000000000000000000",
  "status": "draft",
  "createdAt": "2026-10-28T09:00:00Z",
  "updatedAt": "2026-10-28T09:00:00Z",
  "generator": {
    "kind": "agent",
    "name": "Harbor assistant",
    "model": "example-model"
  },
  "state": {
    "details": {
      "type": "boolean",
      "initial": false
    }
  },
  "motion": {
    "level": "full"
  },
  "tree": {
    "id": "layout-page",
    "props": {
      "title": "Release brief, week 44",
      "description": "Harbor Notes ships on Thursday. Turn on the details to see what is left."
    },
    "children": [
      {
        "id": "control-toggle",
        "props": {
          "label": "Show the details",
          "state": "details"
        }
      },
      {
        "id": "layout-grid",
        "props": {
          "columns": 3
        },
        "motion": {
          "stagger": "base"
        },
        "children": [
          {
            "id": "layout-card",
            "props": {
              "title": "Docs"
            },
            "when": {
              "op": "eq",
              "left": {
                "op": "var",
                "name": "details"
              },
              "right": {
                "op": "num",
                "value": 1
              }
            },
            "motion": {
              "enter": "rise",
              "exit": "fade",
              "duration": "base"
            },
            "children": [
              {
                "id": "blocks-text",
                "props": {
                  "block": {
                    "type": "text",
                    "markdown": "Two guides left to review."
                  }
                }
              }
            ]
          },
          {
            "id": "layout-card",
            "props": {
              "title": "Checks"
            },
            "when": {
              "op": "eq",
              "left": {
                "op": "var",
                "name": "details"
              },
              "right": {
                "op": "num",
                "value": 1
              }
            },
            "motion": {
              "enter": "rise",
              "exit": "fade",
              "duration": "base"
            },
            "children": [
              {
                "id": "blocks-text",
                "props": {
                  "block": {
                    "type": "text",
                    "markdown": "All green on the release branch."
                  }
                }
              }
            ]
          },
          {
            "id": "layout-card",
            "props": {
              "title": "Launch post"
            },
            "when": {
              "op": "eq",
              "left": {
                "op": "var",
                "name": "details"
              },
              "right": {
                "op": "num",
                "value": 1
              }
            },
            "motion": {
              "enter": "rise",
              "exit": "fade",
              "duration": "base"
            },
            "children": [
              {
                "id": "blocks-text",
                "props": {
                  "block": {
                    "type": "text",
                    "markdown": "Draft shared with the crew."
                  }
                }
              }
            ]
          }
        ]
      },
      {
        "id": "layout-card",
        "props": {
          "title": "Risk review"
        },
        "when": {
          "op": "eq",
          "left": {
            "op": "var",
            "name": "details"
          },
          "right": {
            "op": "num",
            "value": 1
          }
        },
        "motion": {
          "enter": "scale",
          "exit": "fade",
          "duration": "slow",
          "layout": true
        },
        "children": [
          {
            "id": "blocks-text",
            "props": {
              "block": {
                "type": "text",
                "markdown": "The status page at status.example.com needs one more check."
              }
            }
          }
        ]
      }
    ]
  }
}
```

### The refused node

```
{
  "id": "layout-card",
  "props": {
    "title": "Risk review"
  },
  "when": {
    "op": "eq",
    "left": {
      "op": "var",
      "name": "details"
    },
    "right": {
      "op": "num",
      "value": 1
    }
  },
  "motion": {
    "enter": "spin",
    "keyframes": [
      {
        "transform": "rotate(720deg)"
      }
    ]
  },
  "children": [
    {
      "id": "blocks-text",
      "props": {
        "block": {
          "type": "text",
          "markdown": "This card paints without its motion."
        }
      }
    }
  ]
}
```

### Painted by the player

Blueprint Motion as data A motion outside the vocabulary

## Motion blueprint (untrusted)

 Untrusted content Draft

No errors: the motion is inside the vocabulary, so the player plays it.

Level full, reduced motion follows your system [Change the settings](https://texuli.com/motion-page/#settings)

## Every export and every token

The complete public surface of the motion system, read from the packages when this page was built, so nothing is missing: the runtime texels, the spring solver, the motion tokens with their custom properties, and the component contract.

- ### `@texuli/core/motion` The runtime texels: 39 exports`DEFAULT_MOTION_DURATION`
- `DEFAULT_MOTION_EASING`
- `DEFAULT_SPRING`
- `DEFAULT_SPRING_SAMPLES`
- `MAX_MOTION_MS`
- `MAX_SPRING_MS`
- `MAX_SPRING_SAMPLES`
- `MAX_STAGGER_SPAN`
- `MAX_TIMELINE_STEPS`
- `MOTION_LEVELS`
- `REDUCED_FADE_MS`
- `SUBTLE_DURATION_SCALE`
- `animate`
- `createFlip`
- `createMotionPresence`
- `createReducedMotionSource`
- `createRun`
- `createSharedElements`
- `dampingRatio`
- `flipTransform`
- `isMotionLevel`
- `lowerMotionLevel`
- `motionDuration`
- `normalizeSpring`
- `parallel`
- `resolveMotion`
- `resolveMotionLevel`
- `retargetVelocity`
- `sequence`
- `springEasing`
- `springKeyframes`
- `springSamples`
- `springSettle`
- `springState`
- `stagger`
- `startAnimation`
- `startScopedViewTransition`
- `timeline`
- `withoutTravel`

- ### `@texuli/core/motion/spring` The spring solver: 12 exports`DEFAULT_SPRING`
- `DEFAULT_SPRING_SAMPLES`
- `MAX_SPRING_MS`
- `MAX_SPRING_SAMPLES`
- `dampingRatio`
- `normalizeSpring`
- `retargetVelocity`
- `springEasing`
- `springKeyframes`
- `springSamples`
- `springSettle`
- `springState`

- ### `@texuli/tokens/motion` The motion tokens: 13 exports`MOTION_DURATIONS`
- `MOTION_DURATION_STEPS`
- `MOTION_EASINGS`
- `MOTION_EASING_NAMES`
- `MOTION_SPRINGS`
- `MOTION_SPRING_NAMES`
- `MOTION_SPRING_SAMPLES`
- `MOTION_VARIABLE_NAMES`
- `motionDurationVar`
- `motionEasingVar`
- `motionSpringVars`
- `motionVariables`
- `slowerDuration`

- ### `texuli/motion` The component contract and seams: 18 exports`DEFAULT_MOTION_SETTINGS`
- `MOTION_DURATION_NAMES`
- `MOTION_LEVEL_NAMES`
- `MOTION_PRESETS`
- `MotionProvider`
- `createIndicatorMotion`
- `createListMotion`
- `createOverlayMotion`
- `createPresenceMotion`
- `createSizeMotion`
- `getMotionSettings`
- `isMotionLevelName`
- `isMotionPreset`
- `loadMotionRuntime`
- `lowerLevel`
- `motionRuntime`
- `resolveComponentMotion`
- `setMotionSettings`

16 custom properties with their theme defaults

| Custom property | Default |
| --- | --- |
| `--tx-motion-instant` | `0ms` |
| `--tx-motion-fast` | `150ms` |
| `--tx-motion-base` | `200ms` |
| `--tx-motion-slow` | `300ms` |
| `--tx-motion-slower` | `500ms` |
| `--tx-motion-ease-standard` | `cubic-bezier(0.4, 0, 0.2, 1)` |
| `--tx-motion-ease-emphasized` | `cubic-bezier(0.2, 0, 0, 1)` |
| `--tx-motion-ease-enter` | `cubic-bezier(0, 0, 0.2, 1)` |
| `--tx-motion-ease-exit` | `cubic-bezier(0.4, 0, 1, 1)` |
| `--tx-motion-ease-linear` | `linear` |
| `--tx-motion-spring-gentle` | `linear(0, 0.0228, 0.0795, 0.1561, 0.2426, 0.3318, 0.419, 0.5013, 0.5767, 0.6444, 0.7041…` |
| `--tx-motion-spring-gentle-duration` | `648ms` |
| `--tx-motion-spring-snappy` | `linear(0, 0.0368, 0.1267, 0.2447, 0.3726, 0.4983, 0.6137, 0.7148, 0.7995, 0.8679, 0.921…` |
| `--tx-motion-spring-snappy-duration` | `453ms` |
| `--tx-motion-spring-bouncy` | `linear(0, 0.1177, 0.3817, 0.6779, 0.9294, 1.0989, 1.1811, 1.1921, 1.1571, 1.1015, 1.045…` |
| `--tx-motion-spring-bouncy-duration` | `844ms` |

## The rules every animation keeps

They hold by construction in the runtime, not by care in each component.

- ### Reduced motion is instant Under prefers-reduced-motion, a provider override or level none, every texel commits the end state at once: no frames run, the finished promise resolves "instant" and callbacks fire in order.
- ### Nothing animates on hydration The server paints the end state. Content present on the first render never animates in; animations start only after mount.
- ### No lasting inline style Every animation plays with fill "none"; the final state lives in classes, attributes or props. That keeps the strict style CSP of this site intact.
- ### The compositor interpolates Everything plays through the Web Animations API; no requestAnimationFrame loop writes styles. Springs become linear() easings.
- ### Focus never waits A closing overlay returns focus, lifts inert and leaves the layer stack at once; only its paint waits for the exit, at most 1.2 s.
- ### Hostile input is clamped Durations and delays are at most 1,000 ms; spring parameters, sample counts, FLIP deltas and list sizes have fixed ranges. Promises never reject.

### In code

```
import { MotionProvider } from 'texuli/motion';
import { Dialog } from 'texuli/dialog';

<MotionProvider level="subtle">
  <Dialog title="Edit crew" motion={{ enter: 'rise', exit: 'fade', duration: 'slow' }} />
</MotionProvider>
```

```
import { animate, createFlip, stagger, timeline } from '@texuli/core/motion';

const run = animate(card, [{ opacity: 0 }, { opacity: 1 }], { duration: 150 });
await run.finished; // "finished", or "instant" under reduced motion
```

## One API contract

Motion is one prop in a contract every exported component follows, so a styled component, a primitive and an agent blueprint behave the same way.

`0.5.0` brought `bind:ref` and rest attributes to about 40 components and made the inputs and 14 primitive parts compose a caller's handlers. `0.6.0` brought the rest: the remaining parts, the Field rule, and a contract test that derives every component from the package exports, so a new export without a case fails.

- ### Rest attributes and ref Attributes a component does not consume (id, aria-*, data-*, class, style, handlers) land on its root element, and bind:ref binds that same element. Form controls with a field wrapper send aria-*, data-* and handlers to the control and layout hooks to the wrapper.

```
<Dialog bind:ref={panel} data-testid="crew" aria-describedby="crew-hint" />
```

- ### Handlers compose: the caller first A handler you pass never replaces the component's own. It runs first; for actions (click, keydown, pointerdown) event.preventDefault() skips the built-in behavior, and bookkeeping (focus, blur, scroll) always runs.

```
<Button onclick={(event) => { if (locked) event.preventDefault(); }}>Archive</Button>
```

- ### One naming rule for callbacks Lowercase on<event> props are DOM handlers on the root element. camelCase on<Name> props are typed component callbacks (onValueChange, onOpenChange, onDismissRequest); a callback that can be vetoed gets an event with preventDefault().

```
<Select onValueChange={(value) => save(value)} onkeydown={trackKeys} />
```

- ### The Field rule The id, aria-describedby, aria-invalid and disabled that a Field hands its control reach the focusable element, so the label focuses it and its description and error are announced: the input of TagsInput, the first thumb of a Slider, the first segment of a DatePicker.

```
<Field label="Tags" description="Up to five">
  {#snippet children(control)}<TagsInput {...control.props} />{/snippet}
</Field>
```

- ### Providers render nothing Roots that only hold state (menu, select, popover, dialog and toast providers, MotionProvider) and portals render no element of their own, so they take no ref or rest attributes.

```
<MotionProvider level="subtle"><App /></MotionProvider>
```

- ### One motion prop Components that move take motion: { enter, exit, layout, duration } or false. The names are the closed spec vocabulary; never CSS, keyframes or milliseconds, and unknown names fall back to the default.

```
<Dialog title="Edit crew" motion={{ enter: 'rise', exit: 'fade', duration: 'slow' }} />
```

## Package docs

The site does not render the package docs yet. Read them in the repository:

- Core motion texels: `packages/core/docs/core-motion.md`
- Motion tokens: `packages/tokens/docs/tokens-motion.md`
- The motion prop and MotionProvider: `packages/texuli/docs/ui-motion.md`
- The spec motion vocabulary: `packages/spec/docs/motion.md`
- Node motion in the player: `packages/player/docs/reference.md`
- The component API contract: `docs/STANDARDS.md`
- ADR-013: the motion runtime: `docs/DECISIONS.md`

## For agents

Motion demos (presence, layout, timelines and springs) that play in the browser. The live parts run in the browser and are not in this Markdown; the operations below do the same work through the read-only MCP server at https://texuli.com/mcp (no sign-in; see https://texuli.com/auth.md).

MCP operations ([reference](https://github.com/DailybotHQ/texuli/blob/main/packages/tools/docs/ops.md#operations)):

- `catalog.search`: Search the component catalog.

Docs:

- [texuli README](https://github.com/DailybotHQ/texuli/blob/main/packages/texuli/README.md)
