Skip to content
Corsair UI
Browse the docs

MotionNo. 79 of 109

Parallax

Drifts content at a different speed from the page as it scrolls past, with CSS scroll-driven animations and no JavaScript.

Installation

pnpm dlx shadcn@latest add @corsair-ui/parallax

No setup needed: the CLI finds @corsair-ui in the shadcn registry directory. The files land in your project as source: edit them like your own code. More in Installation.

Also adds
utils
npm
@radix-ui/react-slotclsxtailwind-merge

Examples

Depth layers

Scroll inside the box. Each ring drifts at its own speed; the scroll container here is the box, so its timeline is too. The rings are clipped with overflow-clip: overflow-hidden would make a scroll container of its own, and that one never scrolls.

API

PropTypeDefault
offset

Drift each way in px; negative values run ahead of the scroll.

number40
asChild

Move the child instead of a wrapper.

booleanfalse

Accessibility

  • Purely visual: nothing about the content or its order changes.
  • Browsers without scroll-driven animations keep it still.
  • With prefers-reduced-motion it holds still and shows the finished state.

Compatibility

Tailwind CSS
3.4 and 4, both checked in CI
React
19
Rendering
Works in server components
Motion
Respects prefers-reduced-motion

Source

1 file, installed as source you own
parallax.tsxView on GitHub
import { Slot } from "@radix-ui/react-slot";
import type { ComponentProps, CSSProperties } from "react";

import { cn } from "@/registry/default/lib/utils";

interface ParallaxProps extends ComponentProps<"div"> {
  /** Move the single child element instead of wrapping it in a div. */
  asChild?: boolean;
  /**
   * How far, in px, the element drifts each way while it crosses the
   * viewport. Positive values lag behind the scroll, negative values run ahead.
   */
  offset?: number;
}

/**
 * Drifts content at a different speed from the page while it scrolls past.
 * It runs on CSS scroll-driven animations, so there is no JavaScript and no
 * scroll listener; browsers without them, and `prefers-reduced-motion`, keep
 * the content still. Works in server components.
 *
 * It follows the nearest scroll container, and `overflow: hidden` makes one
 * that never scrolls, so the content stays put. Clip a wrapper with
 * `overflow: clip` (`overflow-clip`) instead.
 */
function Parallax({ asChild = false, offset = 40, className, style, ...props }: ParallaxProps) {
  const Comp = asChild ? Slot : "div";
  return (
    <Comp
      data-slot="parallax"
      className={cn(
        "supports-[animation-timeline:view()]:animate-parallax motion-reduce:!animate-none",
        className
      )}
      style={
        {
          "--parallax-offset": `${offset}px`,
          animationTimeline: "view()",
          animationRange: "cover 0% cover 100%",
          ...style,
        } as CSSProperties
      }
      {...props}
    />
  );
}

export { Parallax, type ParallaxProps };