# Atelier UI - Full Documentation
Source: https://atelier-ui.com/docs
Generated: 2026-10-01T11:32:42.374Z
---
============================================================
## Orbit Gallery
> A WebGL gallery that places images on rotating rings. Scroll to spin them, click an image to zoom in.
Source file: components/background/orbit-gallery.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
const ITEMS = Array.from({ length: 20 }, (_, i) => ({
src: `https://picsum.photos/seed/atelier-${i + 1}/1200/800`,
alt: "",
}))
```
### Overlays
`onActiveChange` gives you the index of the opened image, or `null` when it closes. Use it to render your own overlay as regular HTML:
```tsx
const [active, setActive] = useState(null)
<>
{active !== null && (
Image {active + 1}
)}
>
```
### Post-processing
The gallery renders through a [WebGL Scene](/docs/foundation/primitive/webgl-scene) layered above other content. Set `mode="texture"` to render it into post-processing instead, so effects like `FluidDistortion` apply to the rings:
```tsx
```
***
## API
| Name | Type | Default | Description |
| ----------------- | --------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `items` | `OrbitGalleryItem[]` | — | The images placed around the rings, as `{ src, alt }`. They repeat to fill each ring. Required. |
| `radius` | `number` | `2.8` | Radius of the innermost ring, in world units. |
| `rings` | `number` | `3` | Number of rings around the center. |
| `ringGap` | `number` | `1.6` | Distance between two neighboring rings, in world units. |
| `tileHeight` | `number` | `0.7` | Height of each image tile. Width follows from the image aspect ratio. |
| `cornerRadius` | `number` | `0.08` | Corner radius of each tile, in world units. `0` keeps square corners. |
| `spinSpeed` | `number` | `1` | Revolutions per minute of the innermost ring. Negative values spin the other way. |
| `spinStagger` | `number` | `0.2` | Speed ratio between one ring and the next. Below `1` outer rings spin slower, above `1` faster. |
| `wheel` | `boolean` | `true` | Whether the scroll wheel accelerates the spin. When `false` the wheel does nothing. |
| `wheelMultiplier` | `number` | `3` | How much the scroll wheel accelerates the spin. `0` disables it. |
| `revealDuration` | `number` | `2` | Duration of the reveal animation, in seconds. |
| `focusDuration` | `number` | `1` | Duration of the click-to-focus zoom and the ring fade, in seconds. |
| `onActiveChange` | `(index: number \| null) => void` | — | Fires with the opened image index when one opens, or `null` when none is. Use it to render your own overlay from your own data. |
| `className` | `string` | — | Classes for the interaction surface the gallery renders into. Sizes the gallery, any container size works. |
It also forwards [WebGL Scene](/docs/foundation/primitive/webgl-scene) props: `mode`, `priority`, `zIndex`, and `transparent`.
***
============================================================
## Sphere Gallery
> A WebGL gallery that maps images onto a sphere. Drag to look around, click an image to zoom in.
Source file: components/background/sphere-gallery.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
const ITEMS = Array.from({ length: 20 }, (_, i) => ({
src: `https://picsum.photos/seed/atelier-${i + 1}/1200/800`,
alt: "",
}))
```
### Overlays
`onActiveChange` gives you the index of the opened image, or `null` when it closes. Use it to render your own overlay as regular HTML:
```tsx
const [active, setActive] = useState(null)
<>
{active !== null && (
{ITEMS[active].title}
)}
>
```
### Post-processing
The gallery renders through a [WebGL Scene](/docs/foundation/primitive/webgl-scene) layered above other content. Set `mode="texture"` to render it into post-processing instead, so effects like `FluidDistortion` apply to the sphere:
```tsx
```
***
## API
| Name | Type | Default | Description |
| ---------------- | --------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `items` | `SphereGalleryItem[]` | — | The images to map onto the sphere, as `{ src, alt }`. They tile to fill the grid. Required. |
| `reveal` | `boolean` | `true` | When `false`, holds the reveal animation; flip to `true` to play it (e.g. after an intro). |
| `revealDuration` | `number` | `2` | Duration of the reveal animation, in seconds. |
| `focusDuration` | `number` | `1` | Duration of the click-to-focus zoom, in seconds. |
| `focusScale` | `number` | `1.7` | How much a tile scales up when focused. |
| `mouseParallax` | `number` | `0.2` | Strength of the mouse-driven parallax tilt. `0` disables it. |
| `onActiveChange` | `(index: number \| null) => void` | — | Fires with the focused image index when one opens, or `null` when none is. Use it to render your own overlay from your own data. |
| `rows` | `number` | `7` | Horizontal tile rings stacked from bottom to top. Tile height follows from this. |
| `columns` | `number` | `12` | Sets the width of every tile. Higher values make tiles narrower and pack more around each ring. |
| `latitudeRange` | `number` | `85` | Vertical spread of the rings in degrees. |
| `gap` | `number` | `0.01` | Seam between neighboring tiles, in world units. `0` makes them touch. |
| `padding` | `number` | `0.03` | Even border between the tile edge and the contained image, in world units. |
| `cornerRadius` | `number` | `0.02` | Tile corner rounding in world units. `0` is square. |
| `tileColor` | `string \| null` | `"#F8F8F8"` | Background color of the tile behind each image, shown in the padding and around images. Set to `null` to hide it so only the image shows. |
| `sphereColor` | `string` | `"#ffffff"` | Color of the sphere behind the tiles. |
| `lensBlur` | `number` | `0.4` | Strength of the edge lens blur. `0` disables it; higher values blur more toward the edges. |
| `fov` | `number` | `70` | Camera field of view in degrees. Narrows on click to zoom into an image. |
| `className` | `string` | — | Classes for the interaction surface the gallery renders into. Sizes the gallery, any container size works. |
It also forwards [WebGL Scene](/docs/foundation/primitive/webgl-scene) props: `mode`, `priority`, `zIndex`, and `transparent`.
***
============================================================
## Spiral Gallery
> An infinite carousel of images staggered along a spiraling tube that scrolls indefinitely.
Source file: components/background/spiral-gallery.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works).
Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
const ITEMS = Array.from({ length: 20 }, (_, i) => ({
src: `https://picsum.photos/seed/atelier-${i + 1}/1200/800`,
alt: "",
}))
```
You can pass fewer images than `tileCount`.
The list repeats until every tile on the tube has one.
### Scroll input
By default the carousel moves with the wheel.
If you want it to use the real page scroll, set `input="scroll"`:
```tsx
```
### Post-processing
The gallery renders through a [WebGL Scene](/docs/foundation/primitive/webgl-scene) layered above other content.
Set `mode="texture"` to render it into post-processing instead, so effects like `FluidDistortion` apply to the spiral:
```tsx
```
***
## API
| Name | Type | Default | Description |
| ----------------- | ------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `items` | `SpiralGalleryItem[]` | - | The images to show, each one as `{ src, alt }`. They repeat to fill `tileCount`. Required. |
| `radius` | `number` | `5` | Radius of the tube the images travel around, in world units. |
| `tileHeight` | `number` | `2.45` | Height of each image, in world units. |
| `tileAspect` | `number` | `1.5` | Width-to-height ratio of each image. Images are cropped to fit. |
| `tileCount` | `number` | `16` | Number of images on screen at once. Items repeat if there are fewer. |
| `verticalSpacing` | `number` | `0.95` | Vertical distance between one image and the next. |
| `turnAngle` | `number` | `46` | Angle between one image and the next around the tube, in degrees. |
| `tileRotation` | `number` | `1` | How much each image rotates on its own vertical axis as it orbits. `1` keeps images flat against the tube, `0` faces them all the same way. |
| `cornerRadius` | `number` | `0.05` | Corner rounding of each image, in world units. |
| `curve` | `number` | `0.1` | How much each image bends inward across its width. |
| `autoScroll` | `number` | `0.1` | Speed the carousel scrolls on its own, with no input. |
| `easing` | `number` | `0.1` | How long the scroll takes to settle, in seconds. Higher is slower. |
| `input` | `"wheel" \| "scroll" \| "none"` | `"wheel"` | Where scrolling comes from. `"wheel"` captures the wheel over the gallery, `"scroll"` follows the page scroll, `"none"` leaves only dragging. |
| `inputSpeed` | `number` | `1.1` | Multiplier on wheel and drag distance. Higher moves further per gesture. |
| `drag` | `boolean` | `true` | Allow dragging the carousel with the pointer. |
| `scrollSpread` | `number` | `0.5` | Extra angle added between images while scrolling. `0` keeps the spacing fixed. |
| `scrollGrowth` | `number` | `0.65` | How much the radius and vertical spacing grow while scrolling. |
| `lensBlur` | `number` | `0.4` | Strength of the lens pass: edge blur, RGB split and the fisheye bulge that swells while an image opens. `0` disables it; higher values blur more toward the edges. |
| `autoScale` | `boolean` | `true` | Shrink the spiral on narrow viewports so it stays inside the container. |
| `scale` | `number` | `1.3` | Overall size of the spiral, applied on top of `autoScale`. |
| `reveal` | `boolean` | `true` | Play the mount animation, where the images start as a flat band and wrap onto themselves into the tube, turning once around. |
| `revealDuration` | `number` | `2` | How long the mount animation takes, in seconds. |
| `focusDuration` | `number` | `2.1` | How long the click-to-focus unwinding takes, in seconds. Sliding between two open images takes 40% of it. |
| `focusScale` | `number` | `0.7` | Share of the container height the open image covers, from `0` to `1`. Lower it for more room around the row. |
| `className` | `string` | - | Classes for the interaction surface the gallery renders into. Sizes the gallery, any container size works. |
It also forwards [WebGL Scene](/docs/foundation/primitive/webgl-scene) props: `mode`, `priority`, `zIndex`, `transparent`, and `autoReflow`.
***
============================================================
## Edge Bounce
> A wrapper that bounces away from the cursor on hover and springs back into place.
Source file: components/cursor/edge-bounce.mdx
***
## Usage
Wrap the content that should bounce away from the cursor.
```tsx
```
***
## API
| Name | Type | Default | Description |
| ------------- | ----------- | ------- | --------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | Content to render inside the wrapper. Required. |
| `className` | `string` | — | Class names applied to the wrapper. |
| `pause` | `number` | `0` | Delay in seconds before springing back. |
| `outDuration` | `number` | `0.35` | Duration in seconds for the wrapper moving away. |
| `inDuration` | `number` | `1` | Duration in seconds for the spring-back animation. |
| `bounce` | `number` | `0.3` | Bounciness of the spring-back. `0` = no overshoot, `1` = maximum oscillation. |
| `distance` | `number` | `35` | Maximum pixels the wrapper moves from its origin, reached at full pointer speed. |
| `rotation` | `number` | `25` | Maximum rotation in degrees, following the pointer's travel direction. `0` disables it. |
***
============================================================
## Fluid Distortion
> A GPU-accelerated fluid distortion effect that reacts to cursor movement.
Source file: components/cursor/fluid-distortion.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
***
## API
| Name | Type | Default | Description |
| --------------------- | --------- | --------- | ----------------------------------------------------------------------------------------- |
| `fluidColor` | `string` | `#b4a6ff` | Sets the fluid color. Effective only when `rainbow` is set to `false`. |
| `backgroundColor` | `string` | `#070410` | Sets the background color. Effective only when `showBackground` is `true`. |
| `showBackground` | `boolean` | `false` | Toggles the background color's visibility. If `false` it becomes transparent. |
| `blend` | `number` | `5` | Blends fluid into the scene when `showBackground` is true. Valid range: `0.00` to `10.0`. |
| `intensity` | `number` | `7` | Sets the fluid intensity. Valid range: `0` to `10`. |
| `force` | `number` | `1.1` | Multiplies the mouse velocity to increase fluid splatter. Valid range: `0.0` to `20`. |
| `distortion` | `number` | `0.8` | Sets the distortion amount. Valid range: `0.00` to `2.00`. |
| `radius` | `number` | `0.65` | Sets the fluid radius. Valid range: `0.01` to `1.00`. |
| `curl` | `number` | `0.8` | Sets the amount of the curl effect. Valid range: `0.0` to `50`. |
| `swirl` | `number` | `2` | Sets the amount of the swirling effect. Valid range: `0` to `20`. |
| `velocityDissipation` | `number` | `0.98` | Reduces the fluid velocity over time. Valid range: `0.00` to `1.00`. |
| `densityDissipation` | `number` | `0.98` | Reduces the fluid density over time. Valid range: `0.00` to `1.00`. |
| `pressure` | `number` | `0.70` | Controls the reduction of pressure. Valid range: `0.00` to `1.00`. |
| `rainbow` | `boolean` | `false` | Activates color mode based on mouse direction. |
***
============================================================
## Image Trail
> Cursor trail effect that spawns images along its path.
Source file: components/cursor/image-trail.mdx
***
## Usage
Pass the trail items as children. They spawn along the cursor path.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/1200/800",
"https://picsum.photos/seed/atelier-2/1200/800",
"https://picsum.photos/seed/atelier-3/1200/800",
]
{IMAGES.map((src) => (
))}
```
***
## API
| Name | Type | Default | Description |
| --------------- | ----------- | ------- | ----------------------------------------------------- |
| `children` | `ReactNode` | — | Items to cycle through as the cursor moves. Required. |
| `spawnDistance` | `number` | `76` | Distance in pixels before spawning an item. |
| `driftAmount` | `number` | `36` | How far each item drifts from its spawn point in px. |
| `removeDelay` | `number` | `1.0` | Seconds before a spawned item is removed. |
***
============================================================
## Lens Media
> An SEO-friendly WebGL image or video with a cursor-following circular lens and chromatic aberration.
Source file: components/cursor/lens-media.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
***
## API
| Name | Type | Default | Description |
| -------------- | -------------------- | --------- | ------------------------------------------------------------------ |
| `type` | `"image" \| "video"` | `"image"` | Render the lens on a still image or a video. |
| `src` | `string` | — | Media source. |
| `alt` | `string` | — | Image alt text. Required (and only used) when `type` is `"image"`. |
| `size` | `number` | `0.12` | Lens radius. |
| `softness` | `number` | `0.5` | Lens edge falloff. |
| `aberration` | `number` | `0.18` | Color spread width. |
| `dispersion` | `number` | `35` | Color sample count. |
| `refraction` | `number` | `0.4` | Inward pull strength. |
| `smoothing` | `number` | `7` | Cursor damping speed. |
| `segments` | `number` | `1` | Plane geometry subdivisions. |
| `webglEnabled` | `boolean` | `true` | Toggle WebGL effect on/off. |
It also forwards ` ` attributes to [WebGL Image](/docs/foundation/primitive/webgl-image) and `` attributes to [WebGL Video](/docs/foundation/primitive/webgl-video) depending on `type`.
***
============================================================
## Liquid Media
> An SEO-friendly WebGL image or video with a cursor-driven liquid ripple effect.
Source file: components/cursor/liquid-media.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
***
## API
| Name | Type | Default | Description |
| -------------- | -------------------- | --------- | ------------------------------------------------------------------ |
| `type` | `"image" \| "video"` | `"image"` | Render the ripple on a still image or a video. |
| `src` | `string` | — | Media source. |
| `alt` | `string` | — | Image alt text. Required (and only used) when `type` is `"image"`. |
| `rippleMap` | `Texture` | built-in | Custom ripple texture. |
| `intensity` | `number` | `0.2` | Displacement strength. |
| `radius` | `number` | `12` | Ripple size, up to `50`. |
| `expandRate` | `number` | `11` | How fast ripples grow, up to `20`. |
| `decayRate` | `number` | `3` | How fast ripples fade out, between `3` and `10`. |
| `segments` | `number` | `1` | Plane geometry subdivisions. |
| `webglEnabled` | `boolean` | `true` | Toggle WebGL effect on/off. |
It also forwards ` ` attributes to [WebGL Image](/docs/foundation/primitive/webgl-image) and `` attributes to [WebGL Video](/docs/foundation/primitive/webgl-video) depending on `type`.
***
============================================================
## Magnetic Dot Grid
> A 2D canvas-based interactive dot grid with magnetic cursor effect.
Source file: components/cursor/magnetic-dot-grid.mdx
***
## Usage
Render it inside the section it should fill.
```tsx
```
***
## API
| Name | Type | Default | Description |
| ------------------- | ---------- | ------------- | -------------------------------------------------------------------- |
| `dotRadius` | `number` | `0.6` | Radius of each dot in px. |
| `spacing` | `number` | `16` | Distance between dots in px. |
| `strength` | `number` | `20` | Strength of the magnetic force in px. |
| `interactionRadius` | `number` | `500` | Cursor influence range in px. |
| `snapSpeed` | `number` | `9` | How fast dots react to the cursor. |
| `returnSpeed` | `number` | `5` | How slowly dots return to their base position. |
| `floatAmplitude` | `number` | `1.8` | How far dots drift during the idle float animation. |
| `floatSpeed` | `number` | `2` | Speed of the floating animation. |
| `baseColor` | `string` | `"#000000"` | Default dot color. |
| `centerColors` | `string[]` | `["#BCBCBC"]` | Colors near the cursor. Cycles through the array. |
| `cycleSpeed` | `number` | `1.5` | Color cycle speed. |
| `className` | `string` | — | Classes applied to the canvas. Use it to size and position the grid. |
***
============================================================
## Pixel Media
> A WebGL image or video that breaks into pixel blocks under the cursor, then falls back when you stop.
Source file: components/cursor/pixel-media.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
***
## API
| Name | Type | Default | Description |
| ------------------- | -------------------- | --------- | ------------------------------------------------------------------ |
| `type` | `"image" \| "video"` | `"image"` | Render the effect on a still image or a video. |
| `src` | `string` | — | Media source. |
| `alt` | `string` | — | Image alt text. Required (and only used) when `type` is `"image"`. |
| `gridSize` | `number` | `22` | Pixel blocks per axis. |
| `interactionRadius` | `number` | `4` | Cursor push radius. |
| `strength` | `number` | `1.65` | Drag strength. |
| `aberration` | `number` | `0.25` | RGB channel split. |
| `trail` | `number` | `0.93` | Trail persistence. |
| `segments` | `number` | `1` | Plane geometry subdivisions. |
| `webglEnabled` | `boolean` | `true` | Toggle WebGL effect on/off. |
It also forwards ` ` attributes to [WebGL Image](/docs/foundation/primitive/webgl-image) and `` attributes to [WebGL Video](/docs/foundation/primitive/webgl-video) depending on `type`.
***
============================================================
## Pixel Trail
> A 2D canvas-based pixelated cursor trail that can sample colors from images.
Source file: components/cursor/pixel-trail.mdx
***
## Usage
The trail follows the cursor. In sample mode it picks its colors from the images under it.
```tsx
```
Cross-origin images need `crossOrigin` to be sampled.
***
## API
| Name | Type | Default | Description |
| --------------- | --------------------- | ---------------------------- | --------------------------------------------------------------------- |
| `mode` | `"color" \| "sample"` | `"color"` | Solid color trail or sample colors from images. |
| `color` | `string` | `"#000000"` | Trail color. Fallback when sampling misses. |
| `imageSelector` | `string` | `"img"` | CSS selector for images to sample. |
| `lighten` | `number` | `20` | Brightness boost on sampled colors (`0`–`255`). |
| `pixelSize` | `number` | `20` | Size of each pixel square in px. |
| `trailRadius` | `number` | `2` | Brush radius in grid cells. `0` for single pixel. |
| `lifetime` | `number` | `1` | Max pixel lifetime in seconds. |
| `fade` | `number` | `0.5` | Fade-out duration in seconds. `0` to disable. |
| `showGrid` | `boolean` | `false` | Show a grid overlay. |
| `gridColor` | `string` | `"rgba(255, 255, 255, 0.1)"` | Grid line color. |
| `gridThickness` | `number` | `1` | Grid line thickness in px. |
| `className` | `string` | — | Classes applied to the canvas. Use it to size and position the trail. |
***
============================================================
## Curve Media
> An SEO-friendly WebGL image or video with scroll-velocity driven bend and chromatic aberration.
Source file: components/scroll/curve-media.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
Works best with smooth scrolling. You can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
***
## API
| Name | Type | Default | Description |
| -------------- | -------------------- | --------- | ------------------------------------------------------------------------------- |
| `type` | `"image" \| "video"` | `"image"` | Render the bend on a still image or a video. |
| `src` | `string` | — | Media source. |
| `alt` | `string` | — | Image alt text. Required (and only used) when `type` is `"image"`. |
| `amplitude` | `number` | `0.05` | Maximum bend intensity, applied to the smoothed scroll velocity. |
| `aberration` | `number` | `0.03` | RGB channel split along the scroll axis, scaled by velocity. |
| `smoothing` | `number` | `20` | Exponential smoothing for the velocity (higher = snappier, lower = floatier). |
| `segments` | `number` | `32` | Plane subdivision count. Higher = smoother curve, more vertices. |
| `webglEnabled` | `boolean` | `true` | When false, the WebGL plane is skipped and the plain element is rendered as-is. |
It also forwards ` ` attributes to [WebGL Image](/docs/foundation/primitive/webgl-image) and `` attributes to [WebGL Video](/docs/foundation/primitive/webgl-video) depending on `type`.
***
============================================================
## Infinite Gallery
> Zero-dependency infinite horizontal gallery with drag/wheel input, and a 3D flip effect.
Source file: components/scroll/infinite-gallery.mdx
***
## Usage
Pass the items as children. Drag or scroll to move the gallery.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/1200/800",
"https://picsum.photos/seed/atelier-2/1200/800",
]
{IMAGES.map((src) => (
))}
```
***
## API
| Name | Type | Default | Description |
| ---------------- | ----------- | ------- | ---------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | Items to render in the gallery. Repeated automatically to fill the view. Required. |
| `perView` | `number` | `4` | Number of items visible at once. |
| `speed` | `number` | `6` | Speed of the scroll. |
| `inertia` | `number` | `0.6` | Inertia of the scroll. |
| `dragMultiplier` | `number` | `3` | Multiplier applied to drag distance. |
| `gap` | `number` | `5` | Gap between items. |
| `className` | `string` | — | Class for the container element. |
***
============================================================
## Infinite Parallax
> Composable infinite parallax column effect with auto-scroll and repeating content.
Source file: components/scroll/infinite-parallax.mdx
***
## Usage
Stack as many columns as you want. Alternate their direction with `reversed`.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/1200/800",
"https://picsum.photos/seed/atelier-2/1200/800",
"https://picsum.photos/seed/atelier-3/1200/800",
]
{IMAGES.map((src) => (
))}
{IMAGES.map((src) => (
))}
{IMAGES.map((src) => (
))}
```
Works best with smooth scrolling. You can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
***
## API
| Name | Type | Default | Description |
| ----------------- | ----------- | ------- | ---------------------------------------------- |
| `children` | `ReactNode` | — | Items to render in the column. |
| `reversed` | `boolean` | `false` | Reverse the column's scroll direction. |
| `autoScrollSpeed` | `number` | `0.02` | Auto-scroll speed. `0` to disable. |
| `parallaxAmount` | `number` | `2` | Parallax intensity applied while scrolling. |
| `className` | `string` | — | Extra classes applied to the column container. |
***
============================================================
## Infinite Zoom
> Mouse wheel infinite zoom effect.
Source file: components/scroll/infinite-zoom.mdx
***
## Usage
Pass the items as children and scroll the wheel to zoom through them.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/1200/800",
"https://picsum.photos/seed/atelier-2/1200/800",
"https://picsum.photos/seed/atelier-3/1200/800",
]
{IMAGES.map((src) => (
))}
```
***
## API
| Name | Type | Default | Description |
| ----------------- | ----------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | Items to zoom. Required. |
| `zoomAmount` | `number` | `3` | Scale between items. Higher values equals increased depth. |
| `lerpValue` | `number` | `0.08` | Smoothing for the wheel input. |
| `className` | `string` | — | Class for the outer container. Use it to size the zoom and block native scrolling, e.g. `fixed inset-0 overflow-hidden touch-none`. |
| `backgroundSpeed` | `number` | `0.2` | Background speed. Deceleration of the last item in view. |
***
============================================================
## Pixel Scroll
> A grid of pixels that fill in or clear out as you scroll, with optional color hints.
Source file: components/scroll/pixel-scroll.mdx
***
## Usage
By default the component creates its own scroll section and pins the canvas on screen while the grid fills in. This is what the demo above uses.
```tsx
```
Works best with smooth scrolling. You can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
### Color
* `text-white` sets the pixel color.
* `bg-black` sets the background.
```tsx
```
### Direction
* `"cover"` fills the grid in, bottom-to-top, as you scroll.
* `"clear"` starts filled and clears out, bottom-to-top, as you scroll.
* `"sweep"` fills in, then clears out, within a single scroll.
```tsx title="direction=cover"
```
```tsx title="direction=clear"
```
```tsx title="direction=sweep"
```
***
## API
| Name | Type | Default | Description |
| ---------------- | ------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pixelSize` | `number` | `50` | Width and height of one pixel, in CSS pixels. Columns are derived from the canvas width and capped at 200, rows from the canvas height, so pixels stay square on every screen. |
| `height` | `string` | — | Height of the grid, as any CSS length. Left out, the grid is pinned full screen for the whole scroll. Set, it is that tall and scrolls with the page, so `height="210px"` is a thin line at the seam between two sections. |
| `colors` | `string[]` | `[]` | CSS colors a pixel can briefly show before it turns the text color. When empty, pixels turn the text color with no color hint. |
| `colorRatio` | `number` | `0.25` | Fraction of pixels that show a color hint, from 0 to 1. Has no effect when `colors` is empty. |
| `randomness` | `number` | `0.4` | How far each pixel's fill point deviates from the bottom-to-top gradient, from 0 to 1. At `0` the grid fills as an ordered gradient; at `1` the fill points are fully scattered. |
| `direction` | `"cover" \| "clear" \| "sweep"` | `"cover"` | How the grid responds to scroll. `"cover"` fills it in, `"clear"` empties it, and `"sweep"` does both in one scroll. |
| `scrollDistance` | `number` | `200` | How long the animation lasts. `100` equals one viewport of scrolling. |
| `overlap` | `number` | `0` | Shortens the empty space before and after the animation, so the animation starts sooner. `100` equals one viewport of scrolling. |
| `className` | `string` | — | Classes forwarded to the canvas element. |
***
============================================================
## Scattered Scroll
> Items that spread apart horizontally while scrolling.
Source file: components/scroll/scattered-scroll.mdx
***
## Usage
The component takes the items as children. Any element works.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/800/1000",
"https://picsum.photos/seed/atelier-2/800/1000",
"https://picsum.photos/seed/atelier-3/800/1000",
"https://picsum.photos/seed/atelier-4/800/1000",
"https://picsum.photos/seed/atelier-5/800/1000",
]
{IMAGES.map((src) => (
))}
```
Works best with smooth scrolling. You can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
***
## API
| Name | Type | Default | Description |
| ---------------- | ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | The items to animate. Any JSX element allowed. Required. |
| `scrollDistance` | `number` | `200` | How long the animation lasts. `100` equals one viewport of scrolling. |
| `overlap` | `number` | `0` | Shortens the empty space before and after the animation, so the animation starts sooner. `100` equals one viewport of scrolling. |
***
============================================================
## Pixelated Text
> A 2D canvas-based animated pixelated SEO friendly text effect.
Source file: components/text/pixelated-text.mdx
***
## Usage
The text is redrawn as animated pixel blocks on a 2D canvas, shifting between clear and pixelated. The original text stays in the DOM for SEO and screen readers.
```tsx
Enter your text here
```
### Choose the wrapper element
Pass `render` to choose the wrapper element. Your props are merged with the component's, so it stays the right semantic tag while keeping the animation. Without `render` the text renders inside a ``.
```tsx
}>Enter your text here
```
***
## API
| Name | Type | Default | Description |
| ----------- | -------------------------- | ----------- | --------------------------------------------------------------- |
| `children` | `ReactNode` | — | Text content to render. Required. |
| `pixelSize` | `number` | `7` | Pixel size. |
| `chaos` | `number` | `0.2` | Amount of chaos. |
| `depth` | `number` | `8.3` | Amplitude (contrast) between clear and pixelated. |
| `colors` | `string[]` | `undefined` | Cycles through colors each tick. Fallback to the default color. |
| `fps` | `number` | `200` | Frames per second. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
***
============================================================
## Text Bounce
> Letters bounce away from the cursor on hover and spring back into place.
Source file: components/text/text-bounce.mdx
***
## Usage
Each letter drifts away from the cursor on hover, then springs back into place.
```tsx
Enter your text here
```
### Choose the wrapper element
Pass `render` to choose the wrapper element. Your props are merged with the component's, so it stays the right semantic tag while keeping the animation. Without `render` the text renders inside a ``.
```tsx
}>Enter your text here
```
***
## API
| Name | Type | Default | Description |
| ------------- | -------------------------- | --------- | --------------------------------------------------------------------------------- |
| `children` | `string` | — | Text content to render. Required. |
| `pause` | `number` | `0` | Delay in seconds before springing back. |
| `outDuration` | `number` | `0.35` | Duration in seconds for the letter drifting away. |
| `inDuration` | `number` | `0.8` | Duration in seconds for the spring-back animation. |
| `bounce` | `number` | `0.5` | Bounciness of the spring-back. `0` = no overshoot, `1` = maximum oscillation. |
| `distance` | `number` | `35` | How many pixels the letter drifts from its origin. |
| `rotation` | `number` | `25` | Max rotation in degrees applied along the horizontal drift axis. `0` disables it. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
***
============================================================
## Text Fluid
> SEO-friendly text with an animated WebGL shimmer and distortion effect.
Source file: components/text/text-fluid.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
}>Enter your text here
```
***
## API
| Name | Type | Default | Description |
| -------------- | --------- | ------- | ----------------------------------------------------- |
| `children` | `string` | — | Text content. Required. |
| `speed` | `number` | `0.2` | Overall animation speed multiplier. |
| `opacity` | `number` | `1` | Overall opacity. |
| `ripple` | `number` | `0.015` | UV warp strength: how much the surface refracts. |
| `amplitude` | `number` | `0.002` | FBM displacement amplitude: how far vertices scatter. |
| `frequency` | `number` | `1.5` | FBM spatial frequency: size of the noise pattern. |
| `segments` | `number` | `20` | Plane geometry subdivisions, higher = better quality. |
| `webglEnabled` | `boolean` | `true` | Toggle WebGL effect on/off, falls back to plain text. |
| `className` | `string` | — | Class applied to the underlying span. |
***
============================================================
## Text Scramble
> A simple text scramble effect that reveals characters from left to right.
Source file: components/text/text-scramble.mdx
***
## Usage
The text scrambles through random characters and reveals from left to right. The animation plays on mount and replays on hover.
```tsx
Enter your text here
```
### Choose the wrapper element
Pass `render` to choose the wrapper element. Your props are merged with the component's, so it stays the right semantic tag while keeping the animation. Without `render` the text renders inside a ``.
```tsx
}>Enter your text here
```
***
## API
| Name | Type | Default | Description |
| ------------- | -------------------------- | ------------------------------------------ | ---------------------------------------- |
| `children` | `string` | — | Text content to scramble. Required. |
| `duration` | `number` | `0.7` | Total duration of the reveal in seconds. |
| `scrambleFps` | `number` | `30` | Number of scramble updates per second. |
| `playOnMount` | `boolean` | `true` | Plays the scramble animation on mount. |
| `playOnHover` | `boolean` | `true` | Replays the scramble animation on hover. |
| `characters` | `string` | `"abcdefghijklmnopqrstuvwxyz@!#*$%^&+_[]"` | Characters used to scramble. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
***
============================================================
## Smooth Scroll
> Lenis smooth scrolling for the whole page, driven by one shared frame loop.
Source file: foundation/primitive/smooth-scroll.mdx
Adds [Lenis](https://lenis.dev) smooth scrolling to your page, with Motion as the clock.
Scroll, JS animations, and WebGL usually each run on their own loop, and they can fall out of sync.\
**Smooth Scroll** runs them on a single loop, in a fixed order, so they always move together without delay.
***
## Install
***
## Usage
Wrap your root layout with the provider.
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll"
export default function RootLayout({ children }) {
return {children}
}
```
### With the WebGL Provider
Both providers run on Motion's clock, nesting order does not matter.
```tsx title="Root layout"
{children}
```
### Control scrolling from components
Any component under the provider can access the Lenis instance with `useLenis`:
```tsx
import { useLenis } from "lenis/react"
function BackToTop() {
const lenis = useLenis()
return lenis?.scrollTo(0)}>Back to top
}
```
### Pass Lenis options
Any [Lenis option](https://github.com/darkroomengineering/lenis#instance-settings) can be passed through the `options` prop, `syncTouch` is enabled by default:
```tsx title="Custom scroll feel"
{children}
```
The one option you can't change is `autoRaf`: the provider drives Lenis itself.
***
## API
| Name | Type | Default | Description |
| ---------- | -------------- | --------------------- | ----------------------------------------------------- |
| `children` | `ReactNode` | — | Your app or page content. Required. |
| `options` | `LenisOptions` | `{ syncTouch: true }` | Lenis instance settings. `autoRaf` is always `false`. |
***
============================================================
## Text Split
> A foundation block that splits a string into letters or words.
Source file: foundation/primitive/text-split.mdx
A structural building block for composing your own text animations, like [Text Bounce](/docs/components/text/text-bounce).
By default, each item is wrapped in an `overflow-clip` span (disable with `showMask={false}`). This keeps slide and reveal animations clipped to each letter's bounding box.
***
## Install
***
## Usage
By default, the string is split into individual letters:
```tsx title="Split by letters"
Hello world
```
Use `splitBy="words"` to split on spaces instead:
```tsx title="Split by words"
Hello world
```
### With renderItems
`renderItems` lets you render each piece yourself. Here, every letter lifts on hover:
```tsx title="Hover lift"
(
{char}
)}
>
Hello world
```
***
## API
| Name | Type | Default | Description |
| ------------- | -------------------------------------------- | ----------- | ------------------------------------------ |
| `children` | `string` | — | Text to split. Required. |
| `splitBy` | `"letters" \| "words"` | `"letters"` | How to split the string. |
| `showMask` | `boolean` | `true` | Wrap each item in an `overflow-clip` span. |
| `renderItems` | `(char: string, index: number) => ReactNode` | — | Custom render for each split item. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
***
============================================================
## WebGL Image
> A foundation block that mirrors an image onto a WebGL plane while preserving accessibility.
Source file: foundation/primitive/webgl-image.mdx
A structural building block for composing your own WebGL image effects, like [Curve Media](/docs/components/scroll/curve-media).
The image is rendered twice: as a real ` ` for SEO and screen readers (hidden when WebGL is on), and as a `Texture` on a plane that tracks the element's bounding box. The two stay pixel-aligned, so the WebGL plane sits exactly over the DOM element.
***
## Install
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
With no `material`, the plane uses a `meshBasicMaterial` and looks identical to a plain ` `:
```tsx title="Default material"
```
### Object-fit
`object-fit: cover` and `object-fit: contain` are replicated on the plane. Set it the way you would on a normal ` `:
```tsx title="Cover and contain"
```
### With material
`material` lets you provide your own R3F material. It receives the loaded `Texture` and a live `pointer` (UV + hover):
```tsx title="Custom shader material"
(
)}
/>
```
The `pointer` object is mutated in place, so reading it inside a `useFrame` gives current values without re-renders.
Use `segments` to subdivide the plane when your shader displaces vertices. `1` is enough for fragment-only effects.
***
## API
| Name | Type | Default | Description |
| -------------- | ----------------------------------------------- | ------- | --------------------------------------------------------------------- |
| `src` | `string` | — | Image source. Required. |
| `alt` | `string` | — | Alternative text. Required. |
| `material` | `(map: Texture, pointer: Pointer) => ReactNode` | — | Custom R3F material. Receives the loaded texture and a live pointer. |
| `segments` | `number` | `1` | Plane geometry subdivisions. Increase for vertex-displacing shaders. |
| `webglEnabled` | `boolean` | `true` | Toggle the WebGL plane. When `false`, only the DOM image is rendered. |
| `zIndex` | `number` | `0` | Render order within the WebGL portal. |
| `autoReflow` | `boolean` | `false` | Re-measures the DOM rect every frame. |
Standard ` ` attributes (`className`, `style`, `width`, `height`, ...) are accepted and forwarded to the DOM element.
### Pointer
| Name | Type | Description |
| ------- | --------- | ----------------------------------------------------------------------------------------------- |
| `uv` | `Vector2` | Normalized pointer position inside the element. `(0, 0)` is bottom-left, `(1, 1)` is top-right. |
| `hover` | `number` | `1` while the pointer is over the element, `0` otherwise. |
***
============================================================
## WebGL Provider
> The root that runs one shared WebGL canvas for the whole page.
Source file: foundation/primitive/webgl-provider.mdx
WebGL Provider mounts a single `` for the whole page and renders your application inside it. Every other WebGL component renders into this canvas, so the application holds one WebGL context instead of one per effect.
The canvas runs on Motion's clock instead of its own `requestAnimationFrame`.
With [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) on the page, every tick moves the scroll first, then the page, then renders the canvas, so WebGL elements always stay exactly on top of the elements they follow.
Add the provider once, at your application root. For the systems it belongs to, see the [introduction](/docs/getting-started/how-it-works).
***
## Install
***
## Usage
Wrap your root layout with the provider. The canvas renders fixed and full screen behind the page, with your content on top:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider"
export default function RootLayout({ children }) {
return {children}
}
```
### Pass Canvas options
Any [React Three Fiber Canvas prop](https://r3f.docs.pmnd.rs/api/canvas) passes through the provider, so you can set props such as `gl`, `camera`, and `dpr`. The provider defaults to `eventPrefix="client"` and `dpr={[1, 1.5]}`, which your props override:
```tsx title="Custom Canvas options"
{children}
```
### Scope the canvas to its container
By default, the canvas covers the viewport. Set `contained` to keep it inside the provider's own element, which must have its own size. The component previews use this to render an effect inside a card rather than across the screen:
```tsx title="Contained canvas"
```
### Track scene readiness
`useWebglReady` returns `false` until the scene's shaders have compiled. This can help prevent visible shader-compilation stutter when an effect first appears.
```tsx
import { useWebglReady } from "@/components/webgl-provider/webgl-provider"
function Effect() {
const ready = useWebglReady()
return {/* ... */}
}
```
***
## API
| Name | Type | Default | Description |
| ----------- | --------------- | ------- | ------------------------------------------------------------------------------ |
| `children` | `ReactNode` | — | Your app or page content. Rendered as normal DOM next to the canvas. Required. |
| `contained` | `boolean` | `false` | Scope the canvas to the provider's element instead of the viewport. |
| `className` | `string` | — | Class on the wrapper element. |
| `style` | `CSSProperties` | — | Merged into the canvas style. |
All other [React Three Fiber Canvas props](https://r3f.docs.pmnd.rs/api/canvas) are forwarded, except `children` and `eventSource`.
`frameloop` is always `"never"`, because Motion's frame loop advances the canvas.
### useWebglReady
| Name | Type | Default | Description |
| --------- | ------------ | -------------- | ----------------------------------------------------------- |
| `scene` | `Scene` | current scene | Scene to compile. |
| `camera` | `Camera` | current camera | Camera to compile against. |
| `enabled` | `boolean` | `true` | Run the compile step. Set `false` to skip it. |
| `onReady` | `() => void` | — | Called once the scene is compiled and one frame has passed. |
Returns a `boolean` that is `false` until the scene is ready, then `true`.
***
============================================================
## WebGL Scene
> A foundation block with its own Three.js scene and camera, rendered into a tracked DOM rect on the shared canvas.
Source file: foundation/primitive/webgl-scene.mdx
A structural building block for effects that need camera motion (dolly, FOV zoom, orbit) and so can't share the fixed global camera used by plane blocks like [WebGL Image](/docs/foundation/primitive/webgl-image).
`WebglScene` tracks a DOM element's bounding rect and renders its children into that rectangle on the **same** WebGL context. No extra `` is created. Its camera is independent of the global one, so children are free to move it.
***
## Install
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Pass a `ref` to the DOM element that defines the viewport, and place any R3F content as children. `WebglScene` teleports the children onto the shared canvas for you:
```tsx
const surface = useRef(null)
```
***
## API
| Name | Type | Default | Description |
| ------------- | -------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `track` | `RefObject` | — | Ref to the DOM element whose bounding rect defines the viewport. Required. |
| `children` | `ReactNode` | — | R3F content, rendered into the tracked rect with the own camera. |
| `mode` | `"texture" \| "scissor"` | `"scissor"` | `scissor` renders the scene above other content, outside of global post-processing. `texture` includes it in post-processing, so effects like bloom or distortion apply to the scene. |
| `camera` | `PerspectiveCamera` | — | Pre-configured camera. Defaults to a `PerspectiveCamera` at FOV 75, z=5. |
| `zIndex` | `number` | `0` | Texture mode only. Render order of the display plane. |
| `transparent` | `boolean` | `true` | Texture mode only. Set `false` for an opaque scene with its own background. |
| `priority` | `number` | — | `useFrame` render priority. |
***
============================================================
## WebGL Text
> A foundation block that mirrors a text element onto a WebGL plane while preserving accessibility.
Source file: foundation/primitive/webgl-text.mdx
A structural building block for composing your own WebGL text effects, like [Text Fluid](/docs/components/text/text-fluid).
The text is rendered twice: as a real `` for SEO and screen readers (hidden when WebGL is on), and as a `CanvasTexture` painted onto a plane that tracks its bounding box. The two stay pixel-aligned, so the WebGL plane sits exactly over the DOM element.
***
## Install
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
With no `material`, the plane uses a `meshBasicMaterial` and looks identical to a plain ``:
```tsx title="Default material"
Hello world
```
### With material
`material` lets you provide your own R3F material. It receives the rasterized `Texture` and a live `pointer` (UV + hover):
```tsx title="Custom shader material"
(
)}
>
Hello world
```
The `pointer` object is mutated in place, so reading it inside a `useFrame` gives current values without re-renders.
Use `segments` to subdivide the plane when your shader displaces vertices. `1` is enough for fragment-only effects.
***
## API
| Name | Type | Default | Description |
| -------------- | ----------------------------------------------- | ---------- | ------------------------------------------------------------------------ |
| `children` | `string` | — | Text to render. Required. |
| `material` | `(map: Texture, pointer: Pointer) => ReactNode` | — | Custom R3F material. Receives the rasterized texture and a live pointer. |
| `segments` | `number` | `1` | Plane geometry subdivisions. Increase for vertex-displacing shaders. |
| `webglEnabled` | `boolean` | `true` | Toggle the WebGL plane. When `false`, only the DOM span is rendered. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
| `zIndex` | `number` | `0` | Render order within the WebGL portal. |
| `autoReflow` | `boolean` | `false` | Re-measures the DOM rect every frame. |
| `pixelRatio` | `number` | `2` | Canvas backing resolution, capped at device DPR. |
### Pointer
| Name | Type | Description |
| ------- | --------- | ----------------------------------------------------------------------------------------------- |
| `uv` | `Vector2` | Normalized pointer position inside the element. `(0, 0)` is bottom-left, `(1, 1)` is top-right. |
| `hover` | `number` | `1` while the pointer is over the element, `0` otherwise. |
***
============================================================
## WebGL Video
> A foundation block that mirrors a video onto a WebGL plane while preserving the DOM element.
Source file: foundation/primitive/webgl-video.mdx
A structural building block for composing your own WebGL video effects. It is the video counterpart of [WebGL Image](/docs/foundation/primitive/webgl-image), so any `material` you write for one works on the other.
The video is rendered twice: as a real `` element (hidden when WebGL is on), and as a `VideoTexture` on a plane that tracks the element's bounding box. The texture pulls each new frame from the same element, so only one video decodes.
***
## Install
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
With no `material`, the plane uses a `meshBasicMaterial` and looks identical to a plain ``. The video autoplays muted and looped by default, so it plays without a user gesture:
```tsx title="Default material"
```
### Object-fit
`object-fit: cover` and `object-fit: contain` are replicated on the plane. Set it the way you would on a normal ``:
```tsx title="Cover and contain"
```
### With material
`material` lets you provide your own R3F material. It receives the `VideoTexture` and a live `pointer` (UV + hover). Reuse the exact shader you wrote for a still image:
```tsx title="Custom shader material"
(
)}
/>
```
The `pointer` object is mutated in place, so reading it inside a `useFrame` gives current values without re-renders.
Use `segments` to subdivide the plane when your shader displaces vertices. `1` is enough for fragment-only effects.
***
## API
| Name | Type | Default | Description |
| -------------- | ----------------------------------------------- | ------- | --------------------------------------------------------------------- |
| `src` | `string` | — | Video source. Required. |
| `material` | `(map: Texture, pointer: Pointer) => ReactNode` | — | Custom R3F material. Receives the video texture and a live pointer. |
| `segments` | `number` | `1` | Plane geometry subdivisions. Increase for vertex-displacing shaders. |
| `webglEnabled` | `boolean` | `true` | Toggle the WebGL plane. When `false`, only the DOM video is rendered. |
| `zIndex` | `number` | `0` | Render order within the WebGL portal. |
| `autoReflow` | `boolean` | `false` | Re-measures the DOM rect every frame. |
Standard `` attributes (`className`, `style`, `poster`, `controls`, ...) are accepted and forwarded to the DOM element. `autoPlay`, `muted`, `loop` and `playsInline` default to `true` and can be overridden.
### Pointer
| Name | Type | Description |
| ------- | --------- | ----------------------------------------------------------------------------------------------- |
| `uv` | `Vector2` | Normalized pointer position inside the element. `(0, 0)` is bottom-left, `(1, 1)` is top-right. |
| `hover` | `number` | `1` while the pointer is over the element, `0` otherwise. |
***
============================================================
## How to contribute
> Contribution guidelines for the project.
Source file: getting-started/contribution.mdx
## Notes
This project uses `pnpm` as the package manager; you must have [pnpm
installed](https://pnpm.io/) globally to run commands and tests locally.
***
## Project structure
Here is where each type of file lives:
* `src/registry/base` holds the components that ship to users.
* `src/registry/hooks` and `src/registry/lib` hold code shared between components.
* `src/registry/demos` holds demo components and their controls.
* `src/registry/index.ts` declares every component and its dependencies.
* `src/content` holds documentation.
```text title="Visual representation"
(src/registry)
|-- base
| |-+ your-component
| |-+ your-component.tsx # Your new component
|-- hooks
| |-+ use-frame-loop.ts # Shared hooks
|-- demos
| |-+ your-component
| | |-+ your-component.tsx # Your new demo component
| | |-+ controls.ts # Your demo controls
| |
| |-- index.ts # Exports the demo components
(src/content)
|-- en
|-- components
|-- _dir.yml
|-+ your-component
|-+ your-component.mdx # Your new component documentation
```
***
## Component format
Each component has a single responsibility and lives in a single file, so it stays self-contained and easy to reuse.
Every base component starts with `"use client"`.
Components run hooks and browser APIs, and users drop them into server components, which is the default in the Next.js app router.
```tsx title="Component structure"
"use client"
const NAMED_X_CONSTANT = {...} as const
const NAMED_Y_CONSTANT = "value" as const
function helperFunction() {...}
type ComponentExampleProps = {...} & ComponentProps<'div'>
export function ComponentExample({}: ComponentExampleProps) {...}
```
When logic is shared across components, put it in `src/registry/hooks` or `src/registry/lib` and list the file in the component's `shared` field.
The `use-frame-loop` hook is one example.
***
## Dependencies
This project uses [motion.dev](https://motion.dev/) for DOM-based animations, [React Three Fiber](https://r3f.docs.pmnd.rs/) for WebGL effects, and [Tailwind CSS](https://tailwindcss.com/) for styling.
Vanilla JS animations such as `requestAnimationFrame` and the Web Animations API can also be used where a library isn't needed.
Anything listed in a component's `dependencies` is installed on the user's machine, so the list has to be complete.
A component that imports Three.js types declares `@types/three` next to `three`.
***
## Testing
Tests run automatically on every pull request.
They verify that every component has its files, demo, export, and documentation in sync.
Unit tests per component are not required by default.
***
## Add a new component
Use the `pnpm scafold-new` command.
It creates the base component, the demo, a templated MDX doc, and patches the registry and demo index.
```bash title="Terminal"
pnpm scafold-new animated-text --category scroll
pnpm scafold-new sparkle --category background
```
If you prefer to do it manually, follow the steps below.
### Base component
Create a folder in `src/registry/base` named after the component:
```tsx title="src/registry/base/component-name/component-name.tsx"
"use client"
export function ComponentName() {
return <>>;
}
```
### Demo component
Create a demo in `src/registry/demos` and structure it how you want:
```tsx title="src/registry/demos/component-name/component-name.tsx"
import { ComponentName } from "@/registry/base/component-name/component-name";
export default function ComponentNameDemo() {
return ;
}
```
### Exporting the demo
Export the demo in `src/registry/demos/index.ts`:
```tsx title="src/registry/demos/index.ts"
export const demos: Record<
string,
React.LazyExoticComponent
> = {
"component-name": lazy(() => import("./component-name/component-name")),
};
```
### Adding to the registry
Declare the component in `src/registry/index.ts`:
```tsx title="src/registry/index.ts"
export const components: TRegistryComponent[] = [
{
name: "new-component",
files: ["new-component.tsx"],
description: "My new component",
shared: ["hooks/use-my-hook.ts"],
dependencies: ["motion"],
registryDependencies: ["text-split"],
},
];
```
`shared` takes full paths with their extension, relative to `src/registry`.
`registryDependencies` names other registry components, which the CLI installs alongside this one.
***
## Write the documentation
A component page opens on its preview, then folds the prose into collapsible sections.
The order is frontmatter, install guide, Usage, API, and the components it builds on.
### Frontmatter
Add the metadata for the component:
```yaml title="Frontmatter"
---
title: "New Component"
description: "My new component"
icon: "Component"
---
```
### Guide and preview
The preview is built by the doc page from the registry, so the document does not declare it.
Add the installation guide with the ` ` component, using the exact name of the base component.
It renders the agent prompt, with the CLI command and the file-by-file copy collapsed underneath it.
```tsx title="Install example"
```
### Usage and API sections
Wrap the prose in ` ` so the page opens on the preview:
````markdown title="Section structure"
Import paths repeat the component name, because each file installs to
`components//.tsx`:
```tsx
import { ComponentName } from "@/components/component-name/component-name";
```
---
| Name | Type | Default | Description |
| ------- | -------- | --------- | ---------------------------------------- |
| `speed` | `number` | `1` | Controls the animation speed multiplier. |
| `color` | `string` | `#ff5733` | Sets the primary color of the component. |
````
Close the page with ` `, which links the component to the primitives it is built from:
```tsx title="Built on"
```
### Controls
Controls let users change props on the live preview, and the values travel into the copyable agent prompt.
Declare them in `src/registry/demos//controls.ts` and register the file in `src/registry/demos/controls.ts`.
The values have to match the default props of the base component:
```ts title="src/registry/demos/component-name/controls.ts"
import type { ControlDef } from "@/types/controls"
export const controls: Record = {
speed: { type: "slider", value: 1, min: 0, max: 10, step: 0.1 },
color: { type: "color", value: "#ff5733" },
palette: { type: "palette", value: ["#ff5733", "#33c1ff"], min: 2, max: 5 },
variant: { type: "select", value: "solid", options: ["solid", "outline"] },
enabled: { type: "boolean", value: true },
glow: { type: "slider", value: 0.5, min: 0, max: 1, step: 0.01, showIf: "enabled" },
}
```
| Type | Fields | Renders |
| --------- | -------------------------- | ------------- |
| `slider` | `value` `min` `max` `step` | Slider |
| `color` | `value` | Color picker |
| `palette` | `value` `min` `max` | Color list |
| `select` | `value` `options` | Select |
| `boolean` | `value` | Toggle switch |
Any control takes an optional `showIf`, naming a boolean control that has to be on for it to render.
Export the base component's props type, then spread `controls` onto it:
```tsx title="Demo component"
export default function ComponentNameDemo(
controls: Partial,
) {
return ;
}
```
============================================================
## Collage 01
> Fluid post-processing over WebGL primitives plus infinite parallax, and text roll.
Source file: collage/collage-01.mdx
## What's a Collage?
Collages are just showcases. They demonstrate how components fit together when assembled. Each component can be installed individually from its docs page.
***
## Components used
* [WebGL Image](/docs/foundation/primitive/webgl-image): lets post-processing apply to ` ` content.
* [WebGL Text](/docs/foundation/primitive/webgl-text): lets post-processing apply to text.
* [Fluid Distortion](/docs/components/cursor/fluid-distortion)
* [Text Roll](/docs/components/text/text-roll)
* [Infinite Parallax](/docs/components/scroll/infinite-parallax)
* [Smooth Scroll](/docs/foundation/primitive/smooth-scroll)
***
## Source
============================================================
## Glowing Fog
> Dynamic fog and light beam cursor follow effect.
Source file: components/background/glowing-fog.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
***
## API
| Name | Type | Default | Description |
| -------------- | ------------------- | ----------- | ---------------------------------------------------- |
| speed | `number` | `18` | Animation speed of the effect. |
| beamColor | `string` | `"#6031ff"` | Color of the central light beam. |
| beamX | `number` | `0.5` | Horizontal position of the beam. |
| beamY | `number` | `0.5` | Vertical position of the beam. |
| beamScale | `number` | `0.5` | Intensity / size of the beam glow. |
| fogDensity | `number` | `1` | Density of the fog. |
| grainIntensity | `number` | `0.1` | Amount of grain. |
| mouseFollow | `number` | `0.9` | Strength of mouse-based parallax follow on the beam. |
| onTextureReady | `(texture) => void` | `undefined` | Returns the rendered result as a texture. |
***
============================================================
## Gradient Flow
> An animated multi-color gradient WebGL flow field background.
Source file: components/background/gradient-flow.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
***
## API
| Name | Type | Default | Description |
| ------------ | ---------- | ----------------------------------- | ---------------------------------------------------------------------- |
| colors | `string[]` | `["#2D1499", "#C2D0F5", "#F7EBEB"]` | Palette blended across the flow, interpolated in Oklab. 2 to 8 colors. |
| speed | `number` | `0.1` | Animation speed of the flow field. |
| scale | `number` | `0.5` | Scale of the flow pattern. |
| amplitude | `number` | `0.5` | Strength of the domain warping. |
| frequency | `number` | `0.5` | Frequency of the warping detail. |
| stripes | `number` | `4` | Number of banding stripes across the gradient. |
| seed | `number` | `0` | Random seed that shifts the pattern. |
| dither | `boolean` | `false` | Enables ordered (Bayer) dithering. |
| ditherAmount | `number` | `5` | Dither strength. Only applies when `dither` is on. |
| ditherSize | `number` | `4` | Size of each dither cell in pixels. Only applies when `dither` is on. |
| grain | `boolean` | `true` | Enables animated film grain. |
| grainAmount | `number` | `0.22` | Grain strength. Only applies when `grain` is on. |
***
============================================================
## Halftone Glow
> A WebGL pulsing glow effect with halftone rendering.
Source file: components/background/halftone-glow.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
***
## API
| Name | Type | Default | Description |
| --------------- | ------------------- | ----------- | ------------------------------------------- |
| speed | `number` | `16` | Pulse speed. |
| blurAmount | `number` | `0.2` | Amount of blur. |
| edgeSize | `number` | `10` | Thickness of the edge glow. |
| edgeDistortion | `number` | `0.02` | How much noise warps the edge glow. |
| ringDistortion | `number` | `0.1` | How much noise warps the ring pulse. |
| edgeColor | `string` | `"#1d334e"` | Color of the edge glow. |
| ringColor | `string` | `"#4599ff"` | Color of the ring pulse. |
| backgroundColor | `string` | `"#000000"` | Solid backdrop the glow is composited over. |
| onTextureReady | `(texture) => void` | `undefined` | Returns the rendered effect as a texture. |
***
============================================================
## Scattered Grid
> A grid where the item nearest the cursor scales up while the others scatter around it.
Source file: components/background/scattered-grid.mdx
***
## Usage
Wrap each item in `ScatteredGridItem`. The layout lives on the container `className`, so any grid or flex arrangement works.
```tsx
import { ScatteredGrid, ScatteredGridItem } from "@/components/scattered-grid/scattered-grid"
const IMAGES = [
"https://picsum.photos/seed/atelier-1/1200/800",
"https://picsum.photos/seed/atelier-2/1200/800",
"https://picsum.photos/seed/atelier-3/1200/800",
"https://picsum.photos/seed/atelier-4/1200/800",
"https://picsum.photos/seed/atelier-5/1200/800",
"https://picsum.photos/seed/atelier-6/1200/800",
"https://picsum.photos/seed/atelier-7/1200/800",
"https://picsum.photos/seed/atelier-8/1200/800",
"https://picsum.photos/seed/atelier-9/1200/800",
]
{IMAGES.map((url) => (
))}
```
***
## API
### ScatteredGrid
| Name | Type | Default | Description |
| ----------------- | ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | Items wrapped in `ScatteredGridItem`. |
| `className` | `string` | — | Container classes. The layout goes here. |
| `hoverScale` | `number` | `1.65` | Scale of the active item. The surrounding items scale toward this value the closer they are. |
| `pushDistance` | `number` | `0.9` | How far the items next to the active one are pushed away, as a fraction of the active item's width. |
| `falloffRadius` | `number` | `2` | Radius of the effect, in multiples of the active item's width. Push, scatter, rotation, and scale fade to zero as items get further from the active one. |
| `scatterDistance` | `number` | `0.2` | Random offset added on top of the push, as a fraction of the active item's width. |
| `maxRotation` | `number` | `10` | Random tilt of the surrounding items, in degrees. |
| `duration` | `number` | `0.3` | How long the movement visually takes, in seconds. |
| `bounce` | `number` | `0.45` | Bounciness of the spring, from 0 to 1. |
### ScatteredGridItem
| Name | Type | Default | Description |
| ----------- | ----------- | ------- | ----------------------------------- |
| `children` | `ReactNode` | — | The cell content, usually an image. |
| `className` | `string` | — | Cell classes, such as `col-span-2`. |
***
============================================================
## Tag Cloud
> Images placed on a sphere and dragged around it, kept flat and scaled by depth.
Source file: components/background/tag-cloud.mdx
***
## Usage
Pass any elements as children. Spin the sphere by dragging it or scrolling over it.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/400/600",
"https://picsum.photos/seed/atelier-2/600/400",
"https://picsum.photos/seed/atelier-3/500/500",
"https://picsum.photos/seed/atelier-4/400/500",
"https://picsum.photos/seed/atelier-5/600/600",
"https://picsum.photos/seed/atelier-6/500/400",
"https://picsum.photos/seed/atelier-7/600/500",
"https://picsum.photos/seed/atelier-8/400/400",
"https://picsum.photos/seed/atelier-9/500/600",
"https://picsum.photos/seed/atelier-10/500/600",
"https://picsum.photos/seed/atelier-11/500/600",
]
{IMAGES.map((url) => (
))}
```
***
## API
| Name | Type | Default | Description |
| ------------- | ----------- | ------- | --------------------------------------------------------------- |
| `children` | `ReactNode` | — | Elements placed on the sphere, one point each. Required. |
| `radius` | `number` | `160` | Distance in pixels from the center of the sphere to each child. |
| `perspective` | `number` | `900` | Perspective distance in pixels. |
| `reveal` | `boolean` | `true` | Animation played when the component mounts. |
| `drag` | `boolean` | `true` | Spin the sphere by dragging it. |
| `wheel` | `boolean` | `true` | Spin the sphere with the mouse wheel or trackpad. |
| `smoothing` | `number` | `0.05` | How smooth the sphere follows drag and wheel input. |
| `className` | `string` | — | Class names applied to the container. |
***
============================================================
## Dither Cursor
> A trail of dithered dots that follows the cursor and bends the scene behind it.
Source file: components/cursor/dither-cursor.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Then use it anywhere in your app:
```tsx
```
***
## API
| Name | Type | Default | Description |
| --------------- | --------------- | ----------- | ------------------------------------------------------------------------------------------------- |
| `pixelSize` | `number` | `10` | Width of one dither cell in pixels. Each cell holds a 4x4 block of dots. |
| `color` | `string` | `"#fff7ee"` | Color of the dots. |
| `intensity` | `number` | `1.03` | Scales cursor speed into dot coverage. Higher values fill a cell sooner. |
| `distortion` | `number` | `4` | How far the velocity field pushes the scene behind the trail. `0` leaves it undistorted. |
| `aberration` | `number` | `2` | How far the red and blue channels separate along the direction of travel. `0` keeps them aligned. |
| `inversion` | `number` | `0.3` | Inverts `color` where the trail crosses WebGL content. HTML behind the canvas does not invert it. |
| `force` | `number` | `1.68` | Multiplies pointer movement before it enters the field. |
| `radius` | `number` | `0.06` | Reach of a single pointer event across the field. |
| `decay` | `number` | `0.9` | Per-frame survival of the field, normalized to 60 Hz. Lower values shorten the trail. |
| `blendFunction` | `BlendFunction` | `NORMAL` | Blend mode used to composite the effect. |
***
============================================================
## Hover Burst
> Images or items that burst around the cursor when it enters an element.
Source file: components/cursor/hover-burst.mdx
***
## Usage
Wrap the hover target with the component and pass the burst images through `items`. A burst fires when the cursor enters the target, so the cursor has to leave and re-enter for the next one.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/400/400",
"https://picsum.photos/seed/atelier-2/400/400",
"https://picsum.photos/seed/atelier-3/400/400",
"https://picsum.photos/seed/atelier-4/400/400",
]
(
))}
>
Hover over me.
```
***
## API
| Name | Type | Default | Description |
| ------------- | -------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | The hover target. |
| `items` | `ReactNode` | — | The nodes that burst from the cursor, usually images. |
| `driftAmount` | `number` | `200` | How far items drift away from the cursor, in pixels. |
| `duration` | `number` | `1.1` | How long an item drifts before it fades away, in seconds. |
| `burstCount` | `number` | `8` | How many items scatter on each burst. |
| `spin` | `number` | `200` | How much an item can rotate during its flight, in degrees, in either direction. Crank it past `360` to make items spin on themselves. `0` keeps them straight. |
| `linger` | `number` | `0` | How long an item holds after it stops drifting, before it scales away, in seconds. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
***
============================================================
## Image Bloom
> Images that pop up along the cursor's path and fade out behind it.
Source file: components/cursor/image-bloom.mdx
***
## Usage
Image Bloom renders a fixed layer behind the page and tracks the cursor across the whole viewport, so it sits next to your content instead of wrapping it.
An image blooms at the cursor every `spawnSpeed` seconds, or every `spawnDistance` px of movement, whichever comes first.
Each image pops in with a random tilt, holds, then retracts.
Unlike [Image Trail](/docs/components/cursor/image-trail), it keeps spawning while the cursor rests.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/1200/800",
"https://picsum.photos/seed/atelier-2/1200/800",
"https://picsum.photos/seed/atelier-3/1200/800",
]
Hover anywhere
{IMAGES.map((src) => (
))}
```
***
## API
| Name | Type | Default | Description |
| --------------- | ----------- | ------- | --------------------------------------------------------------------- |
| `children` | `ReactNode` | — | The images (or any elements) to bloom. |
| `spawnDistance` | `number` | `70` | Distance in px the cursor must travel to bloom the next image. |
| `spawnSpeed` | `number` | `0.4` | Seconds between blooms while the cursor rests. Lower is faster. |
| `lifetime` | `number` | `1.2` | Seconds an image takes to grow to full size, after which it retracts. |
| `rotation` | `number` | `17` | Max random tilt in degrees. |
***
============================================================
## Elastic Stick
> Items that bounce and stick to the center while scrolling.
Source file: components/scroll/elastic-stick.mdx
***
## Usage
The component takes the items as children. Any element works.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/1200/800",
"https://picsum.photos/seed/atelier-2/1200/800",
"https://picsum.photos/seed/atelier-3/1200/800",
]
{IMAGES.map((src) => (
))}
```
Works best with smooth scrolling, you can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
***
## API
| Name | Type | Default | Description |
| ---------------- | ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | The items to animate. |
| `staggerValue` | `number` | `0.03` | Delay between each item, as a fraction of the scroll. |
| `scrollDistance` | `number` | `400` | How long the animation lasts. `100` equals one viewport of scrolling. |
| `overlap` | `number` | `0` | Shortens the empty space before and after the animation, so the animation starts sooner. `100` equals one viewport of scrolling. |
***
============================================================
## Letter Swarm
> Letters that come together into readable text as you scroll, then scatter again.
Source file: components/scroll/letter-swarm.mdx
***
## Usage
The component takes a string as children and animates it letter by letter.
```tsx
}>Enter your text here
```
### Choose the wrapper element
Pass `render` to choose the wrapper element. Your props are merged with the component's, so it stays the right semantic tag while keeping the animation. Without `render` the text renders inside a ``.
```tsx
}>
Order from chaos
```
Works best with smooth scrolling. You can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
***
## API
| Name | Type | Default | Description |
| ---------------- | -------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `string` | — | The text content to animate. Split per letter. |
| `depth` | `number` | `10` | Distance every letter starts below its resting spot. `100` equals one viewport height. |
| `scatter` | `number` | `60` | Extra random distance added per letter on top of `depth`. `100` equals one viewport height. |
| `spread` | `number` | `2` | Multiplies `scatter` to stretch the letters apart. Higher values make their speeds differ more. |
| `rotation` | `number` | `50` | Maximum starting tilt of a letter in degrees. Each letter gets a random fraction of it, in a random direction. |
| `fade` | `number` | `1` | Amount of transparency letters start and end with. `1` fades them in from invisible, `0` disables the fade. |
| `seed` | `number` | `2` | Seed for the randomness. The same seed always produces the same arrangement; change it to reshuffle the letters. |
| `scrollDistance` | `number` | `500` | How long the animation lasts. `100` equals one viewport of scrolling. |
| `overlap` | `number` | `0` | Shortens the empty space before and after the animation, so the animation starts sooner. `100` equals one viewport of scrolling. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
***
============================================================
## Magnify Trail
> A row of overlapping images that grow as they pass the center of the screen while scrolling.
Source file: components/scroll/magnify-trail.mdx
***
## Usage
Pass a list of images as children. They line up in a row and cross the screen as you scroll.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/800/1000",
"https://picsum.photos/seed/atelier-2/800/1000",
"https://picsum.photos/seed/atelier-3/800/1000",
"https://picsum.photos/seed/atelier-4/800/1000",
"https://picsum.photos/seed/atelier-5/800/1000",
"https://picsum.photos/seed/atelier-6/800/1000",
"https://picsum.photos/seed/atelier-7/800/1000",
"https://picsum.photos/seed/atelier-8/800/1000",
]
{IMAGES.map((src) => (
))}
```
Works best with smooth scrolling. You can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
***
## API
| Name | Type | Default | Description |
| ---------------- | ----------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | The items in the row, usually images. |
| `magnify` | `number` | `1.7` | Scale of an item as it passes the center. `1` turns off the magnification. |
| `slant` | `number` | `0.3` | How far items move up and down as they cross, as a fraction of half the viewport. Each item comes in from the top and leaves toward the bottom. `0` keeps them level. |
| `tilt` | `number` | `45` | How much items rotate as they cross, in degrees. They tilt one way coming in and the other way going out. `0` keeps them straight. |
| `scrollDistance` | `number` | `400` | How long the animation lasts. `100` equals one viewport of scrolling. |
| `overlap` | `number` | `0` | Shortens the empty space before and after the animation, so it starts sooner. `100` equals one viewport of scrolling. |
***
============================================================
## Stacking Grid
> A configurable grid of cards that stacks to the center while scrolling.
Source file: components/scroll/stacking-grid.mdx
***
## Usage
The component takes a list of cards as children and stacks them to the center of the screen one by one as you scroll. Gap and padding live on the container `className`.
```tsx
const IMAGES = [
"https://picsum.photos/seed/atelier-1/800/1000",
"https://picsum.photos/seed/atelier-2/1200/800",
"https://picsum.photos/seed/atelier-3/900/900",
"https://picsum.photos/seed/atelier-4/800/1100",
"https://picsum.photos/seed/atelier-5/1200/700",
"https://picsum.photos/seed/atelier-6/800/900",
"https://picsum.photos/seed/atelier-7/1000/1300",
"https://picsum.photos/seed/atelier-8/1200/900",
"https://picsum.photos/seed/atelier-9/900/1200",
"https://picsum.photos/seed/atelier-10/1100/800",
"https://picsum.photos/seed/atelier-11/900/700",
"https://picsum.photos/seed/atelier-12/1000/800",
]
{IMAGES.map((url) => (
))}
```
Works best with smooth scrolling. You can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
***
## API
| Name | Type | Default | Description |
| ---------------- | ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | The cards to place in the grid. The number of children determines the number of rows. |
| `slot` | `ReactNode` | — | Static content pinned to the screen, unaffected by the animation. Fills the viewport with no layout of its own, above the items. |
| `columns` | `number` | `4` | Number of grid columns. |
| `scatter` | `number` | `20` | Maximum random offset of a card from its cell, as a percentage of the cell size. `0` keeps the grid even. |
| `rotation` | `number` | `10` | Maximum tilt of a stacked card in degrees. Each card gets a random fraction of it, in a random direction. |
| `seed` | `number` | `1` | Seed for the randomness. The same seed always produces the same arrangement; change it to reshuffle the cards. |
| `scrollDistance` | `number` | `400` | How long the animation lasts. `100` equals one viewport of scrolling. |
| `overlap` | `number` | `0` | Shortens the empty space before and after the animation, so the animation starts sooner. `100` equals one viewport of scrolling. |
| `className` | `string` | — | Extra classes for the grid container. |
***
============================================================
## Wavy Scroll
> Text, images and any other element that wave across the screen while scrolling.
Source file: components/scroll/wavy-scroll.mdx
***
## Usage
The component takes a regular sentence as children. You can mix text with images or any other element.
```tsx
The lazy fox
jumps over
the brown
dog.
```
Works best with smooth scrolling. You can add [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) at the root for that:
```tsx title="Root layout"
import { SmoothScroll } from "@/components/smooth-scroll/smooth-scroll";
export default function RootLayout({ children }) {
return {children} ;
}
```
***
## API
| Name | Type | Default | Description |
| ---------------- | ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | The sentence. Text is split per letter, other elements are animated as one block. |
| `amplitude` | `number` | `80` | Maximum wave height in pixels. |
| `wavelength` | `number` | `1000` | Distance in pixels between two wave crests. |
| `intensity` | `number` | `1.5` | How strongly the wave reacts to scrolling. `2` gives a full wave at half the scroll speed. |
| `scrollDistance` | `number` | `400` | How long the animation lasts. `100` equals one viewport of scrolling. |
| `overlap` | `number` | `0` | Shortens the empty space before and after the animation, so the animation starts sooner. `100` equals one viewport of scrolling. |
***
============================================================
## Falling Text
> Text that falls and bounces when the cursor moves over it.
Source file: components/text/falling-text.mdx
***
## Usage
All WebGL components render through [one shared canvas](/docs/getting-started/how-it-works). Add the `WebglProvider` once at the root of your app:
```tsx title="Root layout"
import { WebglProvider } from "@/components/webgl-provider/webgl-provider";
export default function RootLayout({ children }) {
return {children} ;
}
```
Letters near the cursor fall, bounce on the bottom of the screen, then fade back to their place.
The text is real DOM content, so any font and any CSS layout (like responsive columns) works.
```tsx
Enter your text here
```
***
## API
| Name | Type | Default | Description |
| ---------------- | -------------------------- | --------- | ------------------------------------------------------------------------------ |
| `children` | `string` | — | The text content. |
| `radius` | `number` | `80` | Distance in px around the cursor within which letters drop. |
| `gravity` | `number` | `510` | Downward acceleration in px/s². |
| `bounce` | `number` | `0.3` | Fraction of the fall speed kept on each floor bounce, from 0 (dead stop) to 1. |
| `respawnDelay` | `number` | `0.1` | Seconds a fallen letter rests on the floor before returning home. |
| `fadeDuration` | `number` | `0.05` | Seconds of the fade out and fade in when a letter returns home. |
| `explodeOnClick` | `boolean` | `true` | Blast nearby letters outward on click. |
| `webglEnabled` | `boolean` | `true` | Render through WebGL, or fall back to plain DOM text. |
| `zIndex` | `number` | `0` | Render order inside the shared canvas. |
| `pixelRatio` | `number` | `2` | Cap on the glyph atlas resolution. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
***
============================================================
## Text Roll
> Interactive rolling text effect on hover, mount and scroll.
Source file: components/text/text-roll.mdx
***
## Usage
Each letter rolls through its characters into place on hover, mount and scroll.
```tsx
Enter your text here
```
### Choose the wrapper element
Pass `render` to choose the wrapper element. Your props are merged with the component's, so it stays the right semantic tag while keeping the animation. Without `render` the text renders inside a ``.
```tsx
}>Enter your text here
```
***
## Notes
Each letter is wrapped in an `overflow-clip` span. Some descenders and ascenders (g, p, t, f) can get clipped. Increase `line-height` / `leading` on the parent if needed.
***
## API
| Name | Type | Default | Description |
| -------------- | ------------------------------------- | ----------- | ------------------------------------------------------------------------ |
| `children` | `string` | — | The text content to animate. |
| `axis` | `"x" \| "y"` | `"y"` | Axis along which letters roll. |
| `mode` | `"letters" \| "group"` | `"letters"` | Animate each letter independently on hover, or the entire group at once. |
| `direction` | `"forward" \| "backward" \| "random"` | `"forward"` | Roll direction. |
| `playOnScroll` | `boolean` | `true` | Trigger the animation when the element enters the viewport. |
| `playOnMount` | `boolean` | `true` | Trigger the animation on mount. |
| `playOnHover` | `boolean` | `true` | Trigger the animation on hover. |
| `stagger` | `number` | `0.01` | Delay in seconds between each letter's animation start. |
| `duration` | `number` | `0.8` | Duration of each letter's roll animation in seconds. |
| `cycles` | `number` | `1` | Number of times the letter rolls before settling. |
| `render` | `ReactElement \| function` | ` ` | Override the wrapper element. |
***
============================================================
## How it works
> Learn how the Atelier UI component system works with your coding agent.
Source file: getting-started/how-it-works.mdx
Atelier UI is a motion and React Three Fiber **component system**, built for [React](https://react.dev) and [Next.js](https://nextjs.org).
You paste one prompt into your coding agent and it installs the component with everything it depends on.
Nothing on this page needs to be set up by hand.
***
## Install with your agent
Copy the prompt under any component preview and paste it into **your coding agent**:
```text title="Prompt"
Add Atelier's Lens Media to my app.
If there is no components.json, run: npx shadcn@latest init -d
Then run: npx shadcn@latest add @atelier/lens-media
That writes the atelier-ui skill under .agents/skills and .claude/skills. Follow it.
Props: type="image" size={0.12} softness={0.5} aberration={0.18}
```
* The prompt includes the props you set on **the live preview**.
* `SKILL.md` tells your agent how to set up the component, and **every component installs it**.
* It installs to `.agents/skills` and `.claude/skills`, so **Claude Code, Codex, Cursor, Copilot and Gemini CLI** all pick it up. The two files are identical.
* Missing [Tailwind CSS v4](https://tailwindcss.com/docs/installation) **stops the install**, and your agent asks before adding it.
* Pro components install the same way once your key is in `.env.local`, see [license](/docs/getting-started/license).
***
## What the agent wires
Every component plugs into three shared systems, mounted once in your root layout:
```tsx title="app/layout.tsx"
export default function RootLayout({ children }) {
return (
{children}
)
}
```
### One WebGL provider
Every WebGL component renders into a single ``.
* One WebGL context, **not one per effect**
* Textures, shaders, and post-processing are shared
* Adding an effect doesn't add a context
For canvas options, see the [WebGL Provider](/docs/foundation/primitive/webgl-provider) reference.
### One Smooth Scroll provider
Smooth Scroll runs [Lenis](https://lenis.dev) on the same [Motion](https://motion.dev) loop that renders the canvas.
* Each frame updates the scroll position, then the page, then the canvas
* WebGL effects **synchronize with the DOM** at any scroll speed
For scroll options, see the [Smooth Scroll](/docs/foundation/primitive/smooth-scroll) reference.
### One transition system
`TransitionPage` wraps the route content and `TransitionLink` triggers the change.
* Back and forward **keep the previous scroll position**
* Page transitions are compatible with **WebGL components**
For transition options, see the [Page Transition](/docs/page-transition/nextjs/clip-transition) reference.
***
## Not using AI?
The prompt is one of three install paths on every component page. The other two are folded underneath it:
* **CLI install** runs a single `npx shadcn@latest add` command that pulls the component and its dependencies
* **Copy files manually** lists every file the component uses, so you install the dependencies and copy the source yourself
============================================================
## License
> Learn where your license key goes and what your agent writes with it.
Source file: getting-started/license.mdx
Most of the catalog is free and open source on [GitHub](https://github.com/whatisjery/atelier-ui), open to [contributions](/docs/getting-started/contribution).
Other components require a [one-time license](/login) that will also give you access to all upcoming features forever.
***
## Add your key
Put the key in a git-ignored `.env.local` at the root of your project:
```text title=".env.local"
ATELIER_PRO_KEY=
```
* **That's the whole setup:** your agent will handles the rest.
* **What's next?:** copy the prompt under the preview, same as a free component.
* **Your key is not exposed:** your agent reads it through `${ATELIER_PRO_KEY}` and never sees the value.
***
## The registry entry
You never have to write this yourself **unless you install manually** or set up [MCP](/docs/getting-started/mcp). Your agent adds the `@atelier` entry to `components.json` the first time you install a pro component:
```json title="components.json"
{
"registries": {
"@atelier": {
"url": "https://www.atelier-ui.com/r/{name}.json",
"headers": {
"Authorization": "Bearer ${ATELIER_PRO_KEY}"
}
}
}
}
```
Installing a free component writes the same entry without the header, as a plain string. **Replace that string with the object above**, or every pro install answers `401`.
An entry pointing at an empty `ATELIER_PRO_KEY` **stops every install**, free components included.
============================================================
## MCP
> Install Atelier UI components from your coding agent with the shadcn MCP server.
Source file: getting-started/mcp.mdx
Model Context Protocol (MCP) is an open standard that lets AI agents connect to external tools.
Atelier UI works with the [shadcn MCP server](https://ui.shadcn.com/docs/mcp), so your agent can search the catalog and install from a plain request instead of a copied prompt. None of this is needed to install a component.
***
## Add the @atelier registry
The server finds Atelier through your `components.json`. Installing with a prompt writes it for you, but MCP comes before your first install, so you set it up by hand.
**1.** Create a `components.json` if you don't already have one:
```bash title="Terminal"
npx shadcn@latest init -d
```
**2.** Add the `@atelier` entry:
```json title="components.json"
{
"registries": {
"@atelier": "https://www.atelier-ui.com/r/{name}.json"
}
}
```
That entry covers the whole catalog and every free component. Pro components use the same entry with a license header, see [license](/docs/getting-started/license).
***
## Connect your agent
***
## Learn more
For manual setup, or a client not listed above, see the [shadcn MCP documentation](https://ui.shadcn.com/docs/mcp).
============================================================
## Band Transition
> A covered route transition that sweeps staggered bands across the page.
Source file: page-transition/nextjs/band-transition.mdx
***
## Usage
Place `BandTransition` in the layout whose routes should transition, wrap the route content with `TransitionPage`, and use `TransitionLink` for the links that trigger it.
In the root layout, every route transitions.
In a [route group](https://nextjs.org/docs/app/api-reference/file-conventions/route-groups) layout, only the routes inside the group transition, with no change to the URL.
```tsx
import { BandTransition } from "@/components/band-transition/band-transition"
import { TransitionLink, TransitionPage } from "@/components/page-transition/page-transition"
export default function Layout({ children }: { children: React.ReactNode }) {
return (
Home
Work
About
{children}
)
}
```
***
## Note
During a transition, the bands overlay the page with `z-index: 70`, above everything else, including fixed navigation.
A fixed element with a z-index above 70, such as a persistent header, stays visible instead.
While a transition runs, `data-atelier-transitioning` is set on ``.
***
## How the transition works
Click a `TransitionLink` and the URL updates at once.
Stacked bands sweep in over the current page with staggered delays, the route commits behind the cover, and the bands keep traveling in the same direction to reveal the new page.
Back and forward play the same transition, with the previous scroll position restored behind the cover.
Assets in a link's `preload` prop start downloading on hover and again on click.
They are usually in cache before the cover is down.
When they are not, the reveal waits.
With `prefers-reduced-motion`, the whole transition is a short fade.
***
## API
### BandTransition
| Name | Type | Default | Description |
| ---------- | ----------- | ----------- | ------------------------------------------ |
| `children` | `ReactNode` | — | Content wrapped by the transition. |
| `color` | `string` | `"#151515"` | Color of the bands. |
| `bands` | `number` | `5` | Number of stacked bands. |
| `duration` | `number` | `0.6` | Duration of each band's sweep, in seconds. |
| `stagger` | `number` | `0.5` | Scales the per-band delay offsets. |
### TransitionLink
| Name | Type | Default | Description |
| --------- | ---------- | ------- | ----------------------------------------------------------------------- |
| `href` | `string` | — | Destination route. Required. |
| `preload` | `string[]` | — | Texture URLs of the destination page, downloaded on hover and on click. |
Every other prop of the Next.js Link component is forwarded.
### TransitionPage
| Name | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------------------------ |
| `children` | `ReactNode` | — | Route content animated by the active transition. |
### TransitionProvider
`BandTransition` is built on `TransitionProvider`, which runs the navigation sequence for a pair of cover animations.
Rendering it directly with custom `coverIn` and `coverOut` callbacks produces a custom transition.
| Name | Type | Default | Description |
| ---------- | ---------------------------------------- | ------- | ------------------------------------------------------------ |
| `children` | `ReactNode` | — | Content wrapped by the provider. |
| `coverIn` | `(reduced: boolean) => Promise` | — | Animation that covers the page before navigation. Required. |
| `coverOut` | `(reduced: boolean) => Promise` | — | Animation that reveals the page after navigation. Required. |
| `page` | `ComponentType<{ children: ReactNode }>` | — | Component rendered around route content by `TransitionPage`. |
***
============================================================
## Clip Transition
> A covered route transition that clips and scales the page, WebGL compatible.
Source file: page-transition/nextjs/clip-transition.mdx
***
## Usage
Place `ClipTransition` in the layout whose routes should transition, wrap the route content with `TransitionPage`, and use `TransitionLink` for the links that trigger it.
In the root layout, every route transitions.
In a [route group](https://nextjs.org/docs/app/api-reference/file-conventions/route-groups) layout, only the routes inside the group transition, with no change to the URL.
```tsx
import { ClipTransition } from "@/components/clip-transition/clip-transition"
import { TransitionLink, TransitionPage } from "@/components/page-transition/page-transition"
export default function Layout({ children }: { children: React.ReactNode }) {
return (
Home
Work
About
{children}
)
}
```
***
## Note
During the exit, a cover with `z-index: 60` and a band with `z-index: 70` overlay the page, above everything inside `TransitionPage`.
While a transition runs, `data-atelier-transitioning` is set on ``, and the nav in the example uses it to fade out.
A fixed element with a z-index above 70, such as a persistent header, stays visible instead.
***
## How the transition works
Click a `TransitionLink` and the URL updates at once.
The exit animation plays over the current page, the route commits behind the cover, and the enter animation reveals the new page.
Back and forward play the same transition, with the previous scroll position restored behind the cover.
When the [WebGL provider](/docs/foundation/primitive/webgl-provider) is present, its canvas clip follows the page during the exit, and WebGL media tracks the scaling page without `autoReflow`.
Assets in a link's `preload` prop start downloading on hover and again on click.
They are usually in cache before the cover is down.
When they are not, the reveal waits.
With `prefers-reduced-motion`, the whole transition is a short fade.
***
## API
### ClipTransition
| Name | Type | Default | Description |
| ------------- | ----------- | -------------- | ------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | Content wrapped by the transition. |
| `background` | `string` | `"white"` | Background color of the page while it clips and scales. |
| `coverColor` | `string` | `"whitesmoke"` | Color of the band that sweeps over the page. |
| `clipColor` | `string` | `"black"` | Color of the backdrop revealed around the clipped page. |
| `clipX` | `number` | `20` | Horizontal inset of the clipped page, in percent per side. |
| `clipY` | `number` | `10` | Vertical inset of the clipped page, in percent per side. |
| `scale` | `number` | `0.8` | Scale the page shrinks to while clipped. |
| `duration` | `number` | `1.3` | Duration of the main clip movement, in seconds. The other tracks scale with it. |
| `loadingSlot` | `ReactNode` | — | Centered over the cover while the page is covered, fading in after the cover arrives. |
### TransitionLink
| Name | Type | Default | Description |
| --------- | ---------- | ------- | ----------------------------------------------------------------------- |
| `href` | `string` | — | Destination route. Required. |
| `preload` | `string[]` | — | Texture URLs of the destination page, downloaded on hover and on click. |
Every other prop of the Next.js Link component is forwarded.
### TransitionPage
| Name | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------------------------ |
| `children` | `ReactNode` | — | Route content animated by the active transition. |
### TransitionProvider
`ClipTransition` is built on `TransitionProvider`, which runs the navigation sequence for a pair of cover animations.
Rendering it directly with custom `coverIn` and `coverOut` callbacks produces a custom transition.
| Name | Type | Default | Description |
| ---------- | ---------------------------------------- | ------- | ------------------------------------------------------------ |
| `children` | `ReactNode` | — | Content wrapped by the provider. |
| `coverIn` | `(reduced: boolean) => Promise` | — | Animation that covers the page before navigation. Required. |
| `coverOut` | `(reduced: boolean) => Promise` | — | Animation that reveals the page after navigation. Required. |
| `page` | `ComponentType<{ children: ReactNode }>` | — | Component rendered around route content by `TransitionPage`. |
***
============================================================
## Pixel Transition
> A covered route transition that pixelates the screen with a fixed grid of squares.
Source file: page-transition/nextjs/pixel-transition.mdx
***
## Usage
Place `PixelTransition` in the layout whose routes should transition, wrap the route content with `TransitionPage`, and use `TransitionLink` for the links that trigger it.
In the root layout, every route transitions.
In a [route group](https://nextjs.org/docs/app/api-reference/file-conventions/route-groups) layout, only the routes inside the group transition, with no change to the URL.
```tsx
import { TransitionLink, TransitionPage } from "@/components/page-transition/page-transition"
import { PixelTransition } from "@/components/pixel-transition/pixel-transition"
export default function Layout({ children }: { children: React.ReactNode }) {
return (
Home
Work
About
{children}
)
}
```
***
## Note
During a transition, the grid overlays the page with `z-index: 70`, above everything else, including fixed navigation.
A fixed element with a z-index above 70, such as a persistent header, stays visible instead.
While a transition runs, `data-atelier-transitioning` is set on ``.
***
## How the transition works
Click a `TransitionLink` and the URL updates at once.
A fixed grid of squares fills in shuffled order over the current page, the route commits behind the cover, and the squares clear in the same order to reveal the new page.
The order reshuffles on every navigation.
The grid count is fixed, so the effect looks the same at every viewport size.
Back and forward play the same transition, with the previous scroll position restored behind the cover.
Assets in a link's `preload` prop start downloading on hover and again on click.
They are usually in cache before the cover is down.
When they are not, the reveal waits.
With `prefers-reduced-motion`, the whole transition is a short fade.
***
## API
### PixelTransition
| Name | Type | Default | Description |
| ---------- | ----------- | ----------- | ----------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` | — | Content wrapped by the transition. |
| `color` | `string` | `"black"` | Color of the squares. |
| `hint` | `string` | `"#303030"` | Accent color a random fifth of the squares flash while appearing and disappearing. At rest the cover is a single color. |
| `pixels` | `number` | `10` | Number of grid columns. The row count derives from it. |
| `duration` | `number` | `0.5` | Time the grid takes to fill or clear, in seconds. |
### TransitionLink
| Name | Type | Default | Description |
| --------- | ---------- | ------- | ----------------------------------------------------------------------- |
| `href` | `string` | — | Destination route. Required. |
| `preload` | `string[]` | — | Texture URLs of the destination page, downloaded on hover and on click. |
Every other prop of the Next.js Link component is forwarded.
### TransitionPage
| Name | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------------------------ |
| `children` | `ReactNode` | — | Route content animated by the active transition. |
### TransitionProvider
`PixelTransition` is built on `TransitionProvider`, which runs the navigation sequence for a pair of cover animations.
Rendering it directly with custom `coverIn` and `coverOut` callbacks produces a custom transition.
| Name | Type | Default | Description |
| ---------- | ---------------------------------------- | ------- | ------------------------------------------------------------ |
| `children` | `ReactNode` | — | Content wrapped by the provider. |
| `coverIn` | `(reduced: boolean) => Promise` | — | Animation that covers the page before navigation. Required. |
| `coverOut` | `(reduced: boolean) => Promise` | — | Animation that reveals the page after navigation. Required. |
| `page` | `ComponentType<{ children: ReactNode }>` | — | Component rendered around route content by `TransitionPage`. |
***
============================================================
## Stripe Transition
> A covered route transition that wipes a staggered grid of stripes across the page.
Source file: page-transition/nextjs/stripe-transition.mdx
***
## Usage
Place `StripeTransition` in the layout whose routes should transition, wrap the route content with `TransitionPage`, and use `TransitionLink` for the links that trigger it.
In the root layout, every route transitions.
In a [route group](https://nextjs.org/docs/app/api-reference/file-conventions/route-groups) layout, only the routes inside the group transition, with no change to the URL.
```tsx
import { TransitionLink, TransitionPage } from "@/components/page-transition/page-transition"
import { StripeTransition } from "@/components/stripe-transition/stripe-transition"
export default function Layout({ children }: { children: React.ReactNode }) {
return (
Home
Work
About
{children}
)
}
```
***
## Note
During a transition, the stripes overlay the page with `z-index: 70`, above everything else, including fixed navigation.
A fixed element with a z-index above 70, such as a persistent header, stays visible instead.
While a transition runs, `data-atelier-transitioning` is set on ``.
***
## How the transition works
Click a `TransitionLink` and the URL updates at once.
A grid of vertical stripes, each split into segments, slides in over the current page with delays cascading across the grid.
The route commits behind the cover, and the segments keep traveling in the same direction to reveal the new page, with the cascade running in reverse order.
Back and forward play the same transition, with the previous scroll position restored behind the cover.
Assets in a link's `preload` prop start downloading on hover and again on click.
They are usually in cache before the cover is down.
When they are not, the reveal waits.
With `prefers-reduced-motion`, the whole transition is a short fade.
***
## API
### StripeTransition
| Name | Type | Default | Description |
| ---------- | ----------- | ----------- | ----------------------------------------------------------- |
| `children` | `ReactNode` | — | Content wrapped by the transition. |
| `color` | `string` | `"#151515"` | Color of the stripes. |
| `stripes` | `number` | `8` | Number of vertical stripes. |
| `segments` | `number` | `6` | Number of segments per stripe. |
| `duration` | `number` | `0.5` | Duration of each segment's slide, in seconds. |
| `stagger` | `number` | `0.025` | Delay between consecutive cells of the cascade, in seconds. |
### TransitionLink
| Name | Type | Default | Description |
| --------- | ---------- | ------- | ----------------------------------------------------------------------- |
| `href` | `string` | — | Destination route. Required. |
| `preload` | `string[]` | — | Texture URLs of the destination page, downloaded on hover and on click. |
Every other prop of the Next.js Link component is forwarded.
### TransitionPage
| Name | Type | Default | Description |
| ---------- | ----------- | ------- | ------------------------------------------------ |
| `children` | `ReactNode` | — | Route content animated by the active transition. |
### TransitionProvider
`StripeTransition` is built on `TransitionProvider`, which runs the navigation sequence for a pair of cover animations.
Rendering it directly with custom `coverIn` and `coverOut` callbacks produces a custom transition.
| Name | Type | Default | Description |
| ---------- | ---------------------------------------- | ------- | ------------------------------------------------------------ |
| `children` | `ReactNode` | — | Content wrapped by the provider. |
| `coverIn` | `(reduced: boolean) => Promise` | — | Animation that covers the page before navigation. Required. |
| `coverOut` | `(reduced: boolean) => Promise` | — | Animation that reveals the page after navigation. Required. |
| `page` | `ComponentType<{ children: ReactNode }>` | — | Component rendered around route content by `TransitionPage`. |
***