A long scroll of cards where the images drift slower than the copy. Keep the speeds small so the depth stays quiet.

Token ready
Quiet surfaces that still feel alive when you move through them.

Soft depth
Images lag behind the scroll so the stack feels layered, not flat.

Keep it tight
Small speed values stay calm. Push them only when the moment needs it.

Ship the cut
A long frame of cards, each with its own slow image drift.

Stay composed
Scroll the list. Watch the pictures move a little slower than the copy.
1
0.45
tsx
import { Parallax, ParallaxLayer } from "@/components/motion/parallax"const CARDS = [ { "title": "Token ready", "body": "Quiet surfaces that still feel alive when you move through them.", "image": "/assets/brand/demos/attachments/attachment-1.png" }, { "title": "Soft depth", "body": "Images lag behind the scroll so the stack feels layered, not flat.", "image": "/assets/brand/demos/attachments/attachment-2.png" }, { "title": "Keep it tight", "body": "Small speed values stay calm. Push them only when the moment needs it.", "image": "/assets/brand/demos/attachments/attachment-3.png" }, { "title": "Ship the cut", "body": "A long frame of cards, each with its own slow image drift.", "image": "/assets/brand/demos/attachments/attachment-4.png" }, { "title": "Stay composed", "body": "Scroll the list. Watch the pictures move a little slower than the copy.", "image": "/assets/brand/demos/attachments/attachment-5.png" }]export function Example() { return ( <Parallax strength={1} className="h-[38rem] w-full" > <div className="mx-auto flex w-full max-w-[300px] flex-col gap-14 py-12"> {CARDS.map((card) => ( <article key={card.title} className="overflow-hidden rounded-xl border border-border bg-background" > <div className="relative aspect-[4/3] overflow-hidden"> <ParallaxLayer speed={0.45} className="absolute inset-0" > <img src={card.image} alt="" className="size-full object-cover" /> </ParallaxLayer> </div> <div className="px-3.5 py-3.5"> <h3 className="text-sm font-medium tracking-wide"> {card.title} </h3> <p className="mt-1 text-xs tracking-wide text-muted-foreground"> {card.body} </p> </div> </article> ))} </div> </Parallax> )}Installation
npx @arctis-sh/@arctis-sh/ui@latest add parallaxUsage
import { Parallax, ParallaxLayer } from "@/components/motion/parallax"<Parallax strength={1} className="h-96 w-full"> <div className="flex flex-col gap-6 px-4 py-8"> <article className="overflow-hidden rounded-xl border"> <div className="relative aspect-[16/10] overflow-hidden"> <ParallaxLayer speed={0.45} className="absolute inset-0"> <img src="/photo.jpg" alt="" className="size-full object-cover" /> </ParallaxLayer> </div> <div className="px-4 py-4"> <h3>Token ready</h3> <p>Images lag behind the scroll.</p> </div> </article> </div></Parallax>API Reference
Parallax
| Prop | Type | Default |
|---|---|---|
| strength | number | 1 |
| className | string | — |
| children | ReactNode | — |
Also accepts the other native div attributes. Scroll happens on this element.
ParallaxLayer
| Prop | Type | Default |
|---|---|---|
| speed | number | 0.35 |
| className | string | — |
| children | ReactNode | — |
Put the layer inside an overflow-hidden frame. The layer scales itself so the image always covers while it drifts.