# motion-components > Framework-agnostic web components for spring-based motion: reveals, hover and press responses, text effects, animated icons, scroll scenes, charts and ready-made widgets. One HTML tag per effect, built on Lit and Motion. - Install: `npm install motion-components`, then `import 'motion-components'` for every tag, or `import 'motion-components/motion-reveal'` for one. - No build step: ``. - Works in plain HTML, React 19, Vue, Svelte, Angular, Astro and Alpine. Wrap your own markup: `

Hello

`. - Every animation is interruptible and respects `prefers-reduced-motion`. - Every animated component has `play()`, `pause()`, `finish()`, `cancel()`, `playState` and `finished`, and fires `motion-start`, `motion-finish` and `motion-cancel` (bubbling, composed). - Time is in seconds. Spring settings are `duration` and `bounce`. ## Scroll-driven blur reveal. Maps the element's scroll progress through the viewport to opacity, blur, and Y-translation. Direction controls whether the blur happens on entry, exit, or both. **Use it for:** hero images, quotes and section headings that should come into focus, or fade away, as the page scrolls. **Avoid it for:** an entrance that should play on its own once the content is in view; use `motion-blur-in`, which plays over a fixed time. Avoid `direction="both"` for text people need to read, because the text is fully clear only when it is in the middle of the screen. **Accessibility:** the content stays in the page while it is faded or blurred, so screen readers can read it at any scroll position. Only opacity, filter and transform change, so the layout does not shift. **Reduced motion:** the content shows fully visible, sharp and in place at once, for every `direction`, and it does not change as the page scrolls. `motion-start` and `motion-finish` still fire, and `cancel()` keeps the content visible. **Common mistakes:** placing it near the end of the page, or on a page too short to scroll. With `direction="in"`, the content is fully clear only when its center reaches the middle of the screen, so it can stay faded and blurred. Use `motion-blur-in` there. Docs: https://www.motion-components.dev/docs/reveal/motion-blur/ ### Install ```js import 'motion-components/motion-blur' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `intensity` | `number` | `10` | Maximum blur amount in pixels at the unfocused extreme. | | `y` | `number` | `12` | Vertical translation in pixels at the unfocused extreme. | | `once` | `boolean` | `true` | When `true` (and `direction="in"`), latch focused state on first reveal. Set `once="false"` to turn it off. | | `direction` | `'in' \| 'out' \| 'both'` | `'in'` | `'in'` blurs on entry, `'out'` blurs on exit, `'both'` blur-focus-blur. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets the latch and re-binds the scroll handler. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The content to blur on scroll. | ## Viewport-triggered blur reveal. Starts blurred and translated, snaps to focus once the element enters the viewport. The dominant editorial entrance pattern of 2024–2025. **Use it for:** headlines, hero text and images that should come into focus with a short animation the first time they scroll into view. **Avoid it for:** effects that should follow the scroll position; use `motion-blur`. For a list of items, use `motion-stagger`. For a fade without blur, use `motion-reveal`. **Accessibility:** the content stays in the page while it is hidden and blurred, so screen readers read it before it animates. Only opacity, filter and transform change, so the layout does not shift. **Reduced motion:** the content is never hidden or blurred. It shows at its final position at once, with no animation. The `motion-start` and `motion-finish` events still fire when it enters the view. **Common mistakes:** wrapping a whole page section in one `motion-blur-in`, so nothing shows until 10% of it is in view; wrap each block instead, or lower `threshold`. Changing `threshold` after the element has rendered has no effect, because it is read only once. Docs: https://www.motion-components.dev/docs/reveal/motion-blur-in/ ### Install ```js import 'motion-components/motion-blur-in' ``` Or without a build step: ```html ``` ### Example ```html

Headline

``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `duration` | `number` | `0.7` | Spring duration of the reveal animation, in seconds. | | `intensity` | `number` | `10` | Initial blur in pixels; animates to 0 on reveal. | | `y` | `number` | `12` | Initial vertical offset in pixels; animates to 0 on reveal. | | `threshold` | `number` | `0.1` | IntersectionObserver threshold (0–1) at which the reveal triggers. | | `once` | `boolean` | `true` | When `true`, only animate the first time the element enters view. Set `once="false"` to turn it off. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets to the blurred initial state and re-runs the reveal. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The content to blur-reveal. | ## Viewport-triggered fade + translate reveal. Starts hidden, animates in with a spring once the element intersects the viewport. **Use it for:** headings, cards and images that should fade and rise into place the first time they scroll into view. **Avoid it for:** content in the first screen that people need at once. For a list of items, use `motion-stagger`, which reveals children one after another. **Accessibility:** the content stays in the page while it is hidden, so screen readers read it before it animates. Only opacity and transform change, so the layout does not shift. **Reduced motion:** the content shows at its final position at once, with no animation. **Common mistakes:** wrapping a whole page section in one reveal, so nothing shows until 10% of it is in view; wrap each block instead, or lower `threshold`. Putting a `motion-reveal` directly inside `motion-stagger` animates the same element twice. Docs: https://www.motion-components.dev/docs/reveal/motion-reveal/ ### Install ```js import 'motion-components/motion-reveal' ``` Or without a build step: ```html ``` ### Example ```html

Reveals when scrolled into view

``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `duration` | `number` | `0.6` | Spring duration of the reveal animation, in seconds. | | `y` | `number` | `24` | Initial vertical offset in pixels; element rises to 0 on reveal. | | `threshold` | `number` | `0.1` | IntersectionObserver threshold (0–1) at which the reveal triggers. | | `once` | `boolean` | `true` | When `true`, only animate the first time the element enters view. Set `once="false"` to turn it off. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Re-runs the reveal animation from its hidden initial state. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The content to reveal on scroll-in. | ## Staggered viewport-triggered reveal for a list of children. Fades and translates each direct child in sequence once the first child enters the viewport. **Use it for:** lists, card grids and icon rows whose items should fade and rise into place one after another when the list scrolls into view. **Avoid it for:** a single block of content; use `motion-reveal` or `motion-blur-in`. Avoid long lists that go far below the screen: the whole sequence starts when the first child comes into view, so items lower down can finish animating before anyone scrolls to them. **Accessibility:** the children stay in the page while they are hidden, so screen readers read them before they animate. Only opacity and transform change, so the layout does not shift. **Reduced motion:** all children show at their final position at once, with no animation. No run starts, so `motion-start` and `motion-finish` do not fire. **Common mistakes:** only direct child elements animate. Wrapping all items in one `
` or `
    ` makes them animate as one block; put the items directly inside `motion-stagger`. Text placed directly inside, outside a child element, is not animated. Docs: https://www.motion-components.dev/docs/reveal/motion-stagger/ ### Install ```js import 'motion-components/motion-stagger' ``` Or without a build step: ```html ``` ### Example ```html
    One
    Two
    Three
    ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `interval` | `number` | `0.06` | Delay between each child's reveal, in seconds. | | `duration` | `number` | `0.5` | Spring duration of each child's reveal, in seconds. | | `y` | `number` | `16` | Initial vertical offset in pixels for each child; rises to 0. | | `from` | `'first' \| 'last' \| 'center'` | `'first'` | Stagger origin: `'first'`, `'last'` or `'center'`. | | `once` | `boolean` | `true` | When `true`, only animate the first time the list enters view. Set `once="false"` to turn it off. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets and re-runs the staggered reveal. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The list of children to reveal in sequence. | ## Spring-based hover transform primitive. Animates scale, translation, rotation, and skew on mouse enter, springs back on leave. Composes with any slotted content. **Use it for:** buttons, links, cards and images that should grow, move or rotate a little while the mouse pointer is over them. **Avoid it for:** feedback on click or tap; use `motion-press`. For effects that follow the pointer, use `motion-magnetic` or `motion-tilt`. Do not use it as the only sign that something is interactive, because it does not run on keyboard focus. **Accessibility:** it adds no role, `tabindex` or ARIA attributes, so the slotted content keeps its own role and keyboard behavior. The effect reacts to mouse enter and leave only, so keyboard focus does not trigger it; give the content its own visible focus style. Only transform changes, so the layout does not shift, but the moved content can cover nearby elements. **Reduced motion:** mouse enter and leave do nothing, and the content stays at its rest state. **Common mistakes:** wrapping a block element such as a card. The host is `display: inline-block`, so the card can shrink to the width of its content; set `display: block` on `motion-hover`. Docs: https://www.motion-components.dev/docs/respond/motion-hover/ ### Install ```js import 'motion-components/motion-hover' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `scale` | `number` | `1.05` | Scale factor on hover. `1` is no scaling. | | `x` | `number` | `0` | Horizontal translation in pixels on hover. | | `y` | `number` | `0` | Vertical translation in pixels on hover. | | `rotate` | `number` | `0` | Rotation in degrees on hover. | | `skew` | `number` | `0` | Skew along the X axis in degrees on hover. | | `duration` | `number` | `0.3` | Spring duration of the hover transition, in seconds. | | `bounce` | `number` | `0.3` | Spring bounciness (0 = critically damped, higher = more elastic). | | `disabled` | `boolean` | `false` | When `true`, ignores pointer input and settles to the rest state. | ### Slots | Slot | Description | | --- | --- | | (default) | The content that lifts on hover. | ## Magnetic cursor pull. Slotted content drifts toward the pointer by `strength`, springs back when the mouse leaves. Best on small interactive targets like buttons and icons. **Use it for:** small targets such as buttons, icon buttons and short links that should drift a little toward the mouse pointer. **Avoid it for:** large areas such as cards or images. The pull is a fraction of the distance from the center, so large elements move far. Use `motion-tilt` or `motion-hover` there. **Accessibility:** it adds no role, `tabindex` or ARIA attributes, so the slotted content keeps its own role and keyboard behavior. The pull reacts to mouse movement only, so keyboard focus does not move it. Only transform changes, so the layout does not shift. Put a real link or button inside. **Reduced motion:** mouse movement does nothing, and the content stays in its normal position. **Common mistakes:** expecting the pull to start before the pointer reaches the element. It reacts only while the pointer is over `motion-magnetic` itself; to make the active area larger, add `padding` to `motion-magnetic`. Docs: https://www.motion-components.dev/docs/respond/motion-magnetic/ ### Install ```js import 'motion-components/motion-magnetic' ``` Or without a build step: ```html ``` ### Example ```html Contact ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `strength` | `number` | `0.4` | Pull strength as a fraction of cursor-to-center distance. | | `duration` | `number` | `0.5` | Spring duration of the pull and release transitions, in seconds. | | `bounce` | `number` | `0.4` | Spring bounciness of the pull and release (0 = no overshoot). | | `disabled` | `boolean` | `false` | When `true`, ignores pointer input and settles to the rest state. | ### Slots | Slot | Description | | --- | --- | | (default) | The element that follows the cursor. | ## Tactile press primitive. Scales slotted content down on `pointerdown` and bounces back on release for a physical click feel. **Use it for:** buttons, toggles and clickable cards that should shrink a little while pressed with a mouse, finger or pen, then spring back on release. **Avoid it for:** content that does nothing when clicked, because the press makes it look clickable. For hover feedback, use `motion-hover`. **Accessibility:** it adds no role, `tabindex` or keyboard handling, so it does not make content clickable or focusable. Put a real ` ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `scale` | `number` | `0.95` | Scale factor while pressed. `1` is no shrink. | | `duration` | `number` | `0.15` | Duration of press and release transitions, in seconds. | | `bounce` | `number` | `0.4` | Spring bounciness of the release (0 = no overshoot). | | `disabled` | `boolean` | `false` | When `true`, ignores pointer input and settles to the rest state. | ### Slots | Slot | Description | | --- | --- | | (default) | The pressable content. | ## 3D tilt-on-hover primitive. Rotates slotted content along X/Y based on cursor position with a soft drop-shadow that tracks the tilt. Optional gloss overlay adds a moving highlight. **Use it for:** cards, product images and feature tiles that should turn in 3D toward the mouse pointer, with an optional moving shine from `gloss`. **Avoid it for:** small controls such as buttons and icons; use `motion-hover` or `motion-magnetic`. **Accessibility:** it adds no role, `tabindex` or ARIA attributes, so the slotted content keeps its own role and keyboard behavior. The tilt reacts to the mouse only, so keyboard focus does not trigger it. The `gloss` layer ignores the pointer, so clicks reach the content. The wrapper does not clip its content, so focus outlines of slotted content show in full. **Reduced motion:** mouse movement does nothing. The content stays flat, at normal size, with no shadow or gloss. **Common mistakes:** changing the border radius of the content after it renders, for example in a media query. The wrapper copies the radius of the first slotted element only on first render and when the slotted content changes, so the corners of the gloss stop matching. Expecting the wrapper to clip the content to its rounded corners; give the content its own `overflow: hidden` for that. Docs: https://www.motion-components.dev/docs/respond/motion-tilt/ ### Install ```js import 'motion-components/motion-tilt' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `max` | `number` | `15` | Maximum tilt angle in degrees on each axis. | | `duration` | `number` | `0.5` | Spring duration of the tilt and settle, in seconds. | | `bounce` | `number` | `0.2` | Spring bounciness of the tilt and settle (0 = no overshoot). | | `scale` | `number` | `1.04` | Scale factor while hovering. | | `gloss` | `boolean` | `false` | When `true`, render a moving radial-gradient gloss highlight overlay. | | `disabled` | `boolean` | `false` | When `true`, ignores pointer input and settles to the rest state. | ### Slots | Slot | Description | | --- | --- | | (default) | The content to tilt. Border-radius is mirrored onto the inner wrapper. | ## Text laid out along a partial arc. Like `motion-circle` but only fills a configurable slice. Optional rotation; supports an inner slot for content. **Use it for:** short labels, badges and headline accents that curve along part of a circle, with an optional slow rotation set by `duration`. **Avoid it for:** long text or body copy. For text around a full circle, use `motion-circle`. For a line of text that moves in a wave, use `motion-curve`. **Accessibility:** screen readers read the whole text once from a visually hidden copy. The positioned letters are `aria-hidden`. Content in the centre slot is read as normal, so give images an `alt` text (empty if they are decorative). A rotation never stops on its own, and `pause-on-hover` works with a mouse only. If people must be able to stop it, add a button that calls `pause()`. **Reduced motion:** the arc does not rotate, even when `duration` is set. The text stays still in its arc shape. **Common mistakes:** too much text for the `radius` and `arc`. The letters are spread evenly over `arc` degrees, so long text crowds together; raise `radius` or `arc`. The element is always a square of twice the `radius`, even for a small `arc`, so plan for the empty space it leaves. Docs: https://www.motion-components.dev/docs/text/motion-arc/ ### Install ```js import 'motion-components/motion-arc' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `text` | `string \| undefined` | | Text to lay out along the arc. | | `radius` | `number` | `100` | Arc radius in pixels. | | `arc` | `number` | `180` | Total angular span of the arc, in degrees. | | `align` | `'top' \| 'bottom'` | `'top'` | Where the arc opens: `'top'` (apex up) or `'bottom'` (apex down). | | `duration` | `number` | `0` | Seconds per full rotation. `0` disables rotation. | | `direction` | `'cw' \| 'ccw'` | `'cw'` | Rotation direction: `'cw'` or `'ccw'`. | | `upright` | `boolean` | `false` | When `true`, counter-rotate each glyph so it stays visually upright. | | `pause-on-hover` | `boolean` | `false` | When `true`, pause the rotation while the cursor is over the element. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | Optional content rendered at the centre of the arc. | ## Text wrapped around a full circle, optionally rotating. Place a logo or icon inside the default slot to render in the centre. **Use it for:** rotating badges and stamps, such as a short label that circles a logo or an icon in the centre slot. **Avoid it for:** text that people must read quickly, because rotating text is hard to read. For text along part of a circle, use `motion-arc`. **Accessibility:** screen readers read the whole text once from a visually hidden copy. The rotating letters are `aria-hidden`. Content in the centre slot is read as normal, so give images an `alt` text (empty if they are decorative). The rotation never stops on its own, and `pause-on-hover` works with a mouse only. If people must be able to stop it, add a button that calls `pause()`. **Reduced motion:** the ring does not rotate. The text stays still around the circle. **Common mistakes:** text with no separator at the end. The letters are spaced evenly around the full circle, so the last word runs into the first; end the text with a separator such as ` • `, as in the example. Too much text for the `radius` makes the letters crowd together. Docs: https://www.motion-components.dev/docs/text/motion-circle/ ### Install ```js import 'motion-components/motion-circle' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `text` | `string \| undefined` | | Text to lay out around the circle. | | `radius` | `number` | `80` | Circle radius in pixels. | | `duration` | `number` | `8` | Seconds per full rotation. Lower is faster. | | `direction` | `'cw' \| 'ccw'` | `'cw'` | Rotation direction: `'cw'` (clockwise) or `'ccw'` (counter-clockwise). | | `upright` | `boolean` | `false` | When `true`, counter-rotate each glyph so it stays visually upright. | | `pause-on-hover` | `boolean` | `false` | When `true`, pause the rotation while the cursor is over the element. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | Optional content rendered at the centre of the circle. | ## Animated number counter. Springs from `from` to `to` once the element scrolls into view. Use `prefix`/`suffix` for currency or units, `decimals` for fractional precision. **Use it for:** key figures and statistics, such as a year, a percentage or a price, that count up once when they scroll into view. **Avoid it for:** values that change while the page is open. Setting a new `to` does not update the number that is shown, and `replay()` counts again from `from`. **Accessibility:** the number is plain text with no `aria-live`. Screen readers read the value shown at that moment, which can be `from` or a value in between. If the final value matters, also state it in nearby text. **Reduced motion:** the number shows `to` at once when the element scrolls into view, with no counting. Before that, it shows `from`. **Common mistakes:** expecting thousands separators. The number is formatted with `toFixed` only, so `10000` shows as "10000", not "10,000". Setting `decimals` lower than the decimals in `to` rounds the final value, so `to="4.87"` with `decimals="0"` ends at "5". Docs: https://www.motion-components.dev/docs/text/motion-counter/ ### Install ```js import 'motion-components/motion-counter' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `from` | `number` | `0` | Starting value of the counter. | | `to` | `number` | `100` | Target value to count up (or down) to. | | `duration` | `number` | `1.5` | Spring duration of the count animation, in seconds. | | `decimals` | `number` | `0` | Number of decimal places to render. | | `prefix` | `string` | `''` | Text rendered before the number (e.g. `"$"`). | | `suffix` | `string` | `''` | Text rendered after the number (e.g. `"%"`). | | `once` | `boolean` | `true` | When `true`, only count the first time the element enters view. Set `once="false"` to turn it off. Ignored with `loop`. | | `loop` | `boolean` | `false` | Count to `to`, hold, snap back to `from`, wait, then count again on repeat. | | `hold` | `number` | `1.6` | With `loop`, seconds the counter stays at `to` before it snaps back to `from`. | | `gap` | `number` | `0.5` | With `loop`, seconds the counter stays at `from` before it counts again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the counter. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets the counter to `from` and re-runs the count animation. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ## Sine-wave text. Each character of `text` rides a travelling wave with a tangent-aligned rotation, producing a flowing serpent effect. With `loop`, the text scrolls horizontally as a marquee while still riding the wave. Text can be set with the `text` attribute or as child text. Child text doubles as a pre-upgrade fallback: the browser shows it before the element is defined, so the page never paints an empty gap. For long marquee strings prefer the `text` attribute so the fallback does not wrap across several lines. **Use it for:** short words and headlines that ride a gentle wave, and, with `loop`, a banner that scrolls while it waves. **Avoid it for:** body text and long sentences that people need to read. For text on a fixed curve, use `motion-arc`. For a marquee that keyboard users can pause, use `motion-ticker`. **Accessibility:** screen readers read the whole text once from a visually hidden copy. The moving letters, and the repeated copies in `loop` mode, are `aria-hidden`. The wave never stops on its own, and `pause-on-hover` works with a mouse only. If people must be able to stop it, add a button that calls `pause()`. **Reduced motion:** the wave does not start. The letters stay on a straight line and, in `loop` mode, the text does not scroll. **Common mistakes:** changing the child text after the element is on the page. The child text is read once, when the element connects; set the `text` property instead. Using `no-pad` without room above and below, so the wave overlaps the content around it. Docs: https://www.motion-components.dev/docs/text/motion-curve/ ### Install ```js import 'motion-components/motion-curve' ``` Or without a build step: ```html ``` ### Example ```html Motion ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `text` | `string \| undefined` | | Text to render along the wave. Falls back to the element's child text when unset. | | `amplitude` | `number` | `24` | Peak vertical offset of the wave, in pixels. | | `wave-length` | `number` | `280` | Distance between wave crests, in pixels. | | `wave-duration` | `number` | `2.5` | Seconds for one full wave cycle. Lower is faster, `0` holds the wave still. | | `loop` | `boolean` | `false` | When `true`, scroll the text horizontally as a marquee. | | `loop-speed` | `number` | `80` | Scroll speed when `loop` is enabled, in pixels per second. | | `loop-gap` | `number` | `48` | Gap between repeated text sets in marquee mode, in pixels. This is spacing, not the seconds-based `gap` other components use for loop timing. | | `no-pad` | `boolean` | `false` | When `true`, omit vertical padding equal to `amplitude`. | | `pause-on-hover` | `boolean` | `false` | When `true`, pause the wave/loop while the cursor is over the element. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ## Animated variable-font axes. Smoothly interpolates one or more variation axes (weight, slant, optical size, custom axes…) between `from` and `to`, triggered by viewport entry, hover, or scroll progress. Uses `CSS.registerProperty` to enable interpolation of `` custom properties, then drives `font-variation-settings` from those properties. **Use it for:** headings, links and labels in a variable font whose weight, slant or width changes when they scroll into view, on hover or focus, or with scroll progress. **Avoid it for:** fonts that are not variable fonts, or that do not have the axis you set, because nothing changes. To scale or move an element on hover, use `motion-hover`. **Accessibility:** the slotted text stays real text, so screen readers read it as normal. With `trigger="hover"`, focus on a focusable element inside, such as a link, also starts the change; the element itself does not take focus. A change in weight or width can change the text width and move the text around it. **Reduced motion:** with `trigger="view"` or `trigger="scroll"`, the axes are set to their `to` values at once and do not follow the scroll. With `trigger="hover"`, the axes switch between `from` and `to` at once, with no transition. **Common mistakes:** changing `axis`, `axes`, `from`, `to` or `trigger` after the element first renders. The setup reads them once, so set them in the markup. Setting both `axes` and `axis`: `axes` wins, and `axis`, `from` and `to` are ignored. Docs: https://www.motion-components.dev/docs/text/motion-font/ ### Install ```js import 'motion-components/motion-font' ``` Or without a build step: ```html ``` ### Example ```html Variable font Multi-axis on scroll ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `axis` | `string` | `'wght'` | Single-axis tag to animate (e.g. `'wght'`, `'slnt'`, `'opsz'`). Ignored if `axes` is set. | | `axes` | `string` | `''` | Multi-axis spec: space-separated `axis:from:to` triples. Overrides `axis`/`from`/`to`. | | `from` | `number` | `300` | Single-axis start value. | | `to` | `number` | `700` | Single-axis end value. | | `duration` | `number` | `0.6` | Spring duration of the axis transition, in seconds. | | `bounce` | `number` | `0` | Spring bounce factor (0 = no overshoot). | | `delay` | `number` | `0` | Delay before the transition starts, in seconds. | | `trigger` | `'view' \| 'hover' \| 'scroll'` | `'view'` | Trigger source: `'view'` (when scrolled into view), `'hover'`, or `'scroll'` (progress-mapped). | | `once` | `boolean` | `true` | When `true` and `trigger="view"`, only animate the first time it enters view. Set `once="false"` to turn it off. Ignored with `loop`. | | `loop` | `boolean` | `false` | With `trigger="view"`, run from `from` to `to`, hold, back to `from`, wait, then repeat. | | `hold` | `number` | `1.6` | With `loop`, seconds the axes stay at `to` before they spring back to `from`. | | `gap` | `number` | `0.5` | With `loop`, seconds the axes stay at `from` before they run to `to` again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the text. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Runs the axes from `from` to `to` again. Only valid when `trigger="view"`. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The text whose font axes will animate. | ## RGB-split glitch effect on text. Renders red and cyan duplicate layers that jitter on hover, an automatic loop, or a single auto-play. **Use it for:** short labels with a technical or error look, such as `ERROR_404` or a game title. **Avoid it for:** long text, body copy and text with markup. For text that decodes through random letters, use `motion-scramble`. **Accessibility:** the original text stays as real text and is read as normal. The red and cyan copies are `aria-hidden`. `trigger="hover"` reacts to the mouse only, not to keyboard focus. With `trigger="loop"`, the bursts never stop on their own; if people must be able to stop them, add a button that calls `pause()`. **Reduced motion:** no glitch bursts run, with any `trigger` and with `glitch()`. The text stays still. **Common mistakes:** putting a link or other markup inside. Only the plain text is kept, so put the `motion-glitch` inside the link instead. Changing `trigger` after the element first renders has no effect. Docs: https://www.motion-components.dev/docs/text/motion-glitch/ ### Install ```js import 'motion-components/motion-glitch' ``` Or without a build step: ```html ``` ### Example ```html ERROR_404 ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `intensity` | `number` | `5` | Maximum horizontal displacement of the RGB layers, in pixels. | | `trigger` | `'hover' \| 'mount' \| 'loop'` | `'loop'` | When the glitch fires: `'hover'`, `'mount'` (once on load), or `'loop'` (keep firing). This is a trigger mode, not the boolean `loop` other components use. | | `interval` | `number` | `2` | Time between glitch bursts when `trigger="loop"`, in seconds. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Plays the glitch again from the start: one burst, or the loop from its first burst. | | `glitch()` | Fires one extra burst. While a loop runs, the loop keeps its rhythm; otherwise this is `replay()`. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The text to glitch. Plain text only — markup inside is replaced. | ## Gravity-drop text. Each character falls in from above and bounces to rest with a stagger across the line. Text can be set with the `text` attribute or as child text. Child text doubles as a pre-upgrade fallback: the browser shows it before the element is defined, so the page never paints an empty gap. **Use it for:** short, playful words and headings whose letters drop in and bounce into place, when the page loads or, with `trigger="view"`, when they scroll into view. **Avoid it for:** long sentences. For a split-text reveal of longer text, use `motion-split` or `motion-headline`. **Accessibility:** screen readers read the whole text once from a visually hidden copy. The falling letters are `aria-hidden`. The element has no heading role, so put it inside a heading element when it is a heading. **Reduced motion:** the letters show at their final position at once, with no drop. **Common mistakes:** placing it far down the page with the default `trigger="mount"`, so the drop is over before anyone sees it; set `trigger="view"`. Changing the child text after the element is on the page; the child text is read once, so set the `text` property instead. Docs: https://www.motion-components.dev/docs/text/motion-gravity/ ### Install ```js import 'motion-components/motion-gravity' ``` Or without a build step: ```html ``` ### Example ```html GRAVITY drops in on scroll ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `text` | `string \| undefined` | | Text to drop in. Falls back to the element's child text when unset. | | `height` | `number` | `60` | Drop distance in pixels (each char starts this far above its rest). | | `interval` | `number` | `0.05` | Delay between successive character drops, in seconds. | | `duration` | `number` | `0.6` | Spring duration of each character's fall, in seconds. | | `bounce` | `number` | `0.45` | Spring bounciness of the landing (0 = no overshoot). | | `delay` | `number` | `0` | Delay before the first character starts falling, in seconds. | | `trigger` | `'mount' \| 'view'` | `'mount'` | When the letters drop: `'mount'` (as soon as it renders) or `'view'` (the first time half of it is in view). | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets and re-runs the gravity drop. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ## Animated headline with split-text reveal. Splits the text content by words, characters, or lines, then reveals each unit with a stagger. `variant="slide"` slides each unit up under a mask; `variant="flip"` flips each unit on the X axis with perspective. **Use it for:** page and section headings that reveal word by word, letter by letter or line by line as they scroll into view. **Avoid it for:** headings with links or other markup, because the content is replaced with plain text. For cards and images, use `motion-reveal`. For lists, use `motion-stagger`. **Accessibility:** the split pieces are `aria-hidden`, and a visually hidden span keeps the full text, so screen readers read it in one piece. The element has no heading role, so put it inside a heading element such as `

    `. **Reduced motion:** the text shows in its final place at once, with no animation. The element does not wait to scroll into view. **Common mistakes:** using `by="lines"` where the width can change. The lines are measured once, when the element first renders, and are not split again on resize. Changing `by` or `variant` after the first render has no effect. `variant="flip"` splits by words when `by="lines"`. Docs: https://www.motion-components.dev/docs/text/motion-headline/ ### Install ```js import 'motion-components/motion-headline' ``` Or without a build step: ```html ``` ### Example ```html Motion-first web components ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `by` | `'words' \| 'chars' \| 'lines'` | `'words'` | Split unit: `'words'`, `'chars'`, or `'lines'`. | | `variant` | `'slide' \| 'flip'` | `'slide'` | Reveal style: `'slide'` (mask + slide up) or `'flip'` (3D rotateX). | | `interval` | `number` | `0.06` | Stagger between units, in seconds. | | `duration` | `number` | `1` | Spring duration of each unit's reveal, in seconds. | | `delay` | `number` | `0` | Delay before the first unit reveals, in seconds. | | `threshold` | `number` | `0.2` | IntersectionObserver threshold (0–1) at which the reveal triggers. | | `once` | `boolean` | `true` | When `true`, only animate the first time the element enters view. Set `once="false"` to turn it off. Ignored with `loop`. | | `loop` | `boolean` | `false` | Reveal the headline, hold, hide it again, then repeat. | | `hold` | `number` | `1.6` | With `loop`, seconds the headline stays visible before it hides again. | | `gap` | `number` | `0.5` | With `loop`, seconds the headline stays hidden before it reveals again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the headline. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets and re-runs the headline reveal. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The headline text. Plain text only — markup inside is replaced. | ## Liquid-distorted text via SVG turbulence + displacement-map. Looped fractal-noise warps the text into a flowing, organic shape. The text is rendered as SVG `` and the filter is applied to that SVG content directly — not to an HTML element via CSS `filter: url(#…)`. On WebKit (iOS Safari and every iOS browser) a `url()` filter applied to an HTML element is cached as a composited layer that only re-rasterizes when a repaint is forced (e.g. scrolling), so the distortion appears frozen except while scrolling. SVG content re-renders its own filtered output on every SMIL tick, which animates reliably across platforms. Text can be set with the `text` attribute or as child text. Child text doubles as a pre-upgrade fallback: the browser shows it before the element is defined, so the page never paints an empty gap. **Use it for:** one short word or title with a slow, flowing distortion, such as an accent in a hero section. **Avoid it for:** long text, and text that people need to read quickly. The text is one line of SVG text and does not wrap. For letters that ride a wave, use `motion-curve`. **Accessibility:** the SVG has `role="img"` and an `aria-label` with the text, so screen readers announce it as an image with that name. If it is a heading, put it inside a heading element. The distortion never stops on its own, and `pause-on-hover` works with a mouse only. Keep `intensity` low enough that the text stays readable. **Reduced motion:** the distortion filter is not applied. The text shows plain and still. **Common mistakes:** setting `duration="0"` to freeze the shape. This also turns the filter off, so the text shows with no distortion. Changing the child text after the element is on the page; the child text is read once, so set the `text` property instead. Docs: https://www.motion-components.dev/docs/text/motion-liquid/ ### Install ```js import 'motion-components/motion-liquid' ``` Or without a build step: ```html ``` ### Example ```html LIQUID ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `text` | `string \| undefined` | | Text to distort. Falls back to the element's child text when unset. | | `intensity` | `number` | `10` | Maximum displacement amount in pixels (peak of the noise pulse). | | `duration` | `number` | `4.5` | Seconds per distortion cycle. Lower is faster, `0` holds the text still. | | `pause-on-hover` | `boolean` | `false` | When `true`, pause the distortion loop while the cursor is over the element. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ## A highlighter stroke that sweeps behind a phrase from left to right. The straight stroke follows text that wraps across lines; `shape="wave"` draws a wavy stroke and keeps the text on one line. The text itself never changes. **Use it for:** a key phrase in a headline or paragraph that the reader should notice, such as a benefit, a result or a number. **Avoid it for:** whole paragraphs, or more than one or two phrases per screen, where the highlight loses its point. For a line under or through text, use `motion-underline` or `motion-strike`. **Accessibility:** the highlight is a background behind the text. The text stays in the page, selectable, and read by screen readers unchanged. If the highlight carries meaning, wrap the component in ``. Keep the color transparent so the text keeps its contrast. **Reduced motion:** the highlight appears fully drawn at once on the same trigger, with no animation. With `loop` it stays drawn and never cycles. **Common mistakes:** an opaque `--mc-marker-color`, which lowers the text contrast. Setting `display: block` on the component, which removes the highlight that follows each line. Using `shape="wave"` on a long phrase, which stays on one line and can overflow a narrow screen. Docs: https://www.motion-components.dev/docs/text/motion-marker/ ### Install ```js import 'motion-components/motion-marker' ``` Or without a build step: ```html ``` ### Example ```html

    Ship motion that feels physical.

    Makes the code feel alive.

    ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `'line' \| 'wave'` | `'line'` | `'line'`, a straight stroke that follows wrapping text, or `'wave'`, a wavy stroke that keeps the text on one line. | | `trigger` | `'view' \| 'hover' \| 'mount'` | `'view'` | What draws the mark: `'view'` (scrolled into view), `'hover'` (pointer enter, undraws on leave) or `'mount'`. | | `duration` | `number` | `0.6` | Spring duration of the drawing, in seconds. | | `delay` | `number` | `0` | Seconds to wait before drawing. | | `bounce` | `number` | `0` | Spring bounciness. `0` keeps the stroke from overshooting the text. | | `loop` | `boolean` | `false` | Draw, hold, undraw, draw again on repeat. With `trigger="view"` the cycle runs while the mark is on screen. | | `hold` | `number` | `1.6` | With `loop`, seconds the mark stays drawn before it undraws. | | `gap` | `number` | `0.5` | With `loop`, seconds the mark stays undrawn before it draws again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the mark, resuming on leave. | | `once` | `boolean` | `true` | With `trigger="view"`, draw only the first time. Set `once="false"` to undraw on leave and draw again on every entry. Ignored with `loop`. | | `threshold` | `number` | `0.6` | Share of the element, from 0 to 1, that must be visible before it draws. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | | `replay()` | Draws the mark again from the start. | | `sweep()` | Sweeps a drawn mark across: it erases from its start, so it leaves at its end, then draws in again from the start. Calls while a sweep runs, before the mark is drawn, or under reduced motion do nothing. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The phrase to highlight. It may wrap across lines, except with `shape="wave"`. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-marker-color` | Color of the highlighter stroke. Default `rgb(250 204 21 / 0.4)`. | ## Vanishing-point text. Each character shrinks and fades along the vanish direction, simulating a 3D recession. Optionally animates the recession back and forth like a depth oscillation. Text can be set with the `text` attribute or as child text. Child text doubles as a pre-upgrade fallback: the browser shows it before the element is defined, so the page never paints an empty gap. **Use it for:** a short word or title that should look as if it goes back into the distance, either still or with a slow back-and-forth depth motion (`loop`, previously `oscillate`). **Avoid it for:** long text or sentences, because the characters sit on one line and never wrap. For content that should tilt in 3D toward the pointer, use `motion-tilt`. **Accessibility:** the full text is in a visually hidden span and the character spans have `aria-hidden`, so screen readers read the text once, as normal words. The far characters are smaller and fainter, so check that they are still readable, or lower `depth`. With `loop`, the motion starts at once and does not end on its own. `pause-on-hover` reacts to the mouse only, so give other users a way to stop it, for example a button that calls `pause()`. **Reduced motion:** with `loop`, the oscillation does not start and the text keeps the still perspective layout. Without `loop` nothing changes, because that layout does not move. **Common mistakes:** placing an oscillating `motion-perspective` inside running text: the characters change `font-size` on every frame, so the element changes size and the text around it moves. Changing the child text after the element connects has no effect; set `text` instead. Docs: https://www.motion-components.dev/docs/text/motion-perspective/ ### Install ```js import 'motion-components/motion-perspective' ``` Or without a build step: ```html ``` ### Example ```html HORIZON ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `oscillate` | | | | | `text` | `string \| undefined` | | Text to render with depth. Falls back to the element's child text when unset. | | `depth` | `number` | `0.65` | How far the far end recedes (0 = flat, 1 = full vanish). | | `vanish` | `'left' \| 'right'` | `'left'` | Direction the text recedes towards: `'left'` or `'right'`. | | `loop` | `boolean` | `false` | When `true`, animate a back-and-forth depth oscillation on repeat. `oscillate` is kept as an alias for `loop`. | | `duration` | `number` | `3` | Seconds per oscillation cycle when `loop` is set. Lower is faster. | | `pause-on-hover` | `boolean` | `false` | When `true`, pause the oscillation while the cursor is over the element. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ## An ellipse or a box that draws itself around a word or a short phrase, like a pen circling something important. It keeps its text on one line. **Use it for:** one word or a short phrase to circle or box, such as a key result, a price or a label. **Avoid it for:** long phrases, because the ring keeps its text on one line and can overflow a narrow screen. Use `motion-marker` or `motion-underline` for text that wraps. **Accessibility:** the drawing is an SVG hidden from screen readers. The text stays in the page, selectable, and read unchanged. The ring takes room on its left and right, so it never covers the neighbouring words. It takes no room above and below, so it can overlap the lines above and below in a tight paragraph. **Reduced motion:** the ring appears fully drawn at once on the same trigger, with no animation. With `loop` it stays drawn and never cycles. **Common mistakes:** a large `padding` in a paragraph with a small line height, which makes the ring cover the neighbouring lines; raise the line height or lower `padding`. Docs: https://www.motion-components.dev/docs/text/motion-ring/ ### Install ```js import 'motion-components/motion-ring' ``` Or without a build step: ```html ``` ### Example ```html

    Every change is reversible.

    ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `'ellipse' \| 'box'` | `'ellipse'` | `'ellipse'`, or `'box'` for a rectangle with slightly rounded corners. | | `padding` | `number` | `6` | Gap in pixels between the text and the stroke. | | `trigger` | `'view' \| 'hover' \| 'mount'` | `'view'` | What draws the mark: `'view'` (scrolled into view), `'hover'` (pointer enter, undraws on leave) or `'mount'`. | | `duration` | `number` | `0.6` | Spring duration of the drawing, in seconds. | | `delay` | `number` | `0` | Seconds to wait before drawing. | | `bounce` | `number` | `0` | Spring bounciness. `0` keeps the stroke from overshooting the text. | | `loop` | `boolean` | `false` | Draw, hold, undraw, draw again on repeat. With `trigger="view"` the cycle runs while the mark is on screen. | | `hold` | `number` | `1.6` | With `loop`, seconds the mark stays drawn before it undraws. | | `gap` | `number` | `0.5` | With `loop`, seconds the mark stays undrawn before it draws again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the mark, resuming on leave. | | `once` | `boolean` | `true` | With `trigger="view"`, draw only the first time. Set `once="false"` to undraw on leave and draw again on every entry. Ignored with `loop`. | | `threshold` | `number` | `0.6` | Share of the element, from 0 to 1, that must be visible before it draws. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | | `replay()` | Draws the mark again from the start. | | `sweep()` | Sweeps a drawn mark across: it erases from its start, so it leaves at its end, then draws in again from the start. Calls while a sweep runs, before the mark is drawn, or under reduced motion do nothing. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The word or short phrase to circle. It stays on one line. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-mark-color` | Color of the stroke. Default `var(--mc-color-accent, #2563eb)`. | | `--mc-mark-thickness` | Thickness of the stroke. Default `2px`. | ## Decode-style text scramble. Cycles each character through random glyphs, locking them in left-to-right until the original text resolves. Docs: https://www.motion-components.dev/docs/text/motion-scramble/ ### Install ```js import 'motion-components/motion-scramble' ``` Or without a build step: ```html ``` ### Example ```html DECODE_ME ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `interval` | `number` | `0.04` | Time between glyph swaps, in seconds. | | `delay` | `number` | `0` | Delay before scrambling starts, in seconds. | | `iterations` | `number` | `2` | Number of random-glyph frames per character before locking in. | | `once` | `boolean` | `true` | When `true`, only scramble the first time the element enters view. Set `once="false"` to turn it off. Ignored with `loop`. | | `loop` | `boolean` | `false` | Scramble to the original text, hold, wait, then scramble again on repeat. | | `hold` | `number` | `1.6` | With `loop`, seconds the resolved text stays visible before it scrambles again. | | `gap` | `number` | `0.5` | With `loop`, seconds between cycles, while the text is already resolved. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the text. | | `trigger` | `'view' \| 'hover'` | `'view'` | What starts the scramble: `'view'` (when scrolled into view) or `'hover'`. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Re-runs the scramble animation. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The text to scramble. Plain text only — read once on connect. | ## Split-text reveal primitive. Splits the slotted text into spans and fades + translates each unit in with a stagger when the first unit enters view. **Use it for:** headings and short paragraphs that should come in word by word, letter by letter or line by line when they scroll into view. **Avoid it for:** text with links, emphasis or other markup, because the content is replaced with plain spans. To reveal a block as one piece, use `motion-reveal`. For units that slide up under a mask, use `motion-headline`. **Accessibility:** the split spans get `aria-hidden`, and a visually hidden span keeps the original text, so screen readers read the whole phrase and not single letters. Only opacity and transform animate, so the layout does not shift. **Reduced motion:** the text is still split, but every unit shows at once in its final place, with no animation. **Common mistakes:** expecting `by="lines"` to follow the layout: lines are measured once, on first render, and are not measured again when the width changes. Replacing the text later: the new text is not split and does not animate. Docs: https://www.motion-components.dev/docs/text/motion-split/ ### Install ```js import 'motion-components/motion-split' ``` Or without a build step: ```html ``` ### Example ```html Each character animates in. ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `by` | `'words' \| 'chars' \| 'lines'` | `'words'` | Split unit: `'words'`, `'chars'`, or `'lines'`. | | `interval` | `number` | `0.05` | Stagger between units, in seconds. | | `duration` | `number` | `0.6` | Spring duration of each unit's reveal, in seconds. | | `y` | `number` | `20` | Initial vertical offset in pixels for each unit. | | `once` | `boolean` | `true` | When `true`, only animate the first time the element enters view. Set `once="false"` to turn it off. Ignored with `loop`. | | `loop` | `boolean` | `false` | Reveal the units, hold, hide them again, then repeat. | | `hold` | `number` | `1.6` | With `loop`, seconds the text stays revealed before it hides again. | | `gap` | `number` | `0.5` | With `loop`, seconds the text stays hidden before it reveals again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the text. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets and re-runs the staggered reveal. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The text to split. Markup inside is replaced with span units. | ## Springy character spread on hover. Each character springs outward from the centre by `spread`, snapping back on mouse leave. Text can be set with the `text` attribute or as child text. Child text doubles as a pre-upgrade fallback: the browser shows it before the element is defined, so the page never paints an empty gap. **Use it for:** short words, logos or labels whose letters should spread apart with a springy bounce when the mouse moves over them. **Avoid it for:** text that must wrap, because the letters sit on one line, and effects that keyboard users need to see. To lift or scale a whole element on hover, use `motion-hover`. **Accessibility:** the full text is in a visually hidden span and the letter spans have `aria-hidden`, so screen readers read the text once, as normal words. The effect reacts to the mouse only, not to keyboard focus, so do not use it to show state or meaning. **Reduced motion:** the letters do not move when the mouse enters. **Common mistakes:** a large `spread` next to other content: the edge letters move about `spread` pixels outside the element and can cover the content next to it, so leave space around it. Changing the child text after the element connects has no effect; set `text` instead. Docs: https://www.motion-components.dev/docs/text/motion-stretch/ ### Install ```js import 'motion-components/motion-stretch' ``` Or without a build step: ```html ``` ### Example ```html STRETCH ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `text` | `string \| undefined` | | Text to stretch. Falls back to the element's child text when unset. | | `spread` | `number` | `12` | Maximum lateral displacement of edge characters, in pixels. | | `duration` | `number` | `0.77` | Spring duration of the spread and release, in seconds. | | `bounce` | `number` | `0.55` | Spring bounciness (0 = critically damped, higher = more elastic). | | `disabled` | `boolean` | `false` | When `true`, ignores pointer input and settles back. `pauseAll()` sets it. | ## A line that draws itself through a phrase, from left to right. Use it for an old price, a removed step or a word you replace. **Use it for:** an old price, a removed step or a replaced word, often followed by the new value marked with `motion-marker` and a `delay`. **Avoid it for:** text the reader still needs to read, because text with a line through it is harder to read. **Accessibility:** the line alone does not tell screen readers that the text is removed. Wrap the component in ``, and the new value in ``. Browsers then draw their own line through ``, so set `text-decoration: none` on it. **Reduced motion:** the line appears fully drawn at once on the same trigger, with no animation. With `loop` it stays drawn and never cycles. **Common mistakes:** a `` around the component with its default `text-decoration`, so two lines show. Docs: https://www.motion-components.dev/docs/text/motion-strike/ ### Install ```js import 'motion-components/motion-strike' ``` Or without a build step: ```html ``` ### Example ```html €49 €29 ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `trigger` | `'view' \| 'hover' \| 'mount'` | `'view'` | What draws the mark: `'view'` (scrolled into view), `'hover'` (pointer enter, undraws on leave) or `'mount'`. | | `duration` | `number` | `0.6` | Spring duration of the drawing, in seconds. | | `delay` | `number` | `0` | Seconds to wait before drawing. | | `bounce` | `number` | `0` | Spring bounciness. `0` keeps the stroke from overshooting the text. | | `loop` | `boolean` | `false` | Draw, hold, undraw, draw again on repeat. With `trigger="view"` the cycle runs while the mark is on screen. | | `hold` | `number` | `1.6` | With `loop`, seconds the mark stays drawn before it undraws. | | `gap` | `number` | `0.5` | With `loop`, seconds the mark stays undrawn before it draws again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the mark, resuming on leave. | | `once` | `boolean` | `true` | With `trigger="view"`, draw only the first time. Set `once="false"` to undraw on leave and draw again on every entry. Ignored with `loop`. | | `threshold` | `number` | `0.6` | Share of the element, from 0 to 1, that must be visible before it draws. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | | `replay()` | Draws the mark again from the start. | | `sweep()` | Sweeps a drawn mark across: it erases from its start, so it leaves at its end, then draws in again from the start. Calls while a sweep runs, before the mark is drawn, or under reduced motion do nothing. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The phrase to strike through. It may wrap across lines. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-mark-color` | Color of the line. Default `currentColor`. | | `--mc-mark-thickness` | Thickness of the line. Default `2px`. | ## Letter-swap text effect. Splits text into individual characters and swaps them vertically on hover or when scrolled into view. **Use it for:** short labels and headings whose letters should slide out while copies slide in, when the mouse moves over them or when they scroll into view (`trigger="view"`). **Avoid it for:** long text, because every letter becomes two spans, and text with links or other markup, which is removed. To move a whole element on hover, use `motion-hover`. **Accessibility:** screen readers read the whole text once from a visually hidden copy, which also names a link or button the text sits in. The letters, both the visible ones and the copies that swap in, are `aria-hidden`. The hover trigger reacts to the mouse only, not to keyboard focus. **Reduced motion:** the letters never swap. The text stays still in its normal state. **Common mistakes:** changing the text or `trigger` after the first render: both are read once, so replace the element instead. Setting `delay` with the hover trigger and expecting it only on enter: the swap back on mouse leave waits for `delay` too. Docs: https://www.motion-components.dev/docs/text/motion-swap/ ### Install ```js import 'motion-components/motion-swap' ``` Or without a build step: ```html ``` ### Example ```html Hello Hello ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `trigger` | `'hover' \| 'view'` | `'hover'` | Trigger mode: `'hover'` (swap on mouseenter/mouseleave) or `'view'` (swap on viewport entry). | | `reverse` | `boolean` | `true` | Letters swap bottom-to-top. Set `reverse="false"` to swap top-to-bottom. | | `interval` | `number` | `0.03` | Delay in seconds between each character's animation start. | | `duration` | `number` | `0.7` | Spring duration of each character swap, in seconds. | | `bounce` | `number` | `0.3` | Spring bounciness (0 = critically damped, higher = more elastic). | | `once` | `boolean` | `true` | When `true`, only animate the first time the element enters view (`trigger="view"`). Set `once="false"` to turn it off. Ignored with `loop`. | | `loop` | `boolean` | `false` | Swap the words, hold, swap back, then repeat. With `trigger="view"` the cycle runs while on screen. | | `hold` | `number` | `1.6` | With `loop`, seconds the swapped words stay in place before they swap back. | | `gap` | `number` | `0.5` | With `loop`, seconds before the next swap. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the text. | | `delay` | `number` | `0` | Delay in seconds before the animation starts. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets and re-runs the swap animation. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The text to animate. Plain text only — read once on connect. | ## Mask-clip reveal primitive. Clips slotted content behind an `overflow:hidden` boundary and slides it upward into view — the same pattern used inside `motion-headline`, exposed as a composable wrapper. **Use it for:** a heading, a line or a word that should slide up from behind an invisible edge when it scrolls into view. **Avoid it for:** content in the first screen that people need at once. For a heading that splits into lines, words or letters by itself, use `motion-headline` with `variant="slide"`. **Accessibility:** the content stays in the page while it is hidden, so screen readers read it before it animates. Only transform changes, so the layout does not shift. The host clips its content with `overflow: hidden`, so a focus outline on a link inside can be cut off; give such links an outline that stays inside, for example with a negative `outline-offset`. **Reduced motion:** the content shows in place at once, with no slide. **Common mistakes:** wrapping a whole paragraph in one mask: the host is `inline-block`, so the paragraph slides up as one block, not line by line. Use one mask per line, or `motion-headline` with `by="lines"`. Docs: https://www.motion-components.dev/docs/text/motion-text-mask/ ### Install ```js import 'motion-components/motion-text-mask' ``` Or without a build step: ```html ``` ### Example ```html Your headline text ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `duration` | `number` | `0.9` | Spring duration of the slide-up reveal, in seconds. | | `delay` | `number` | `0` | Delay before the reveal starts, in seconds. | | `threshold` | `number` | `0.2` | IntersectionObserver threshold (0–1) at which the reveal triggers. | | `once` | `boolean` | `true` | When `true`, only animate the first time the element enters view. Set `once="false"` to turn it off. Ignored with `loop`. | | `loop` | `boolean` | `false` | Slide the content in, hold, slide it back out, then repeat. | | `hold` | `number` | `1.6` | With `loop`, seconds the content stays visible before it slides back out. | | `gap` | `number` | `0.5` | With `loop`, seconds the content stays hidden before it slides in again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the mask. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets the inner mask offset and re-runs the reveal. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The text or inline content to reveal under the mask. | ## Horizontal auto-scrolling ticker / marquee. Duplicates children to create a seamless infinite loop. Supports pause-on-hover, keyboard pause (Space/Enter), direction control, and an optional sine-wave vertical oscillation effect. **Use it for:** a looping strip of short items, such as logos, tags or news headlines, that moves sideways across the page on its own. **Avoid it for:** links, buttons and other controls: only the original items can be clicked or focused, not the copies, and the ticker keeps moving while one of them has focus. Also avoid it for content people must read in full. For items that people move through themselves, use `motion-slider`. **Accessibility:** the ticker is focusable (`tabindex="0"`) and has `role="region"` with the `aria-label` "Scrolling ticker. Press Space to pause.", which replaces any `aria-label` you set in the HTML. Hover (with `pause-on-hover`) and focus slow it to a stop. When the ticker itself has focus, Space or Enter pauses it until one of them is pressed again. Key presses on items inside it are left alone. Every copy of the items is `aria-hidden` and `inert`, so screen readers read each item once and Tab reaches only the original items. **Reduced motion:** the ticker is not built and does not move. The items stay in normal flow, with no copies and no `gap`. It gets no `tabindex`, role or label, because there is nothing to pause. **Common mistakes:** putting bare text directly inside: only child elements become items, so wrap each item in an element such as ``. With `wave`, the ticker does not clip its content, so the items can show past its edges and make the page scroll sideways; put it in a parent with `overflow-x: clip`. Docs: https://www.motion-components.dev/docs/text/motion-ticker/ ### Install ```js import 'motion-components/motion-ticker' ``` Or without a build step: ```html ``` ### Example ```html Item one Item two Item three ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `speed` | `number` | `60` | Scroll speed in pixels per second. | | `gap` | `number` | `32` | Gap between items in pixels. | | `direction` | `'left'\|'right'` | `'left'` | Scroll direction. | | `pause-on-hover` | `boolean` | `true` | Pause on mouse enter / focus. Set `"false"` to keep scrolling. Read once on connect. | | `wave` | `boolean` | `false` | Enable sine-wave vertical oscillation. Read once on connect. | | `wave-amplitude` | `number` | `10` | Wave amplitude in pixels. Read once on connect. | | `wave-length` | `number` | `300` | Wave length in pixels. Read once on connect. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `pause()` | Holds the ticker. A pause from your code also ends any hover hold, so leaving does not lift it. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | Items to scroll. Each direct child is duplicated to fill the container. `speed`, `gap` and `direction` are live: changing them restarts the marquee. The remaining attributes are read once while the marquee is built. | ## Typewriter text effect. Reveals slotted text character-by-character, with optional looping (type → hold → erase → retype) and a blinking caret. **Use it for:** a short line, such as a hero tagline or a prompt, that should type itself out when it scrolls into view, once or on repeat with `loop`. **Avoid it for:** long text, important information and text with links or other markup, which is removed. For words that take turns in one place, use `motion-words`. **Accessibility:** screen readers read the whole text at all times from a visually hidden copy, even before typing starts and while `loop` erases it. The typed characters and the caret are `aria-hidden`. **Reduced motion:** the full text shows at once and nothing is typed. The caret shows but does not blink; set `cursor="false"` to hide it. **Common mistakes:** waiting for `motion-finish` or `finished` with `loop` set: a looping run never finishes on its own. Forgetting that the element is empty until it types: the content after it moves as the line grows, so reserve the space in your layout. Docs: https://www.motion-components.dev/docs/text/motion-typewriter/ ### Install ```js import 'motion-components/motion-typewriter' ``` Or without a build step: ```html ``` ### Example ```html Hello, world. ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `interval` | `number` | `0.05` | Time between characters while typing, in seconds. | | `delay` | `number` | `0` | Delay before typing starts after viewport entry, in seconds. | | `hold` | `number` | `1.8` | Time to hold the finished line before erasing (loop mode), in seconds. | | `loop` | `boolean` | `false` | When `true`, type → hold → erase → retype on repeat. | | `gap` | `number` | `0.5` | With `loop`, seconds the line stays empty after erasing before it retypes. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the text. | | `cursor` | `boolean` | `true` | When `true`, render a blinking caret after the typed text. Set `cursor="false"` to hide it. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Resets and re-runs the typing animation from the start. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The text to type. Plain text only — read once on connect. | ## A line that draws itself under a phrase, from left to right. Straight, dashed and dotted lines follow text that wraps across lines; `shape="wave"` and `shape="zigzag"` draw a wavy or a zigzag line and keep the text on one line. It uses the text color unless you set one. **Use it for:** a word or link that should stand out with a drawn line, such as a call to action or a term you introduce. **Avoid it for:** ordinary links in body text, where the browser's own underline is clearer and always visible. For a highlight behind the text, use `motion-marker`. **Accessibility:** the line is a background under the text, which stays in the page and is read by screen readers unchanged. With `trigger="hover"` the line follows the pointer only, so give links their own visible focus style. **Reduced motion:** the line appears fully drawn at once. With `trigger="hover"` it appears on enter and disappears on leave, instantly. With `loop` it stays drawn and never cycles. **Common mistakes:** leaving the link's own `text-decoration` on, so two lines show; set `text-decoration: none` on the link. Using `shape="wave"` or `shape="zigzag"` on a long phrase, which stays on one line and can overflow a narrow screen. Docs: https://www.motion-components.dev/docs/text/motion-underline/ ### Install ```js import 'motion-components/motion-underline' ``` Or without a build step: ```html ``` ### Example ```html Read the guide ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `'line' \| 'dashed' \| 'dotted' \| 'wave' \| 'zigzag'` | `'line'` | `'line'`, `'dashed'` or `'dotted'`, which follow wrapping text, or `'wave'` or `'zigzag'`, which keep the text on one line. | | `trigger` | `'view' \| 'hover' \| 'mount'` | `'view'` | What draws the mark: `'view'` (scrolled into view), `'hover'` (pointer enter, undraws on leave) or `'mount'`. | | `duration` | `number` | `0.6` | Spring duration of the drawing, in seconds. | | `delay` | `number` | `0` | Seconds to wait before drawing. | | `bounce` | `number` | `0` | Spring bounciness. `0` keeps the stroke from overshooting the text. | | `loop` | `boolean` | `false` | Draw, hold, undraw, draw again on repeat. With `trigger="view"` the cycle runs while the mark is on screen. | | `hold` | `number` | `1.6` | With `loop`, seconds the mark stays drawn before it undraws. | | `gap` | `number` | `0.5` | With `loop`, seconds the mark stays undrawn before it draws again. | | `pause-on-hover` | `boolean` | `false` | Pause the loop while the pointer is over the mark, resuming on leave. | | `once` | `boolean` | `true` | With `trigger="view"`, draw only the first time. Set `once="false"` to undraw on leave and draw again on every entry. Ignored with `loop`. | | `threshold` | `number` | `0.6` | Share of the element, from 0 to 1, that must be visible before it draws. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | | `replay()` | Draws the mark again from the start. | | `sweep()` | Sweeps a drawn mark across: it erases from its start, so it leaves at its end, then draws in again from the start. Calls while a sweep runs, before the mark is drawn, or under reduced motion do nothing. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The phrase to underline. It may wrap across lines. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-mark-color` | Color of the line. Default `currentColor`. | | `--mc-mark-thickness` | Thickness of the line. Default `2px`. | ## Rotating word swap. Cycles through a comma-separated list of `words`, springing the host width between values and crossfading each word with a translate + blur transition. Optional per-word `colors` list. **Use it for:** one word inside a heading or sentence that should change on a timer, such as a verb that takes turns with other verbs. **Avoid it for:** information people must read in full, because only one word shows at a time, and long phrases, which do not wrap. To type a phrase out letter by letter, use `motion-typewriter`. **Accessibility:** only the current word is in the page, and changes are not announced (there is no live region), so a screen reader reads the word that shows at that moment. Make sure the sentence makes sense with every word. The words change without end, and the component gives users no way to stop them. Check that every color in `colors` has enough contrast with the background. **Reduced motion:** the words still change every `interval` seconds, but each change is instant, with no slide, blur or width animation. `pause()`, `play()` and `cancel()` control the cycle as usual. **Common mistakes:** putting the words as child text: the component only reads the `words` attribute and does not show its children. A `words` list with fewer than two entries never changes. Docs: https://www.motion-components.dev/docs/text/motion-words/ ### Install ```js import 'motion-components/motion-words' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `words` | `string` | `''` | Comma-separated list of words to cycle through. | | `colors` | `string` | `''` | Comma-separated list of CSS colors, one per word. Optional. | | `interval` | `number` | `2` | Time each word stays visible, in seconds. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ## Animates any SVG icon on a spring: it draws the strokes in, or makes the icon pop, bounce, rotate, wiggle, pulse or slide up, down, left or right. It can also do both at once, as in `animation="draw wiggle"`. Every run starts from where the icon is, so hovering again mid-animation never makes it jump. With `trigger="hover"`, a slide holds while the pointer is over it and springs back on leave. Pass the icon as a URL in `src`, as an SVG string in `icon`, or as a child ``. Stroke sets such as Lucide, Tabler, Heroicons and Iconoir can draw; filled icons such as Phosphor and Bootstrap pop instead. The icon takes `currentColor`. Inside a button, a link or a ``, `hover` and `click` follow that element. **Use it for:** icons that draw in or move when people hover or click a button or link, when they scroll into view, or once when the page loads. It works with any SVG icon. **Avoid it for:** icons that switch between two states, such as menu and close or play and pause; use `motion-icon-state`. Avoid `trigger="loop"` next to text people need to read, because the loop never stops on its own. **Accessibility:** without `label` the icon is decorative and gets `aria-hidden="true"`. With `label` it gets `role="img"` and that name as `aria-label`. The icon is never focusable, and `trigger="hover"` reacts to the pointer only, not to keyboard focus. Inside a button or link with visible text, leave `label` empty. An icon-only button needs a name: set `aria-label` on the button, or `label` on the icon. **Reduced motion:** the icon shows its final state at once, with no animation. With `draw` and `trigger="view"`, the icon stays hidden until it scrolls into view and then appears at once. **Common mistakes:** using `animation="draw"` with a filled icon, such as Phosphor or Bootstrap: there are no strokes to draw, so it pops instead. Setting `label` on an icon inside a button that already has text, so screen readers read the name twice. Listing two motions, such as `animation="pop wiggle"`: only `draw` combines with a motion, so only the first motion runs. Docs: https://www.motion-components.dev/docs/icons/motion-icon/ ### Install ```js import 'motion-components/motion-icon' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `src` | `string` | `''` | URL of an SVG file, for example from a CDN or your own `/icons` folder. Responses are cached per URL. | | `icon` | `string` | `''` | SVG markup to render, for example `import { Heart } from 'lucide-static'`. Takes precedence over `src`. | | `animation` | `IconAnimation` | `'draw'` | `'draw'`, `'pop'`, `'bounce'`, `'rotate'`, `'wiggle'`, `'pulse'`, `'slide-up'`, `'slide-down'`, `'slide-left'` or `'slide-right'`, or `draw` plus one of the others, such as `'draw slide-right'`, to run both. `draw` needs a stroke icon and falls back to `pop`. | | `trigger` | `'hover' \| 'click' \| 'view' \| 'mount' \| 'loop'` | `'hover'` | What starts the animation: `'hover'`, `'click'`, `'view'` (scrolled into view), `'mount'` or `'loop'`. | | `duration` | `number` | `0.6` | Duration of one run, in seconds. | | `bounce` | `number` | `0.4` | Spring bounciness as the icon settles (0 = no overshoot). `wiggle` always uses at least `0.6`. | | `delay` | `number` | `0` | Seconds to wait before the animation starts. | | `interval` | `number` | `1.2` | Seconds to pause between runs when `trigger="loop"`. | | `once` | `boolean` | `true` | With `trigger="view"`, only animate the first time. Set `once="false"` to replay on every entry. | | `label` | `string` | `''` | Accessible name. Without it the icon is decorative and hidden from screen readers. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Restarts the animation from its starting state. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `error` | When `src` cannot be loaded. | | `motion-start` | When an animation run starts. | | `motion-finish` | When an animation run finishes. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | An inline `` icon. Shown while `src` loads and ignored once `icon` or `src` renders. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-icon-size` | Width and height of the icon. Default `1.5em`. | | `--mc-icon-color` | Icon color. Default `currentColor`. | | `--mc-icon-fill` | Fills the inside of an outline icon. Default `none`. | ### CSS parts | Part | Description | | --- | --- | | `icon` | The wrapper around the rendered `icon` SVG. | ## An icon that morphs between two states with spring physics: menu and close, play and pause, copy and check, plus and minus, chevron down and up, an empty and a filled heart, loading and done, and eye and eye-off. Drawn on a 24 px grid with 2 px round strokes, so it sits next to Lucide, Tabler and Heroicons. **Use it for:** controls and indicators that switch between two states, such as a menu button, a play and pause control, a copy button that confirms, or a like button. **Avoid it for:** icons outside the eight built-in pairs, or icons that only animate on hover or on scroll; use `motion-icon`, which takes any SVG. **Accessibility:** with `toggle`, the icon gets `role="button"`, `aria-pressed` that follows `active`, and `tabindex="0"` unless you set one. Space and Enter switch it, and keyboard focus shows an outline. `label` becomes the `aria-label`; without it, a toggle logs a warning and falls back to the icon name, such as "menu". Without `toggle`, a `label` gives the icon `role="img"`, and no `label` hides it from screen readers. **Reduced motion:** the icon switches to the new state at once, with no morph, and the loading spinner does not spin. **Common mistakes:** setting `toggle` on an icon inside your own ` ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `depth` | `number` | `0.5` | Parallax intensity. `0` = no movement (scrolls with page), `1` = strong drift. | | `axis` | `'x' \| 'y'` | `'y'` | Scroll axis: `'x'` for horizontal, `'y'` for vertical. | | `container` | `string` | `''` | CSS selector for a custom scroll container element (defaults to the document). | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | The content that parallax-scrolls. | ## Scroll-driven scene with sticky positioning and data-driven child animations. Children declare `data-from`/`data-to` JSON and `data-start`/`data-end` progress range to interpolate transform and CSS properties on scroll. **Use it for:** scroll stories where a stage stays pinned while its children move, scale, rotate or fade as the reader scrolls, such as a product reveal. **Avoid it for:** a simple drift on one element; use `motion-parallax`. For a one-time entrance, use `motion-reveal`. Do not put long text in the stage: it is one screen tall and cuts off what does not fit. **Accessibility:** the component adds no roles or aria attributes. Children stay in the page and in reading order, so screen readers can read them at any scroll position, even when `data-from` sets `opacity` to 0. The stage has `overflow: hidden` and is as tall as the viewport or the `container`, so check the scene with large text and on small screens. **Reduced motion:** the scroll animation does not start. Children show their `data-to` end state at once. The scene keeps its `height` and its sticky stage. **Common mistakes:** hiding children in CSS, for example with `opacity: 0`, and showing them only through `data-to`: only the properties listed in `data-from` are animated, so they stay hidden. Put the start values in `data-from` instead. Writing `data-from` or `data-to` as invalid JSON, such as with single quotes: the child is skipped without an error. Docs: https://www.motion-components.dev/docs/scroll/motion-scene/ ### Install ```js import 'motion-components/motion-scene' ``` Or without a build step: ```html ``` ### Example ```html
    Reveal on scroll
    ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `height` | `string` | `'200vh'` | Height of the scroll-driven scene (e.g. `"200vh"`, `"150%"`). | | `pin` | `boolean` | `true` | Whether the inner stage is `position: sticky`. Set `pin="false"` to turn it off. | | `container` | `string` | `''` | CSS selector for a custom scroll container element (defaults to the document). | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | Content rendered inside the sticky viewport-stage. Any child with `data-from` and `data-to` attributes will be animated on scroll. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-progress` | Set by the scene on itself: scroll progress from `0` to `1`. Read it in your own CSS, for example `calc(var(--mc-progress) * 200)`. | ## Countdown timer with animated digit transitions. Supports flip (default) and roll (slot-machine) modes. Customisable format, labels, and target date. **Use it for:** the time left until a launch, an event start or the end of a sale, shown in days, hours, minutes and seconds. **Avoid it for:** elapsed time or a stopwatch, because it only counts down and stays at zero when the time is up. To count a number up once, use `motion-counter`. **Accessibility:** the numbers and unit labels are plain text, and the `:` separators are hidden from screen readers. There is no live region, so screen readers do not announce each second. In `roll` mode the columns of the numbers 0 to 9 are `aria-hidden`, and screen readers read the current value of each unit from a visually hidden copy. **Reduced motion:** the timer still updates every second, but each digit changes in place with no flip or roll. `motion-start` and `motion-finish` fire as usual, and `pause()`, `play()` and `cancel()` control the timer. **Common mistakes:** setting a new `to` after the time is up; the timer has stopped, so call `play()` to count down again. Leaving `days` out of `format` when the target is more than a day away; hours only go from 0 to 23, so the days are lost. Docs: https://www.motion-components.dev/docs/components/motion-countdown/ ### Install ```js import 'motion-components/motion-countdown' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `to` | `string` | `''` | ISO date string the countdown counts down to. | | `format` | `string` | `'days hours minutes seconds'` | Space-separated list of units to display: `days`, `hours`, `minutes`, `seconds`. | | `labels` | `boolean` | `true` | Whether to show unit labels (Days, Hours, etc.) beneath each value. Set `labels="false"` to turn it off. | | `roll` | `boolean` | `false` | Enable slot-machine roll animation instead of the default flip. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | Once when the time is up, or when `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-countdown-size` | Font size of the digits. Default `3.5rem`. | | `--mc-countdown-color` | Digit color. Default `currentColor`. | | `--mc-countdown-gap` | Space between units. Default `0.75rem`. | | `--mc-countdown-label-size` | Font size of the unit labels. Default `0.65rem`. | | `--mc-countdown-label-color` | Unit label color. Default `currentColor`. | ## Animated modal dialog. Slides up with spring physics on open, springs back down on close. Built on the native `` element for top-layer stacking and built-in accessibility. Open/close via the `open` attribute or `show()` / `close()` methods. **Use it for:** confirmations, short forms and focused tasks that must block the rest of the page until the person responds. **Avoid it for:** hints, menus or notes that should not block the page; use the native `popover` attribute or `
    ` for those. To enlarge images from a grid, use `motion-gallery`, which has its own lightbox. **Accessibility:** it opens a native `` with `showModal()`, so the browser moves focus into the dialog and makes the rest of the page inert. Escape closes it with the exit animation. The component gives the dialog no accessible name, so start the content with a clear heading, and always include a visible close button. **Reduced motion:** the panel and the backdrop appear and disappear at once, with no slide or fade. `motion-close` still fires after the dialog closes. **Older browsers:** Safari before 17.4 and Chrome before 122 do not pass custom properties to `::backdrop`. There the backdrop appears at once, without a fade, and uses the default color and blur. **Common mistakes:** listening for the native `close` event on `motion-dialog`; it does not reach the host, so listen for `motion-close`. Expecting a click outside the panel to close the dialog; that only happens with `light-dismiss`. Docs: https://www.motion-components.dev/docs/components/motion-dialog/ ### Install ```js import 'motion-components/motion-dialog' ``` Or without a build step: ```html ``` ### Example ```html

    Hello

    Spring-animated modal dialog.

    ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `open` | `boolean` | `false` | Whether the dialog is open. Set this attribute to open declaratively. | | `duration` | `number` | `0.5` | Spring duration for enter/exit animations, in seconds. | | `bounce` | `number` | `0.25` | Spring bounciness (0 = critically damped, higher = more elastic). | | `y` | `number` | `40` | Initial vertical offset for the slide-up entrance, in pixels. | | `no-backdrop` | `boolean` | `false` | When present, the dimmed/blurred overlay is hidden. The dialog floats without a backdrop. | | `light-dismiss` | `boolean` | `false` | When present, clicking the backdrop outside the panel closes the dialog. | ### Methods | Method | Description | | --- | --- | | `show()` | Opens the dialog with the entrance animation. | | `close()` | Closes the dialog with the exit animation. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-open` | Dispatched when the dialog opens, as the entrance animation starts. | | `motion-close` | Dispatched after the close animation completes. | ### Slots | Slot | Description | | --- | --- | | (default) | Content rendered inside the dialog panel. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-dialog-bg` | Panel background. Default `var(--mc-color-surface, Canvas)`. | | `--mc-dialog-color` | Panel text color. Default `var(--mc-color-text, CanvasText)`. | | `--mc-dialog-radius` | Panel corner radius. Default `16px`. | | `--mc-dialog-padding` | Panel padding. Default `2rem`. | | `--mc-dialog-max-width` | Maximum panel width. Default `min(560px, 100vw - 2rem)`. | | `--mc-dialog-max-height` | Maximum panel height. Default `100dvh - 4rem`. | | `--mc-dialog-shadow` | Panel box shadow. The default ends with a 1px ring in `var(--mc-color-border, rgba(0, 0, 0, 0.08))`. | | `--mc-dialog-backdrop-color` | Backdrop overlay color. Default `rgba(0, 0, 0, 0.48)`. | | `--mc-dialog-backdrop-blur` | Backdrop `backdrop-filter`. Default `blur(6px)`. | ## Two-sided card that flips between a `front` and `back` slot with spring physics. Trigger on hover or click; choose the rotation axis. **Use it for:** a short teaser on the front with a few lines of detail on the back, such as a team member card or a flash card. **Avoid it for:** information that people must not miss, because the back stays hidden until the card flips. For a simple lift on hover, use `motion-hover`. **Accessibility:** with `trigger="click"` the card gets `role="button"` and `tabindex="0"`, and Enter or Space flips it. With the default `trigger="hover"` it reacts only to the pointer, so keyboard users cannot flip it. Both faces stay in the page, so screen readers read the front and the back at any time. A click card reports its face with `aria-pressed`. **Reduced motion:** the card shows the other face at once, with no rotation. **Common mistakes:** leaving the card without a size; both faces are positioned on top of each other, so the card has no height of its own. Set a CSS width and height on `motion-flip-card`. Putting links or buttons inside a `trigger="click"` card; they sit inside a button, and a click on them also flips the card. Docs: https://www.motion-components.dev/docs/components/motion-flip-card/ ### Install ```js import 'motion-components/motion-flip-card' ``` Or without a build step: ```html ``` ### Example ```html
    Tap me
    Hello, back side.
    ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `trigger` | `'hover' \| 'click'` | `'hover'` | What flips the card: `'hover'` or `'click'`. | | `axis` | `'x' \| 'y'` | `'y'` | Rotation axis: `'y'` (around vertical) or `'x'` (around horizontal). | | `duration` | `number` | `0.7` | Spring duration of the flip, in seconds. | | `bounce` | `number` | `0.2` | Spring bounciness (0 = critically damped, higher = more elastic). | | `perspective` | `number` | `1000` | CSS perspective applied to the host, in pixels. | | `flipped` | `boolean` | `false` | Whether the back is showing. Setting it turns the card without firing `motion-change`. | ### Methods | Method | Description | | --- | --- | | `flip()` | Turns the card to its other face and fires `motion-change`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-change` | When a hover, click, key or `flip()` turns the card. `detail: { flipped }`. | ### Slots | Slot | Description | | --- | --- | | `front` | The face shown at rest. | | `back` | The face revealed after flipping. | ## Image gallery with a spring-animated expand/collapse lightbox. Grid layout with hover scaling on each item. Opens a full-viewport lightbox with prev/next navigation, keyboard support, and focus trapping. **Use it for:** a grid of photos or screenshots that people open one at a time in a large view and step through. **Avoid it for:** a single image, or images that link to other pages, because every item becomes a button. To compare two versions of one image, use `motion-image-compare`. To show one large slide at a time, use `motion-slider`. **Accessibility:** the host gets `role="group"` and the label "Image gallery". Each item gets `role="button"`, `tabindex="0"` and `aria-haspopup="dialog"`, and opens with Enter or Space. The lightbox has `role="dialog"` and `aria-modal="true"`. Focus moves to its close button, Tab stays on the lightbox buttons, the Left and Right arrow keys change the item, a live region reads the position, and Escape closes it and returns focus. Each item's label is its `data-caption`, or "Open item 1 of 3" when there is none, and this label replaces the `alt` text, so give every item a `data-caption`. **Reduced motion:** the entrance stagger and the hover lift are off, the backdrop shows at once, and opening, changing and closing items happen with no animation. **Common mistakes:** adding items after the gallery is in the page; it reads its children once, so later items do not open the lightbox. Styling items with `motion-gallery > img`; each item is moved into a `motion-hover` wrapper (and into a `motion-stagger` when `stagger` is on), so use a class on the item instead. Docs: https://www.motion-components.dev/docs/components/motion-gallery/ ### Install ```js import 'motion-components/motion-gallery' ``` Or without a build step: ```html ``` ### Example ```html Photo 1 Photo 2 Photo 3 ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `columns` | `number` | `3` | Number of grid columns. | | `gap` | `number` | `16` | Grid gap in pixels. | | `aspect-ratio` | `string` | | CSS `aspect-ratio` for each item. Unset by default, so items keep their intrinsic ratio. | | `stagger` | `boolean` | `true` | Stagger the entrance animation. Set `"false"` to disable. Read once on connect. | ### Properties | Property | Type | Description | | --- | --- | --- | | `index` | `number` | Index of the item open in the lightbox, or -1 while it is closed. | ### Methods | Method | Description | | --- | --- | | `open()` | Opens the lightbox on the item at `index`, or moves to it when the lightbox is already open. | | `close()` | Closes the lightbox. | | `prev()` | Moves the lightbox to the previous item. Does nothing on the first one or while closed. | | `next()` | Moves the lightbox to the next item. Does nothing on the last one or while closed. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `name` | | | `motion-open` | The lightbox opened. `detail: { index }`. | | `motion-change` | The lightbox moved to another item. `detail: { index }`. | | `motion-close` | The lightbox closed. | ### Slots | Slot | Description | | --- | --- | | (default) | Gallery items. Each item should have a unique `data-caption` for the lightbox caption overlay (optional). Items are expected to be images or media elements. `columns`, `gap` and `aspect-ratio` are live. `stagger` is read once, when the entrance animation runs. | ## Before/after image comparison slider with a draggable, spring-damped handle. Slot two children with `slot="before"` and `slot="after"`. Keyboard-accessible: focus the handle and use arrow keys to nudge the split. **Use it for:** before and after views of the same scene, such as a photo edit or a redesign, with two images of the same size. **Avoid it for:** more than two images, or two images with different framing. For a set of images, use `motion-gallery` or `motion-slider`. **Accessibility:** the round knob takes keyboard focus. The arrow keys move the split by 2% (Left and Right, or Up and Down when `orientation="vertical"`), and Shift with an arrow key moves it by 10%. Home and End move it to 0% and 100%. The knob has `role="slider"`, the label "Image comparison" and a value from 0 to 100, so screen readers announce it. Write `alt` text that says which image is before and which is after. **Reduced motion:** a click or a key press moves the split at once, with no spring. Dragging follows the pointer directly, as it always does. **Common mistakes:** leaving the element without a size; both images are positioned on top of each other, so it has no height of its own. Set a CSS height or aspect ratio on `motion-image-compare`. On touch screens a swipe that starts on it moves the split and does not scroll the page, so leave room around it on phones. Docs: https://www.motion-components.dev/docs/components/motion-image-compare/ ### Install ```js import 'motion-components/motion-image-compare' ``` Or without a build step: ```html ``` ### Example ```html Before After ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `start` | `number` | `50` | Initial split position as a percentage (0–100). | | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Drag axis. | | `bounce` | `number` | `0.25` | Spring bounciness of the snap-to-cursor animation. | | `duration` | `number` | `0.45` | Spring duration of the snap-to-cursor animation, in seconds. | ### Properties | Property | Type | Description | | --- | --- | --- | | `position` | `number` | Split position as a percentage (0–100), where the split is going. Setting it springs the split there without firing `motion-change`; before the first render it sets the position the split starts at. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-change` | When a drag, click or key moves the split. `detail: { position }`, the percentage it moves to. | ### Slots | Slot | Description | | --- | --- | | `before` | The left/top image (visible behind the clip). | | `after` | The right/bottom image (revealed by dragging). | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-color-accent` | Color of the keyboard focus ring on the knob. | ## Fixed progress bar driven by document or per-element scroll. Spring-eased scaleX transform — settles past the value rather than hard-snapping. **Use it for:** a thin reading progress bar at the top or bottom of the window on long articles and documentation pages. **Avoid it for:** the progress of a task such as an upload; it only follows scrolling and has no value you can set. Use the native `` element for that. **Accessibility:** the bar is decoration only, so it has `aria-hidden="true"` and screen readers skip it. Screen reader users follow their own reading position, not the scroll position. The bar never takes focus and ignores the pointer. **Reduced motion:** the bar still follows scrolling, but without the spring. Its width matches the scroll position exactly. **Common mistakes:** pointing `target` at a box that scrolls on its own; the bar measures how that element moves through the window, not scrolling inside it. Setting `target` to an element that is not in the page yet when the bar starts; the bar then follows the whole document instead. Docs: https://www.motion-components.dev/docs/components/motion-progress/ ### Install ```js import 'motion-components/motion-progress' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `position` | `'top' \| 'bottom'` | `'top'` | `'top'` or `'bottom'` of the viewport. | | `thickness` | `number` | `3` | Bar thickness in pixels. | | `target` | `string` | `''` | CSS selector of the scroll target. Defaults to the document. | | `bounce` | `number` | `0.15` | Spring bounciness applied to the scaleX response (0 = none). | | `duration` | `number` | `0.4` | Spring duration of the scaleX response, in seconds. | ### Properties | Property | Type | Description | | --- | --- | --- | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-progress-color` | Bar color. Default `var(--mc-color-accent, #2563eb)`. | ## Horizontal slider with spring-snap drag, arrow-button navigation, and dot indicators. Slides snap to full-viewport-width positions on release. Responsive to container resize. **Use it for:** a set of full-width slides, such as product photos or testimonials, that people move through by dragging, with the arrows or with the dots. **Avoid it for:** content that everyone must see, because later slides are easy to miss. It always shows one slide at the full width, so it does not fit a row of small cards. For a grid of images that open large, use `motion-gallery`. **Accessibility:** the slider takes keyboard focus, and the Left and Right arrow keys change the slide. The arrows are native buttons labelled "Previous slide" and "Next slide", and keyboard focus on them shows an outline. At the first and last slide, the arrow that cannot move has `aria-disabled="true"` and does nothing. The dots are not focusable and have no labels. The slider has no role or label and does not announce slide changes, and slides out of view are not hidden from screen readers or the Tab key. Add `role="region"` and an `aria-label` to the slider, and make sure each slide makes sense on its own. **Reduced motion:** the slide changes at once, with no spring. While you drag, the slides still follow the pointer, and the dots still use a short CSS transition. **Common mistakes:** adding slides after the slider is built; it reads its children once, so later children do not become slides. Docs: https://www.motion-components.dev/docs/components/motion-slider/ ### Install ```js import 'motion-components/motion-slider' ``` Or without a build step: ```html ``` ### Example ```html
    Slide 1
    Slide 2
    Slide 3
    ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `gap` | `number` | `24` | Space between slides in pixels. | | `arrows` | `boolean` | `true` | Show prev/next arrow buttons. Set `"false"` to hide them. | ### Properties | Property | Type | Description | | --- | --- | --- | | `index` | `number` | Index of the active slide. | ### Methods | Method | Description | | --- | --- | | `next()` | Moves to the next slide. Does nothing on the last one. | | `prev()` | Moves to the previous slide. Does nothing on the first one. | | `goTo()` | Navigate to the slide at `index`. Pass `initialVelocity` (px/s) for a flick-snap feel. Called before the slider is built, it sets the slide the slider starts on. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-change` | Dispatched when the active slide index changes. `detail: { index }`. | ### Slots | Slot | Description | | --- | --- | | (default) | Slides. Each direct child becomes a slide in the carousel. Both attributes are read once while the slider is built. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-color-accent` | Color of the keyboard focus ring on the arrows. | ## Mouse-tracked radial-gradient spotlight overlay. Wrap any content; the spotlight fades in on hover and springs toward the cursor for a smooth, weightful feel rather than locking 1:1 to the pointer. **Use it for:** a soft glow that follows the pointer over cards, feature panels or a hero section, as a decorative touch. **Avoid it for:** anything that carries meaning, because keyboard, touch and reduced motion users may never see it. To move or lift the element itself on hover, use `motion-hover` or `motion-tilt`. **Accessibility:** the glow layer has `aria-hidden="true"` and ignores the pointer, so it does not change what screen readers read or what people can click. When content inside it takes keyboard focus, the glow fades in at the last pointer position, or at the center when the pointer is not over it. **Reduced motion:** the glow does not show and does not follow the pointer. **Common mistakes:** setting `border-radius` on the inner card only; the glow copies the radius of `motion-spotlight`, so round the host too. Using a strong `--mc-spotlight-color` over text; the glow sits on top of the content and can lower the text contrast. Docs: https://www.motion-components.dev/docs/components/motion-spotlight/ ### Install ```js import 'motion-components/motion-spotlight' ``` Or without a build step: ```html ``` ### Example ```html
    …
    ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `size` | `number` | `400` | Diameter of the spotlight in pixels. | | `duration` | `number` | `0.35` | Spring duration of the follow and the fade, in seconds. Higher trails the cursor more. | | `bounce` | `number` | `0` | Spring bounciness of the follow (0 = no overshoot). | ### Slots | Slot | Description | | --- | --- | | (default) | The content the spotlight overlays. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-spotlight-color` | Center color of the radial gradient. Default `rgba(255,255,255,0.18)`. | ## Light / dark mode control with an optional third "system" option. Applies `data-theme` and `color-scheme` to a target element, follows the OS preference, optionally remembers the choice, and can reveal the new theme with a circular view-transition wipe from the control. API-compatible with Google Chrome Labs' `dark-mode-toggle`. **Use it for:** letting people choose a light or dark theme, or follow the system setting, for a whole site or for one part of a page. **Avoid it for:** sites with only one theme, and other on/off settings; use a native checkbox for those. To only show the current theme as an icon, use `motion-theme-icon`. **Accessibility:** `icon` and `switch` render a button with `role="switch"`, where checked means dark. With `system`, `icon` is a plain button whose label names the current mode, and each press moves to the next mode. `toggle` is a native radio group in a `
    `; with `icon-only` the labels stay available to screen readers. `menu` is a menu button: the Up and Down arrow keys open it and move between items, Home and End jump to the first and last item, and Escape closes it and returns focus to the button. Without `legend` the name is "Dark" for `icon` and `switch`, and "Theme" for `toggle` and `menu`, so set `legend` to a clear name. **Reduced motion:** the theme changes at once and `wipe` is skipped. The icon, the selection marker, the switch thumb and the menu change with no animation, and the hover and press effects are off. **Common mistakes:** writing dark styles only in a `prefers-color-scheme: dark` media query; the toggle cannot change that query, so style `[data-theme="dark"]` on the target. Relying on the component alone to apply a saved theme; it applies the theme only after it loads, so add a small inline script in `` to avoid a flash of the wrong theme. Docs: https://www.motion-components.dev/docs/components/motion-theme-toggle/ ### Install ```js import 'motion-components/motion-theme-toggle' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `mode` | `'light' \| 'dark' \| 'system'` | `'light'` | Selected mode: `'light'`, `'dark'`, or `'system'` (requires `system`). Defaults to the OS preference. | | `appearance` | `'icon' \| 'toggle' \| 'switch' \| 'menu'` | `'icon'` | Visual style: `'icon'`, `'toggle'` (segmented), `'switch'`, or `'menu'` (drop-down). | | `system` | `boolean` | `false` | Offers a third "system" option that follows the OS preference. Ignored by `switch`. | | `permanent` | `boolean` | `false` | Remembers the choice in `localStorage` (`motion-theme`, or `motion-theme:` for other targets) and restores it on load. | | `legend` | `string` | `''` | Group label. Used as the accessible name of `icon`, `switch` and `menu`. | | `light` | `string` | `'Light'` | Label of the light option. | | `dark` | `string` | `'Dark'` | Label of the dark option. | | `system-label` | `string` | `'System'` | Label of the system option. | | `remember` | `string` | `''` | When set, shows a checkbox with this label that toggles `permanent`. | | `target` | `string` | `'html'` | CSS selector of the element that receives `data-theme` and `color-scheme`. | | `wipe` | `boolean` | `false` | Reveals the new theme with a circular view-transition wipe from the control. Off by default. | | `icon-only` | `boolean` | `false` | Hides the option labels of the `toggle` appearance and shows icons only. Labels stay available to screen readers and as tooltips. | | `duration` | `number` | `0.5` | Spring duration of the icon morph and wipe, in seconds. | | `bounce` | `number` | `0.25` | Spring bounciness of the icon morph and controls. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `colorschemechange` | When the mode or applied color scheme changes. `detail: { colorScheme, mode }`. | | `permanentcolorschemechange` | When `permanent` changes. `detail: { permanent }`. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-theme-toggle-surface` | Background of the switch track, segmented group and menu trigger. | | `--mc-theme-toggle-border` | Border color of the controls. | | `--mc-theme-toggle-accent` | Selected segment, switch thumb and active menu item background. | | `--mc-theme-toggle-menu-bg` | Drop-down menu background. Default `Canvas`. | | `--mc-theme-toggle-radius` | Corner radius of the controls. Default `999px`. | | `--mc-theme-icon-size` | Icon size. Default `1.25em`. | ### CSS parts | Part | Description | | --- | --- | | `button` | The icon button (`icon`) or menu trigger (`menu`). | | `track` | The switch track (`switch`). | | `thumb` | The switch thumb (`switch`). | | `segment` | Each option of the segmented group (`toggle`). | | `menu` | The drop-down panel (`menu`). | | `item` | Each drop-down option (`menu`). | ## A bar or line chart drawn from HTML. Pass numbers in `values`, wrap a `` (first column labels, one series per column), or set `data` from JavaScript. Bars grow and lines draw in when the chart scrolls into view. When the data changes, every bar and point springs to its new value, and a change in the middle of an animation continues from where the chart is. The scale springs to its new range too: old grid lines fade out and new ones fade in. Set `animate-scale="false"` to switch the scale at once. **Use it for:** comparing values across categories with bars, or showing a trend over time with lines, for up to eight series. **Avoid it for:** parts of a whole; use `motion-pie`. For a small trend in a sentence or a table cell, use `motion-sparkline`. Avoid it for more than eight series, because series after the eighth are not drawn. **Accessibility:** the drawing is hidden from screen readers, which read a visually hidden data table instead: your own `
    `, or one built from `values` or `data` with `label` as its caption. The plot is a focusable group with the role description "chart" and `label` as its name. The Left and Right arrow keys move between points, Home and End jump to the first and last, Escape hides the tooltip, and a polite live region announces each point. Set `label`, and give each series a name. **Reduced motion:** bars and lines appear at their final values at once when the entrance would start (on mount, or when 30% of the chart is in view). Data changes and tooltip moves also happen at once. **Common mistakes:** writing thousands separators in `values`, such as `1,200`: commas separate values there, so it reads as 1 and 200. Using numbers such as years as series names in a header row of ``. Docs: https://www.motion-components.dev/docs/charts/motion-chart/ ### Install ```js import 'motion-components/motion-chart' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `type` | `'bar' \| 'line'` | `'bar'` | `'bar'` or `'line'`. | | `values` | `string` | `''` | Comma-separated numbers. Separate several series with `;`. Ignored when a `
    ` cells: the row is read as data. Use `` for every cell of that row, or a `
    ` or `data` is given. | | `labels` | `string` | `''` | Comma-separated labels for the `values`. | | `series` | `string` | `''` | Comma-separated series names for the `values`. | | `min` | | `NaN` | Lowest value on the axis. Defaults to the lower of `0` and the smallest value. | | `max` | | `NaN` | Highest value on the axis. Defaults to the largest value, rounded up. | | `format` | `string` | `''` | Number format: `'compact'`, `'percent'`, `'currency:EUR'` or `'unit:kilometer'`. | | `trigger` | `'view' \| 'mount'` | `'view'` | When the entrance runs: `'view'` (scrolled into view) or `'mount'`. | | `duration` | `number` | `0.8` | Spring duration in seconds. | | `bounce` | `number` | `0.25` | Spring bounciness (0 = no overshoot). | | `interval` | `number` | `0.04` | Seconds between one bar group and the next on entrance. | | `tooltip` | `boolean` | `true` | Shows a tooltip on hover and keyboard focus. Set `tooltip="false"` to hide it. | | `legend` | `boolean` | `true` | Shows a legend for two or more series. Set `legend="false"` to hide it. | | `label` | `string` | `''` | Accessible name of the chart. | | `animate-scale` | `boolean` | `true` | Springs the scale and its grid lines to a new range when the data changes. Set `animate-scale="false"` to switch the scale at once. | ### Properties | Property | Type | Description | | --- | --- | --- | | `data` | `ChartData \| null` | Data from JavaScript: `{ labels, series: [{ name, values }] }`. Takes precedence over a table and `values`. | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Restarts the entrance animation. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When the entrance animation starts. | | `motion-finish` | When the entrance animation finishes. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | An optional `
    ` with the data. It stays available to screen readers. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-chart-height` | Height of the chart, including the legend. Default `16rem`. | | `--mc-chart-1` | Color of the first series. `--mc-chart-2` to `--mc-chart-8` color the others. | | `--mc-chart-surface` | Background of the tooltip. Default `Canvas`. | ### CSS parts | Part | Description | | --- | --- | | `legend` | The legend, shown for two or more series. | | `tooltip` | The tooltip. | ## A pie or donut chart for parts of a whole. Pass numbers in `values`, wrap a `
    ` (labels in the first column, values in the second), or set `data` from JavaScript. The pie sweeps in when it scrolls into view, the slices spring to new sizes when the data changes, and the hovered slice springs outward. More than six slices are combined into "Other". **Use it for:** showing how a total splits into a few parts, such as traffic sources or a budget, with six slices or fewer. **Avoid it for:** values that are not parts of one whole, values over time, or many categories; use `motion-chart`. **Accessibility:** the drawing is hidden from screen readers, which read a visually hidden data table instead: your own `
    `, or one built from `values` or `data` that lists each label, value and share, with `label` as its caption. The plot is a focusable group with the role description "pie chart" and `label` as its name. The arrow keys move between slices, Home and End jump to the first and last, Escape clears the selection, and a polite live region announces each slice with its value and share. Set `label`. **Reduced motion:** the whole pie appears at once when the sweep-in would start (on mount, or when 30% of it is in view). New values, the hovered slice and the tooltip change at once, without springs. **Common mistakes:** passing negative values: they are drawn as 0. Passing more than six items: the sixth item and all after it are merged into one slice labeled "Other", so group small items yourself to choose the label. Docs: https://www.motion-components.dev/docs/charts/motion-pie/ ### Install ```js import 'motion-components/motion-pie' ``` Or without a build step: ```html ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `values` | `string` | `''` | Comma-separated numbers, one per slice. Ignored when a `
    ` or `data` is given. | | `labels` | `string` | `''` | Comma-separated slice labels. | | `donut` | `boolean` | `false` | Cuts out the center and shows the total there. | | `format` | `string` | `''` | Number format: `'compact'`, `'percent'`, `'currency:EUR'` or `'unit:kilometer'`. | | `total-label` | `string` | `'Total'` | Caption under the total in a donut. | | `trigger` | `'view' \| 'mount'` | `'view'` | When the sweep-in runs: `'view'` (scrolled into view) or `'mount'`. | | `duration` | `number` | `0.8` | Spring duration in seconds. | | `bounce` | `number` | `0.25` | Spring bounciness when the values change (0 = no overshoot). | | `tooltip` | `boolean` | `true` | Shows a tooltip on hover and keyboard focus. A donut shows the hovered slice in its center instead. Set `tooltip="false"` to hide it. | | `legend` | `boolean` | `true` | Shows a legend with each share. Set `legend="false"` to hide it. | | `label` | `string` | `''` | Accessible name of the chart. | ### Properties | Property | Type | Description | | --- | --- | --- | | `data` | `PieData \| null` | Data from JavaScript: `{ labels, values }`. Takes precedence over a table and `values`. | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Sweeps the pie in again. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When the sweep-in starts. | | `motion-finish` | When the sweep-in finishes. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### Slots | Slot | Description | | --- | --- | | (default) | An optional `
    ` with the data. It stays available to screen readers. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-chart-height` | Height of the chart, including the legend. Default `16rem`. | | `--mc-chart-1` | Color of the first slice. `--mc-chart-2` to `--mc-chart-6` color the others. | | `--mc-chart-surface` | Background behind the chart, used for the gap between slices. Default `Canvas`. | ### CSS parts | Part | Description | | --- | --- | | `legend` | The legend. | | `tooltip` | The tooltip. | ## A small trend line that sits inside a sentence, a table cell or a stat tile. It draws itself in when it scrolls into view, and springs to new values when `values` or `data` changes. **Use it for:** a small trend next to a number, in a sentence, a table cell or a stat tile, where the shape matters more than the exact values. **Avoid it for:** data people need to read exactly or explore. It has no axes, tooltip or keyboard support; use `motion-chart` instead. **Accessibility:** it has `role="img"` and an `aria-label`: your `label`, or a generated summary such as "from 3 to 11, low 3, high 11". The drawing itself is hidden, and the sparkline is not focusable. The summary has no units or context, so set `label`, for example "Signups this week, from 3 to 11", when the text nearby does not explain the trend. **Reduced motion:** the line appears in full at once when the draw-in would start (on mount, or when half of it is in view). New values replace the old ones at once. **Common mistakes:** passing fewer than two values: nothing is drawn. Writing thousands separators, such as `1,200`: commas separate values, so it reads as 1 and 200. Docs: https://www.motion-components.dev/docs/charts/motion-sparkline/ ### Install ```js import 'motion-components/motion-sparkline' ``` Or without a build step: ```html ``` ### Example ```html Signups this week ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `values` | `string` | `''` | Comma-separated numbers. Ignored when `data` is set. | | `area` | `boolean` | `false` | Fills the area under the line with a light wash. | | `trigger` | `'view' \| 'mount'` | `'view'` | When the draw-in runs: `'view'` (scrolled into view) or `'mount'`. | | `duration` | `number` | `1` | Spring duration in seconds. | | `bounce` | `number` | `0.2` | Spring bounciness when the values change. | | `label` | `string` | `''` | Accessible name. Without it the name describes the trend, for example "from 3 to 11, low 3, high 11". | ### Properties | Property | Type | Description | | --- | --- | --- | | `data` | `number[] \| null` | The numbers as an array, set from JavaScript. Takes precedence over `values`. | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Draws the line in again. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When the draw-in starts. | | `motion-finish` | When the draw-in finishes. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-sparkline-width` | Width. Default `6em`. | | `--mc-sparkline-height` | Height. Default `1.5em`. | | `--mc-sparkline-color` | Line color. Default `currentColor`. | | `--mc-sparkline-accent` | Color of the dot on the last value. Default the line color. | ## Syntax-highlighted code block with macOS-style window chrome, an optional copy button, and an optional typewriter reveal. Highlighting is built in for HTML, JS/TS, CSS, JSON and Python, with no external tokenizer; token colours are overridable through CSS custom properties. Source is read from a ` ``` ### Example ```html ``` ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `filename` | `string` | `'app.html'` | Name shown in the window chrome. Doubles as the language hint: the file extension picks the tokenizer unless `code-lang` overrides it. | | `code-lang` | `string` | `''` | Explicit highlighting language, overriding the `filename` extension. One of `html`, `js`, `css`, `json` or `python`; anything else renders unhighlighted. | | `hide-chrome` | `boolean` | `false` | Hide the whole title bar: traffic-light dots, filename and copy button. Suppresses `copy` as a side effect, since the button lives in the chrome. | | `copy` | `boolean` | `false` | Show a copy-to-clipboard button in the chrome. Copies the dedented source, not the highlighted markup. | | `copy-label` | `boolean` | `false` | Label the copy button with "Copy" / "Copied!" text instead of showing the icon alone. | | `compact` | `boolean` | `false` | Denser layout: smaller radius, tighter padding and a smaller type scale. | | `typing` | `boolean` | `false` | Reveal the code with a typewriter animation instead of rendering it at once. | | `typing-speed` | `number` | `80` | Typing speed in characters per second. Total duration is derived from the character count. | | `typing-delay` | `number` | `0` | Delay in seconds before the first character appears, applied on every pass. | | `typing-loop` | `boolean` | `true` | Repeat the typing animation. Set `typing-loop="false"` to type once; the animation then waits until the block scrolls into view instead of starting on connect. | | `typing-hold` | `number` | `1.5` | Seconds to hold the completed text before restarting. Ignored when `typing-loop="false"`. | ### Properties | Property | Type | Description | | --- | --- | --- | | `code` | `string` | The source shown, before highlighting. Setting it is the same as `setCode()`. | | `playState` | `'idle' \| 'running' \| 'paused' \| 'finished'` | Current state: `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | `Promise` | Resolves when the current run settles. A new run replaces the promise. | ### Methods | Method | Description | | --- | --- | | `replay()` | Restart the typing animation from the beginning | | `setCode()` | Replaces the code. A typing run in progress is cancelled with `motion-cancel`, and typing starts again. | | `play()` | Starts the animation, or resumes it when paused. Resolves once the run settles. Under `prefers-reduced-motion: reduce` the final state is applied immediately without animating. | | `pause()` | Holds the animation at its current position. No-op unless running. | | `finish()` | Jumps to the final state and settles. Fires `motion-finish`. | | `cancel()` | Reverts to the initial state and returns to idle. Fires `motion-cancel`. | ### Events The `motion-*` events bubble and are composed. | Event | Description | | --- | --- | | `motion-start` | When a run starts. | | `motion-finish` | When a run finishes, or `finish()` jumps to the end. | | `motion-cancel` | When `cancel()` stops a run and resets it. | ### CSS custom properties | Property | Description | | --- | --- | | `--mc-code-keyword` | Keyword token colour. | | `--mc-code-string` | String token colour. | | `--mc-code-tag` | Tag and selector token colour. | | `--mc-code-attr` | Attribute and property token colour. | | `--mc-code-num` | Numeric token colour. | | `--mc-code-comment` | Comment token colour. Falls back to `--mc-color-muted`. | | `--mc-color-surface` | Background of the code area. | | `--mc-color-surface-2` | Background of the window and its title bar. | | `--mc-color-border` | Window border, title bar divider and copy button border. | | `--mc-color-muted` | File name, copy button and typing cursor color. | | `--mc-color-accent` | Copy button color on hover. | ## Inline code snippet with an optional copy-to-clipboard button. Renders the slotted text inside a styled `` element; the copy button fades in on hover, or stays visible with `copy-visible`. The copy icon is a `motion-icon-state` that morphs into a check after copying, and it scales with the text. **Use it for:** short code inside running text, such as a command, a file name, an attribute or a CSS variable, with an optional copy button. **Avoid it for:** code that spans more than one line or is long, because it never wraps; use `motion-code` for blocks. **Accessibility:** the text is in a real `` element. The copy button is a native ` ) } ``` ##### Vue 3 Configure custom elements in Vite and add preload in your entry file. ```ts import { preloadCSS } from 'motion-components/preload' import 'motion-components' const style = document.createElement('style') style.textContent = preloadCSS document.head.appendChild(style) // vite.config.ts — tell Vue to accept motion-* tags import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [ vue({ template: { compilerOptions: { isCustomElement: tag => tag.startsWith('motion-') } } }) ] }) ``` ##### Angular Add `CUSTOM_ELEMENTS_SCHEMA` and the preload stylesheet to `angular.json`. ```json // angular.json — add preload.css to styles "styles": [ "node_modules/motion-components/dist/preload.css" ] // app.component.ts — register schema import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core' @Component({ selector: 'app-root', standalone: true, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: `` }) export class AppComponent {} ``` ### Flash prevention Web components are undefined when the HTML first renders. The browser treats `` as a plain `HTMLElement`. Its Light DOM children (`slot="back"` content, dialog panels, etc.) display as unstyled visible DOM before the JavaScript bundle arrives and calls `customElements.define()`. Slot-based components are especially sensitive because they carry visible Light DOM. #### Solution: inline CSS in `` The only 100% reliable fix: inject a ` {children} ) } ``` ##### Astro Import in frontmatter, inline with `is:inline`. ```astro --- import { preloadCSS } from 'motion-components/preload' --- ``` ##### Nuxt / Vue Use `useHead` in `app.vue` or your root layout. ```vue ``` ##### SvelteKit Inline in `+layout.svelte` via ``. ```svelte ``` ##### Angular Add `preload.css` to the `styles` array. ```json "styles": [ "node_modules/motion-components/dist/preload.css" ] ``` #### Alternative: CSS import Simpler to set up, slightly less reliable: import the `preload.css` file. Most bundlers convert this to a render-blocking ``. The browser won't paint until the CSS arrives. The gap between receiving the HTML and fetching the CSS is small but non-zero. Suitable for development and less flash-sensitive setups. ```js import 'motion-components/preload.css' ``` Or link it from a CDN in ``: ```html ``` ## JavaScript API > Play, pause, finish and cancel any motion component from JavaScript, wait for it to end, listen for its events, and control every component at once. Source: https://www.motion-components.dev/docs/js-api/ Every component is an HTML element, so you control it the way you control any element: find it, call a method, listen for an event. ### Quick start ```js const reveal = document.querySelector('motion-reveal') reveal.play() // start, or resume after pause() reveal.pause() // hold where it is reveal.finish() // jump to the end reveal.cancel() // stop and go back to the start reveal.replay() // cancel, then play reveal.playState // 'idle' | 'running' | 'paused' | 'finished' await reveal.finished // wait for the current run to end ``` Try the methods on a ticker: ### Methods and properties | Member | Type | Default | Description | | --- | --- | --- | --- | | `play()` | () => Promise | — | Starts the animation, or resumes it after `pause()`. Does nothing while it runs. The promise resolves when the run ends. | | `pause()` | () => void | — | Holds the animation where it is. `play()` continues it. | | `finish()` | () => void | — | Jumps to the end state. | | `cancel()` | () => void | — | Stops the animation and returns to the start state. | | `replay()` | () => void | — | Cancels, then plays. It means the same on every component that has it. Available on components with an animation that ends, such as the reveals, text effects, charts, icons and `motion-code`. | | `playState` | PlaybackState | 'idle' | `'idle'`, `'running'`, `'paused'` or `'finished'`. | | `finished` | Promise | — | Resolves when the current run ends, by finishing or by `cancel()`. Each run gets a new promise. It never rejects. | Some components add their own method. `motion-glitch` has `glitch()`, which fires one extra burst. `motion-underline`, `motion-marker`, `motion-strike` and `motion-ring` have `sweep()`, which erases the drawn mark from its start and draws it again. A sweep is a normal run, so it fires the same events and resolves the same promises. ### Wait for an animation `play()` returns a promise, so you can run code after an animation ends. ```js const reveal = document.querySelector('motion-reveal') reveal.cancel() await reveal.play() console.log('reveal complete') ``` An endless effect, such as `motion-ticker`, `motion-circle` or any component with `loop`, never ends by itself. Its promise resolves only when you call `finish()` or `cancel()`, so don't await it. ### Events Components fire these events. They bubble and cross shadow roots, so one listener on `document` hears every component on the page. | Event | Type | Default | Description | | --- | --- | --- | --- | | `motion-start` | CustomEvent | — | A run starts, from its trigger or from `play()`. | | `motion-finish` | CustomEvent | — | A run reaches its end, or `finish()` jumps there. | | `motion-cancel` | CustomEvent | — | `cancel()` stops a run and resets it. | | `motion-change` | CustomEvent | — | A widget changes state. `event.detail` holds the new state: `{ index }` on `motion-slider` and `motion-gallery`, `{ flipped }` on `motion-flip-card`, `{ position }` on `motion-image-compare` and `{ active }` on `motion-icon-state`. | | `motion-open` | CustomEvent | — | `motion-dialog` or the `motion-gallery` lightbox opens. On the gallery, `event.detail` is `{ index }`. | | `motion-close` | CustomEvent | — | `motion-dialog` or the `motion-gallery` lightbox closes. | ```js document.addEventListener('motion-finish', (event) => { console.log(event.target.localName, 'finished') }) ``` Some components contain others. `motion-gallery` uses `motion-stagger` and `motion-hover`, so a listener on the gallery also hears the stagger's events. When you listen on an element that contains other components, check `event.target` first. This matters most for `motion-change`: a flip card inside a slider fires one too, and it bubbles up to the slider. ```js const card = document.querySelector('#card') card.addEventListener('motion-finish', (event) => { if (event.target !== card) return console.log('the card finished') }) ``` ### Control every component at once Import these helpers to pause, resume or cancel every component on the page, or only the ones inside an element you pass. ```js import { pauseAll, resumeAll } from 'motion-components' // Pause everything while the tab is hidden document.addEventListener('visibilitychange', () => { document.hidden ? pauseAll() : resumeAll() }) // Or only the components inside one element pauseAll(document.querySelector('#hero')) ``` | Export | Type | Default | Description | | --- | --- | --- | --- | | `pauseAll(root?)` | (root?: Node) => void | document | Pauses every running component inside `root`, and disables the components that follow the pointer. It also holds endless decorative loops, such as the `motion-icon-state` loading spinner. | | `resumeAll(root?)` | (root?: Node) => void | document | Resumes what `pauseAll()` paused, and enables only what it disabled. | | `cancelAll(root?)` | (root?: Node) => void | document | Cancels every component inside `root`. | `pauseAll()` also reaches components inside other components, such as the stagger inside a gallery. ### TypeScript Each component registers its tag, so `querySelector` returns the right type without a cast. Use `MotionControllable` for code that works with any component. Events are typed too, so `addEventListener` knows the type of `event.detail`. The detail types are exported: `MotionChangeDetail`, `SliderChangeDetail`, `GalleryIndexDetail`, `FlipCardChangeDetail`, `ImageCompareChangeDetail` and `IconStateChangeDetail`. ```ts import type { MotionControllable, PlaybackState } from 'motion-components' const reveal = document.querySelector('motion-reveal') // MotionReveal | null function restart(el: MotionControllable) { el.cancel() void el.play() } const state: PlaybackState | undefined = reveal?.playState const slider = document.querySelector('motion-slider') slider?.addEventListener('motion-change', (event) => { console.log(event.detail) // MotionChangeDetail }) ``` ### Reduced motion When the visitor asks for reduced motion, `play()` shows the end state at once. It still resolves, and it still fires `motion-start` and `motion-finish`, so your code needs no special case. `motion-words` and `motion-countdown` keep running without animation, because changing words and counting are their job. ### Scroll-driven components `motion-parallax`, `motion-scene`, `motion-progress` and `motion-blur` follow the scroll position instead of a clock. `playState` is `'running'` while they follow it. `pause()` stops following and holds the current state, `play()` follows again, `finish()` shows the end state, and `cancel()` stops and resets. ### Components without these methods - **Pointer-driven:** `motion-hover`, `motion-press`, `motion-magnetic`, `motion-tilt` and `motion-stretch` follow the pointer. Set `disabled` to turn them off. `pauseAll()` does it for you. - **Widgets:** `motion-dialog`, `motion-gallery`, `motion-slider`, `motion-flip-card`, `motion-image-compare`, `motion-spotlight`, `motion-icon-state`, `motion-theme-icon` and `motion-theme-toggle` have their own methods: `show()` and `close()` on the dialog, `open()` and `next()` on the gallery, `goTo()` and `next()` on the slider, and `flip()` on the flip card and on `motion-icon-state`. The `motion-icon-state` loading spinner is an endless loop, and `pauseAll()` holds it. Each component's page lists the full set. - **Static:** `motion-code-inline` does not animate. ## Migrating to 1.0 > Every renamed attribute, unit change and new default between motion-components 0.x and 1.0, with how to convert each one. Source: https://www.motion-components.dev/docs/migration/ Version 1.0 makes the attributes consistent across all 40 components. Most changes are renames. A few change the unit, so the number changes too. The table below lists every change and how to convert it. Upgrading from 1.0 or 1.1? Read the first section: since 1.2.0, every CSS custom property starts with `--mc-`. ### CSS custom properties start with --mc- Since version 1.2.0, every CSS custom property starts with `--mc-`. The names without the prefix no longer work. Add `mc-` after the two dashes and keep the rest of the name: ```css motion-icon { --mc-icon-color: #2563eb; /* was --icon-color */ } motion-chart { --mc-chart-1: #e11d48; /* was --chart-1 */ } ``` | Component | Before | Now | | --- | --- | --- | | `motion-chart``motion-pie` | `--chart-height``--chart-surface``--chart-1``--chart-2``--chart-3``--chart-4``--chart-5``--chart-6``--chart-7``--chart-8` | `--mc-chart-height``--mc-chart-surface``--mc-chart-1``--mc-chart-2``--mc-chart-3``--mc-chart-4``--mc-chart-5``--mc-chart-6``--mc-chart-7``--mc-chart-8` | | `motion-sparkline` | `--sparkline-width``--sparkline-height``--sparkline-color``--sparkline-accent` | `--mc-sparkline-width``--mc-sparkline-height``--mc-sparkline-color``--mc-sparkline-accent` | | `motion-code` | `--code-keyword``--code-string``--code-tag``--code-attr``--code-num``--code-comment` | `--mc-code-keyword``--mc-code-string``--mc-code-tag``--mc-code-attr``--mc-code-num``--mc-code-comment` | | `motion-countdown` | `--countdown-size``--countdown-color``--countdown-gap``--countdown-label-size``--countdown-label-color` | `--mc-countdown-size``--mc-countdown-color``--mc-countdown-gap``--mc-countdown-label-size``--mc-countdown-label-color` | | `motion-dialog` | `--dialog-bg``--dialog-color``--dialog-radius``--dialog-padding``--dialog-max-width``--dialog-max-height``--dialog-shadow``--dialog-backdrop-color``--dialog-backdrop-blur` | `--mc-dialog-bg``--mc-dialog-color``--mc-dialog-radius``--mc-dialog-padding``--mc-dialog-max-width``--mc-dialog-max-height``--mc-dialog-shadow``--mc-dialog-backdrop-color``--mc-dialog-backdrop-blur` | | `motion-icon``motion-icon-state` | `--icon-size``--icon-color``--icon-fill``--icon-accent` | `--mc-icon-size``--mc-icon-color``--mc-icon-fill``--mc-icon-accent` | | `motion-progress` | `--progress-color` | `--mc-progress-color` | | `motion-spotlight` | `--spotlight-color` | `--mc-spotlight-color` | | `motion-theme-icon``motion-theme-toggle` | `--theme-icon-size``--theme-toggle-surface``--theme-toggle-border``--theme-toggle-accent``--theme-toggle-menu-bg``--theme-toggle-radius` | `--mc-theme-icon-size``--mc-theme-toggle-surface``--mc-theme-toggle-border``--mc-theme-toggle-accent``--mc-theme-toggle-menu-bg``--mc-theme-toggle-radius` | | `motion-scene (set for its children)` | `--progress` | `--mc-progress` | | `Theme colors``read by motion-code``motion-code-inline``motion-dialog``motion-image-compare``motion-progress``motion-ring and motion-slider` | `--color-accent``--color-accent-dim``--color-border``--color-muted``--color-surface``--color-surface-2``--color-text` | `--mc-color-accent``--mc-color-accent-dim``--mc-color-border``--mc-color-muted``--mc-color-surface``--mc-color-surface-2``--mc-color-text` | This search lists the old names in your code. It also finds your own variables with the same names, so review each match: ```terminal grep -rnE -- '--(color-|icon-|chart-|sparkline-|code-|countdown-|dialog-|progress|spotlight-|theme-)' src ``` ### Install ```terminal npm install motion-components@^1.0.0 ``` ### The rules in 1.0 | Rule | What it means for you | | --- | --- | | Time is in seconds | `duration`, `delay`, `interval` and `hold` are always seconds. Values that were milliseconds must be divided by 1000. | | `interval` | Seconds between steps: characters, words or bursts. | | `speed` | Only a rate per second: pixels per second (`motion-ticker`, `loop-speed`) or characters per second (`typing-speed`). | | Springs | Every spring is tuned with `duration` and `bounce`. | | Boolean attributes | Every boolean accepts `"false"`. Flags that are on by default, like `once`, `pin` or `labels`, can now be turned off in HTML: `once="false"`. | | Triggers | `'view'` means when it scrolls into view and `'mount'` means once on load. `'hover'`, `'click'`, `'loop'` and `'scroll'` keep their meaning. | | Events | Events start with `motion-`. The theme toggle keeps `colorschemechange` and `permanentcolorschemechange` for compatibility with `dark-mode-toggle`. | | CSS custom properties | Start with `--mc-` and are named after the component: `--mc-dialog-*`, `--mc-countdown-*`, `--mc-code-*`, `--mc-progress-color`, `--mc-spotlight-color`. | ### Every change | Component | 0.x | 1.0 | How to convert | | --- | --- | --- | --- | | `motion-arc` | `speed` | `duration` | Same number. Seconds per full rotation, `0` means no rotation. | | `motion-blur` | `blur` | `intensity` | Same number, in pixels. | | `motion-blur-in` | `blur` | `intensity` | Same number, in pixels. | | `motion-circle` | `speed` | `duration` | Same number. Seconds per full rotation. | | `motion-code` | `type` | `typing` | Boolean, same meaning. | | `motion-code` | `type-speed` | `typing-speed` | Same number, characters per second. | | `motion-code` | `type-delay` | `typing-delay` | Divide by 1000. Now in seconds. | | `motion-code` | `type-loop-delay` | `typing-hold` | Divide by 1000. Now in seconds. | | `motion-code` | `no-loop` | `typing-loop="false"` | Looping is on by default. Set `typing-loop="false"` to type once. | | `motion-curve` | `speed` | `wave-duration` | Use `1 / speed`. Seconds per wave cycle, so `speed="0.4"` becomes `wave-duration="2.5"`. | | `motion-font` | `trigger="auto"` | `trigger="view"` | Same behavior: animates when it scrolls into view. | | `motion-glitch` | `interval` | `interval` | Divide by 1000. Now in seconds, so `2000` becomes `2`. | | `motion-glitch` | `trigger="auto"` | `trigger="mount"` | Same behavior: glitches once on load. | | `motion-gravity` | `stagger` | `interval` | Same number, seconds between characters. | | `motion-gravity` | `fixed spring` | `duration, bounce` | `duration` now takes effect. `bounce` (default `0.45`) tunes the landing. | | `motion-liquid` | `speed` | `duration` | Use `9 / speed`. Seconds per distortion cycle, so `speed="2"` becomes `duration="4.5"`. | | `motion-parallax` | `speed` | `depth` | Same number. | | `motion-perspective` | `animate` | `loop` | Boolean, same meaning. `loop` is the name; `oscillate` still works as an alias. | | `motion-perspective` | `speed` | `duration` | Use `1 / speed`. Seconds per cycle, so `speed="1.5"` becomes `duration="0.667"`. | | `motion-progress` | `color` | `--mc-progress-color` | Now a CSS custom property: `style="--mc-progress-color: #60a5fa"`. | | `motion-scramble` | `speed` | `interval` | Divide by 1000. Seconds between glyph swaps. | | `motion-scramble` | `delay` | `delay` | Divide by 1000. Now in seconds. | | `motion-scramble` | `hover` | `trigger="hover"` | `trigger` is `'view'` (default) or `'hover'`. | | `motion-slider` | `slidechange event` | `motion-change event` | Same `detail: { index }`. Now also composed. | | `motion-spotlight` | `color` | `--mc-spotlight-color` | Now a CSS custom property: `style="--mc-spotlight-color: rgba(96,165,250,0.25)"`. | | `motion-spotlight` | `smoothing, fade-duration` | `duration, bounce` | The follow is now a spring. `duration` sets both the follow and the fade. | | `motion-stretch` | `stiffness, damping` | `duration, bounce` | `bounce` is `1 - damping / (2 × √stiffness)`, and `duration` is how long the old spring took to settle. Since 1.2.0 the defaults are `duration="0.77" bounce="0.55"`, the same spring as the old defaults `320` and `16`. `stiffness="400" damping="10"` is `duration="1.11" bounce="0.75"`. | | `motion-swap` | `stagger-duration` | `interval` | Same number, seconds between characters. | | `motion-swap` | `transition (property)` | `duration, bounce` | The old default `{ type: 'spring', duration: 0.7 }` is `duration="0.7" bounce="0.3"`. | | `motion-swap` | `trigger="reveal"` | `trigger="view"` | Same behavior: swaps once when scrolled into view. | | `motion-theme-toggle` | `transition="wipe"` | `wipe` | The page wipe is now off by default. Add the boolean `wipe` to turn it on. `transition="none"` can be removed. | | `motion-typewriter` | `speed` | `interval` | Divide by 1000. Seconds between characters. | | `motion-typewriter` | `delay` | `delay` | Divide by 1000. Now in seconds. | | `motion-typewriter` | `pause` | `hold` | Divide by 1000. Seconds to hold the line before erasing. | | `motion-words` | `interval` | `interval` | Divide by 1000. Now in seconds, so `2000` becomes `2`. | ### Code token colors `motion-code` token colors move from `--cw-*` to `--mc-code-*`. Replace the prefix and keep the rest of the name: ```css motion-code { --mc-code-keyword: #c678dd; /* was --cw-keyword */ --mc-code-string: #98c379; /* was --cw-string */ } ``` ### New in 1.0 - `motion-press` and `motion-magnetic` have a `bounce` attribute. `motion-tilt` has `duration` and `bounce`. - `motion-theme-toggle` has `icon-only` for the `toggle` appearance. - Registering a component twice no longer throws. Two bundles can include the same component. - Importing the library during server-side rendering no longer throws, so it works in Next.js, Astro and similar frameworks. - `once="false"` replays the animation every time the element scrolls into view. - Text components escape their text. Markup inside the text is shown as text and never runs as HTML. - Screen readers read the whole text once in `motion-arc`, `motion-circle`, `motion-curve`, `motion-gravity` and `motion-perspective`, instead of letter by letter. - With reduced motion, `motion-split` and `motion-headline` show their text at once. ### Find old attributes in your code This search lists most of the attributes that changed. Review each match with the table above. ```terminal grep -rnE 'speed=|stiffness=|damping=|stagger-duration=|type-speed=|type-delay=|type-loop-delay=|no-loop|smoothing=|fade-duration=|slidechange|--cw-|trigger="(auto|reveal)"' src ``` ## Using with AI > Give AI assistants and coding agents the current motion-components docs: llms.txt, Markdown pages, a rules file for your project and editor completions. Source: https://www.motion-components.dev/docs/ai/ AI assistants write better motion-components code when they read the current docs instead of guessing from what they learned in training. This site publishes its docs in formats they can read, and the package includes data that editors use for completions. ### Give your assistant the docs Three kinds of file cover every component. Pick the one that fits how your tool works. | File | What it has | | --- | --- | | [llms.txt](https://www.motion-components.dev/llms.txt) | A list of every component, docs page and recipe, with a link and one line each. Use it when your tool can follow links, like a coding agent that can browse the web. | | [llms-full.txt](https://www.motion-components.dev/llms-full.txt) | Every component's attributes, events, slots and CSS custom properties in one file. Use it when you want to give a chat one link or one file. | | `.md` pages | Any docs page as Markdown: add `.md` to the address, as in [/docs/text/motion-marker.md](https://www.motion-components.dev/docs/text/motion-marker.md). Use it when you work on one component and want a short context. | Each docs page also links its Markdown version in its ``, so tools that look for one find it. To start a chat, point the assistant at the file and then describe the effect: ```txt Read https://www.motion-components.dev/llms-full.txt first. Then use motion-components to fade in the feature cards on my landing page one after another as they scroll into view. ``` ### Add rules to your project Coding agents read a rules file in your project before each task. Paste these rules into the file your tool reads, such as `AGENTS.md`, `CLAUDE.md`, a rule in `.cursor/rules/` or `.github/copilot-instructions.md`. ```md ### Animation Use motion-components (https://www.motion-components.dev) for motion. - Use a motion-components tag for entrances, hover and press feedback, text effects, icons, scroll effects and charts. Do not write CSS keyframes or animation code for these. - Wrap existing markup instead of rebuilding it: `
    ...
    ` - Combine effects by nesting tags, for example `` cards inside ``. Do not look for one option that does both. - Import one subpath per tag: `import 'motion-components/motion-reveal'` - Import `motion-components/preload.css` once, so content does not flash before the tags load. Other setups: https://www.motion-components.dev/docs/#flash - Time is in seconds: `duration="0.6"`, `delay="0.2"`. Springs are tuned with `duration` and `bounce`. - Boolean attributes are on when present. `once="false"` turns one off. - CSS custom properties start with `--mc-`, for example `--mc-progress-color`. - The components handle `prefers-reduced-motion`. Do not add your own check. - Before using an attribute, check its name in https://www.motion-components.dev/llms-full.txt because names changed in past releases and names from memory can be wrong. ``` ### When the assistant uses old names Models trained on older versions may write attributes that no longer exist, such as `speed` on `motion-scramble`, times in milliseconds, or `--chart-1` instead of `--mc-chart-1`. Give the assistant `llms-full.txt`, or point it to the [migration guide](https://www.motion-components.dev/docs/migration/), which lists every rename and how to convert it. ### Editor completions The package includes data files that give VS Code completions and hover help for every tag and attribute in HTML files, and for every CSS custom property in CSS files. The hover help has the same guidance as these docs: when to use a component, when not to, and accessibility notes. It is a quick way to check what an assistant wrote. Add the files to `.vscode/settings.json`: ```json { "html.customData": [ "./node_modules/motion-components/dist/vscode.html-custom-data.json" ], "css.customData": [ "./node_modules/motion-components/dist/vscode.css-custom-data.json" ] } ``` JetBrains IDEs such as WebStorm need no setup: they read the package's `web-types.json` on their own. Other tools can read `node_modules/motion-components/dist/custom-elements.json`, a Custom Elements Manifest with the same information. ## Chart Recipes > Copy-paste chart recipes: a stat tile, a period switcher, a chart from a form, data from JSON, a budget donut, color palettes and a live line chart. Source: https://www.motion-components.dev/docs/recipes/charts/ Everyday data patterns built with [`motion-chart`](https://www.motion-components.dev/docs/charts/motion-chart/), [`motion-pie`](https://www.motion-components.dev/docs/charts/motion-pie/) and [`motion-sparkline`](https://www.motion-components.dev/docs/charts/motion-sparkline/). Each snippet is complete: copy it into a page that loads the library. ```html ``` ### Stat tile A number that counts up above its trend. `motion-counter` animates the value, `motion-sparkline` draws the trend. ```html
    Revenue this month
    ``` ### Week, month and year switcher Buttons swap the data and the color, and the bars spring to the new values while the axis follows. Click quickly between periods: the bars turn around from wherever they are. ```html
    ``` ### Chart from a form Type a number and the matching bar springs to it. The inputs are the data, so the chart never needs a separate update call. ```html
    ``` ### Load data from a JSON file Fetch the data and set `data`. The chart waits with empty axes, then animates in once the data arrives. This demo loads `/data/sales.json` from this site. ```html ``` `sales.json` has the same shape as `data`: `{ "labels": [...], "series": [{ "name": "Online", "values": [...] }] }`. ### Budget donut by month A select changes the month, and the slices spring to their new sizes. The center shows the total, or the slice you hover. ```html
    ``` ### Color palettes The colors are CSS custom properties, `--mc-chart-1` to `--mc-chart-8`. Set them on a wrapper, and every chart inside takes them. These buttons switch palettes for a bar chart and a donut at once. ```html
    ``` Without JavaScript, set the palette in CSS: `.my-charts { --mc-chart-1: #0369a1; --mc-chart-2: #0ea5e9; }`. The default palette is chosen so the colors stay apart for readers with color blindness. When you pick your own, make the colors differ in lightness too, as the Mono palette does. ### Live line chart A new point every 1.5 seconds. The oldest point drops off, and the whole line springs into its new shape. ```html ``` ## Icon Recipes > Copy-paste recipes for animated icons: buttons, a notification bell, a like button, a download button, icon navigation and a feature list. Source: https://www.motion-components.dev/docs/recipes/icons/ Everyday interface patterns built with [`motion-icon`](https://www.motion-components.dev/docs/icons/motion-icon/) and [`motion-icon-state`](https://www.motion-components.dev/docs/icons/motion-icon-state/). Each snippet is complete: copy it into a page that loads the library. ```html ``` ### Button with an animated icon Put a `motion-icon` inside a button. It animates when the pointer enters any part of the button, not only the icon. ```html ``` ### Notification bell The bell wiggles when a notification arrives. Call `replay()` from your code, and update the badge and the button name for screen readers. ```html ``` ### Like button with a count The heart fills on a spring and the count goes up or down. The button keeps `aria-pressed` in sync, so screen readers know the state. ```html ``` ### Download button: arrow, spinner, check Two components work together. `motion-icon` shows the arrow and bounces on hover. On click, `motion-icon-state` takes over with a spinner that turns into a check. ```html ``` The `[hidden]` rule is needed because a component's own `display` style is stronger than the browser's default for `hidden`. ### Navigation with icons Each link has an icon with its own animation. Inside a link, the icon animates when the pointer enters the whole link. ```html ``` ### Feature list that draws in on scroll Each icon draws itself in when the list scrolls into view. A growing `delay` makes them follow each other. ```html
    • Light Each icon file loads once and is shared.
    • Accessible Reduced motion is respected.
    • Simple One tag per icon.
    ``` ### More recipes The [motion-icon-state page](https://www.motion-components.dev/docs/icons/motion-icon-state/#recipes) has five more: a menu button, a copy button, a save button with a spinner, a show password button and an accordion. ## Magnetic CTA > A call-to-action button that attracts the cursor with magnetic pull, lifts on hover, and presses on click. Three primitives composed into one premium interaction. Source: https://www.motion-components.dev/docs/recipes/magnetic-cta/ A call-to-action button that attracts the cursor, lifts on hover, and presses on click. Three primitives composed into one premium interaction. ### Complete code ```html ``` ### How it works Each component handles one layer of the interaction. They compose without conflicting because each animates its own transform. #### Magnetic pull ```html ``` `strength` controls how far the content follows the cursor. At `0.4` the button shifts 40% of the cursor offset. Lower values feel subtle; higher values feel playful. #### Hover lift ```html ``` The hover lift gives visual confirmation that the element is interactive. The spring makes the transition feel physical rather than mechanical. #### Press feedback ```html ``` The press scale provides tactile feedback. The button compresses when clicked and springs back on release. ### Tuning Adjust each layer independently. The magnetic strength, hover scale, and press depth are all separate knobs. #### Subtle version ```html ``` #### Playful version ```html ``` ### Accessibility All three components respect `prefers-reduced-motion`. Users who request reduced motion get the button at rest with no magnetic pull, hover transform, or press feedback. ## Scroll Progress > A fixed progress bar that tracks scroll position with spring physics, combined with parallax sections and reveal entrances. Source: https://www.motion-components.dev/docs/recipes/scroll-progress/ A fixed progress bar that tracks scroll position with spring physics, combined with parallax sections and reveal entrances. ### Complete code ```html

    Features

    Content that fades in on scroll.

    ...

    Pricing

    More content that reveals on scroll.

    ``` ### How it works #### Progress bar ```html ``` The progress bar is a fixed element that fills as you scroll. It uses a spring-eased `scaleX` transform, so the fill feels physical rather than linear. `position` places it at `"top"` or `"bottom"`. #### Parallax ```html ... ``` `depth` controls how far the content drifts relative to the scroll. At `0.3` it moves at 30% of the scroll speed, which reads as depth. Use `axis="x"` for horizontal parallax. #### Reveal entrances ```html
    Content that fades in.
    ``` Reveals fire when the element enters the viewport via IntersectionObserver. They work at any scroll position, not just the initial load. ### Tuning #### Heavier parallax ```html ... ``` Higher speed values create a more pronounced depth effect. At `0.6` the content noticeably lags behind the scroll. #### Custom progress color ```html ``` The `color` attribute accepts any CSS color value, including gradients. ### Accessibility The progress bar is decorative and hidden from screen readers via `aria-hidden`. Parallax and reveal components respect `prefers-reduced-motion`: parallax becomes static and reveals fire instantly. ## Staggered Grid > A grid of cards that fade in sequentially when they enter the viewport. motion-stagger handles the timing, motion-reveal handles the entrance. Source: https://www.motion-components.dev/docs/recipes/staggered-grid/ A grid of cards that fade in sequentially when they enter the viewport. The stagger handles timing, the reveal handles the entrance. ### Complete code ```html

    Design

    Pixel-perfect interfaces that scale.

    Develop

    Clean, maintainable code.

    Deploy

    Ship with confidence.

    ``` ### How it works Two components with one responsibility each: stagger controls when each child appears, reveal controls how it appears. #### Stagger timing ```html ``` `interval` is the delay between each child starting its entrance. `from` controls where the cascade starts: `"first"` (default), `"last"`, or `"center"`. #### Reveal entrance ```html ``` Setting `y="0"` means no vertical slide, just a fade. Use `y="24"` for a lift effect. The stagger wraps the reveal, so each child fades in sequence. ### Variations #### With slide-up ```html
    ...
    ``` Use `from="center"` to fan outward from the middle. Works well for feature grids. #### With viewport trigger By default, the stagger fires when the first child enters the viewport. Add `once` to prevent re-triggering on scroll back. ```html ... ... ``` ### Accessibility Both components respect `prefers-reduced-motion`. Users get the final state immediately: all cards visible, no stagger, no fade animation. ## Tilt Card > A card that tilts in 3D following the cursor, with a spotlight overlay and hover lift. Three primitives composed for a premium card interaction. Source: https://www.motion-components.dev/docs/recipes/tilt-card/ A card that tilts in 3D following the cursor, with a spotlight overlay and hover lift. The kind of interaction you see on premium landing pages. ### Complete code ```html
    ✦

    Analytics

    Real-time insights into your product usage.

    ``` ### How it works Two interaction primitives layered on a static card. The tilt handles 3D rotation, the spotlight adds a tracking light effect. #### 3D tilt ```html ``` `max` is the maximum rotation in degrees. At `15` the card tilts up to 15 degrees toward the cursor. The `scale` lifts it slightly on hover for a floating feel. #### Spotlight overlay ```html ``` The spotlight renders a radial gradient that follows the cursor on a spring. It fades in on hover and out on leave. The `duration` attribute (default 0.35) controls how much the light lags behind the cursor. ### Tuning #### Subtle ```html
    ...
    ``` #### Dramatic ```html
    ...
    ``` Add `gloss` to motion-tilt for a moving specular highlight across the card surface. ### Card styles The card itself is just CSS. The motion components wrap it without modifying its structure. ```css .card { background: var(--mc-color-surface); border: 1px solid var(--mc-color-border); border-radius: 16px; padding: 2rem; width: 320px; min-height: 200px; transform-style: preserve-3d; } ``` ### Accessibility Both components respect `prefers-reduced-motion`. The tilt settles to a flat position and the spotlight stays hidden. The card remains fully readable without any motion. ## Typewriter Hero > A hero section with a character-by-character typewriter headline and a fading subcopy. The typewriter adds presence, the reveal adds depth. Source: https://www.motion-components.dev/docs/recipes/typewriter-hero/ A hero section with a character-by-character typewriter headline and a fading subcopy. The typewriter adds presence, the reveal adds depth. ### Complete code ```html

    Build interactions that ship.

    Physics-based web components for any framework. No easing curves to tune.

    ``` ### How it works #### Typewriter headline ```html

    Build interactions that ship.

    ``` The typewriter reveals the headline one character at a time. `interval` is the time between characters in seconds. `delay` adds a pause, in seconds, before typing starts. The `cursor` attribute adds a blinking caret. #### Reveal subcopy ```html

    Physics-based web components.

    ``` The subcopy fades in with a slight lift. Without an explicit `delay`, it starts immediately when the element enters the viewport. The different durations create a natural choreography: the typewriter takes longer, the reveal resolves faster. ### Variations #### Looping ```html

    Build. Ship. Iterate.

    ``` Add `loop` to cycle through: type, pause, erase, retype. The `delay` is the pause between cycles in milliseconds. #### Word-by-word For a calmer, more editorial feel, use `motion-headline` instead. It reveals word by word with a mask slide rather than character by character. ```html

    The future of motion

    ``` ### Accessibility The typewriter respects `prefers-reduced-motion`: the full text appears instantly with no character-by-character animation, and the caret does not blink. Screen readers always read the full text, even while it types. Put the typewriter inside the `

    `, not around it: it replaces its own content, so a heading inside it would be lost.