# Documentation

> Explore the full Motion Components API. Animate, reveal, respond, and compose with spring-based web components. Framework-agnostic and drop-in.

Source: https://www.motion-components.dev/docs/

Motion Components is a library of physics-based web components. Compose primitives to build natural, interruptible interactions.

## Installation

```terminal
npm install motion-components
```

Import once. All custom elements register themselves as a side effect:

```js
import 'motion-components'
```

Import a specific class if you need the TypeScript type or want to call methods:

```ts
import { MotionReveal } from 'motion-components'

const el = document.querySelector('motion-reveal') as MotionReveal
el.replay()
```

No bundler? Use the CDN directly:

```html
<script type="module">
  import 'https://cdn.jsdelivr.net/npm/motion-components@1/+esm'
</script>
```

## Use only what you need

Every component ships as its own subpath export. Import a single tag and the bundler will only pull in that file. The rest of the library never reaches your bundle, so you pay only for what you use.

Just one component:

```js
// registers <motion-countdown> only
import 'motion-components/motion-countdown'
```

A handful of components:

```js
import 'motion-components/motion-hover'
import 'motion-components/motion-reveal'
import 'motion-components/motion-stagger'
```

Import the class for typing or method access:

```ts
import { MotionCountdown } from 'motion-components/motion-countdown'

const el = document.querySelector<MotionCountdown>('motion-countdown')!
console.log(el.to)
```

CDN, single component:

```html
<script type="module">
  import 'https://cdn.jsdelivr.net/npm/motion-components@1/dist/motion-countdown.js/+esm'
</script>
```

The full bundle (`import 'motion-components'`) is convenient and brotli-compresses to ~40 KB for all components, Lit and Motion included. Per-component imports drop straight to the cost of just the tags you use.

## Frameworks

Most frameworks work without configuration. A few need a small one-time setup so their compiler doesn't treat unknown tags as errors.

#### Vanilla / Astro / Svelte

Import preload CSS and the library. Works without configuration.

```js
import { preloadCSS } from 'motion-components/preload'
import 'motion-components'

const style = document.createElement('style')
style.textContent = preloadCSS
document.head.appendChild(style)
```

#### React

React 19 has full custom element support. Add preload and import in your entry file.

```tsx
import { preloadCSS } from 'motion-components/preload'
import 'motion-components'

const style = document.createElement('style')
style.textContent = preloadCSS
document.head.appendChild(style)

export function App() {
  return (
    <motion-hover scale="1.08">
      <button>Hover me</button>
    </motion-hover>
  )
}
```

#### 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: `<motion-hover><button>Hover</button></motion-hover>`
})
export class AppComponent {}
```

## Flash prevention

Web components are undefined when the HTML first renders. The browser treats `<motion-flip-card>` 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 `<head>`

The only 100% reliable fix: inject a `<style>` tag in the document `<head>` that hides every component while it's `:not(:defined)`. The CSS must be available _synchronously before the first paint_. A render-blocking CSS file (`<link>`) arrives after the HTML, leaving a timing gap. Inline CSS has zero gap, zero flash risk.

```ts
import { preloadCSS } from 'motion-components/preload'

const style = document.createElement('style')
style.textContent = preloadCSS
document.head.appendChild(style)
```

### Framework examples

Each example below inlines `preloadCSS` as a `<style>` tag in the document `<head>`. The import uses the `motion-components/preload` subpath. This file has zero side effects and evaluates cleanly in Node.js during SSR.

#### React / Vite

Inline CSS in `main.tsx`. Works before first paint.

```tsx
import { preloadCSS } from 'motion-components/preload'
import 'motion-components'

// Inject into <head> (framework-agnostic):
const style = document.createElement('style')
style.textContent = preloadCSS
document.head.appendChild(style)
```

#### Next.js

Inline in `app/layout.tsx` (App Router) or `pages/_app.tsx`.

```tsx
import { preloadCSS } from 'motion-components/preload'
import 'motion-components'

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <style>{preloadCSS}</style>
      </head>
      <body>{children}</body>
    </html>
  )
}
```

#### Astro

Import in frontmatter, inline with `is:inline`.

```astro
---
import { preloadCSS } from 'motion-components/preload'
---
<html>
  <head>
    <style is:inline set:html={preloadCSS}></style>
  </head>
  <body><slot /></body>
</html>
```

#### Nuxt / Vue

Use `useHead` in `app.vue` or your root layout.

```vue
<script setup>
import { preloadCSS } from 'motion-components/preload'
useHead({ style: [{ children: preloadCSS }] })
</script>
```

#### SvelteKit

Inline in `+layout.svelte` via `<svelte:head>`.

```svelte
<script>
  import { preloadCSS } from 'motion-components/preload'
  import 'motion-components'
</script>

<svelte:head>
  <style>{preloadCSS}</style>
</svelte:head>
```

#### 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 `<link>`. 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 `<head>`:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/motion-components/dist/preload.css">
```
