---
title: "Parallax"
description: "Parallax classes and the parallax config ranges."
requested_language: sk
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/sk/next/styling/parallax
version: unreleased (main)
docs_index: https://docs.systhema.app/sk/next/llms.txt
---
> This page isn't translated yet. Showing English.


Parallax moves media a little slower than the page as it scrolls, inside a frame that crops it. Put `parallax` on the media and `parallax-wrapper` on its frame; [`ParallaxListener`](https://docs.systhema.app/sk/next/components/listeners.md#parallaxlistener) (or `initParallax()` in the [vanilla JS bundle](https://docs.systhema.app/sk/next/styling/vanilla-js.md)) writes the transforms. The [`Image`](https://docs.systhema.app/sk/next/components/image.md) and [`Video`](https://docs.systhema.app/sk/next/components/video.md) components set up both classes with their `parallax` prop.

## Quick reference

<!-- generated:utilities parallax -->

| Class              | Styles                                                                                                                                                                                            |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `parallax`         | `& { aspect-ratio: inherit; width: 100%; height: 100%; transform: translateY(-8.33%) scale(1.1666); object-fit: cover; }`<br>`@media (prefers-reduced-motion: reduce) { & { transform: none; } }` |
| `parallax-wrapper` | `pointer-events: none;`<br>`position: relative;`<br>`overflow: hidden;`                                                                                                                           |

<!-- /generated -->

| Modifier            | Effect                                                                                                              |
| ------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `parallax--big`     | Uses `translateRangeBig` instead of `translateRange`. Goes on the media or on its wrapper.                          |
| `parallax--noscale` | Keeps the media at its natural size (scale 1). For media that no wrapper crops, such as a free-floating decoration. |

## Basic usage

Scroll inside the preview: the image travels against the page within its frame. The demo scrolls inside its own frame because the engine follows the scroll position of the document it runs in.

```tsx preview iframe height=420 title="Parallax image"
import { Image } from '@systhemaui/next'

export default function Demo() {
  return (
    // The frame stays 420px tall; the absolutely positioned page inside it is
    // taller, so the demo document scrolls and the listener has something to follow.
    <div className="h-[420px] w-full">
      <div className="absolute inset-x-0 top-0 flex flex-col gap-10 px-8 pt-24 pb-64">
        <p className="text-h4 color-heading">Scroll inside this preview</p>
        <Image
          src="/demo-assets/mountains.webp"
          alt="Pastel mountain ridges"
          width={1920}
          height={1080}
          aspectRatio="21/9"
          parallax
          disableAnimation
          className="rounded-lg"
        />
        <p className="max-w-[60ch] color-body">
          The image is scaled up slightly and shifted as its frame crosses the viewport, so its
          edges never show.
        </p>
        <Image
          src="/demo-assets/coast.webp"
          alt="A coastline at low tide"
          width={1800}
          height={1200}
          aspectRatio="16/9"
          parallax
          disableAnimation
          className="rounded-lg"
        />
      </div>
    </div>
  )
}
```

With `parallax`, `Image` renders the image inside a `figure.parallax-wrapper` that carries the aspect ratio, and puts `className` and the animation classes on that wrapper. Pass `parallaxNoWrapper` to render the bare image when you provide the frame yourself.

In markup, the frame needs a size (here from an aspect ratio) and the media fills it:

```html
<figure class="parallax-wrapper aspect-21/9">
  <img class="parallax" src="/images/mountains.webp" alt="Pastel mountain ridges" />
</figure>
```

`.parallax` makes the media fill the wrapper with `object-fit: cover` and gives it a starting transform, so it is already in place before the first scroll event. `.parallax-wrapper` clips it and ignores pointer events.

## How the motion is computed

While the element crosses the viewport, the engine maps its progress from entering at the bottom (0%) to leaving at the top (100%) onto the translate range, and writes `translateY(<t>%) scale(<scale>)` as an inline style. Before the element enters it sits at the start of the range, after it leaves at the end. Updates are batched to one per animation frame.

## Reduced motion

Under `prefers-reduced-motion: reduce` the engine writes no transform at all, and the stylesheet's own reduced-motion rule sets `transform: none`, so the media simply fills its wrapper. Both engines (`ParallaxListener` and the vanilla `initParallax()`) follow the preference live: turning it on mid-visit clears the inline transforms, turning it off starts the motion again, without a reload.

## Responsive and state variants

The parallax classes have no breakpoint variants: the engine reads the class, not the media query. To have parallax on large screens only, render two images, or toggle the `parallax` prop from your own media-query state.

## Customizing

### Configuration

The ranges and the scale live in `systhema.config`:

```ts title="systhema.config.ts"
import type { SysthemaConfig } from '@systhemaui/core'

const config: SysthemaConfig = {
  parallax: {
    translateRange: { from: -16, to: 16 },
    translateRangeBig: { from: -32, to: 32 },
    scale: 1.1666,
  },
}

export default config
```

These are the defaults. The two knobs are tied: a translate of `t` percent needs `scale >= 1 + |t| / 50`, or the media falls short of its wrapper at the extremes. Systhema takes the scale as configured and clamps both ranges to what it covers, so the shipped `1.1666` allows about 8.3% of travel either way, for `parallax--big` as much as for the default. Raise `scale` to open up the ranges: `scale: 1.32` covers ±16%, `scale: 1.64` covers ±32%.

`parallax--noscale` uses scale 1 with the clamped default range, so inside a `parallax-wrapper` it uncovers the wrapper's edges as it moves. Use it on media that isn't cropped.

## Related

- [Listeners](https://docs.systhema.app/sk/next/components/listeners.md#parallaxlistener)
- [Image](https://docs.systhema.app/sk/next/components/image.md)
- [Motion](https://docs.systhema.app/sk/next/concepts/motion.md)
- [systhema.config reference](https://docs.systhema.app/sk/next/reference/config.md#parallax)
