Progressive Blur
A progressive blur mask at the edge of scrolling content — the closer to the edge, the blurrier — so content fades in and out smoothly.
A stacked backdrop-filter mask: several blur layers are each cut into one window, doubled layer by layer, and offset toward the edge, accumulating into a "blurrier the closer to the edge" progression. Just drop it at the edge of any relative container.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add progressive-blur.enProgressive Blur is a React progressive blur mask component built with Tailwind CSS. The effect comes entirely from stacked CSS backdrop-filter and mask-image layers, with no animation library and no canvas. Use it at the edges of scrolling lists, marquees or image walls so content softens before it leaves the viewport.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ProgressiveBlur/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- Top and bottom edges of a long list or chat thread, hinting that there is more to scroll
- Left and right ends of a horizontally scrolling card row or customer logo marquee
- Under a fixed top navbar, so content scrolling past blurs beneath it
- The bottom of an image wall or portfolio grid, ahead of a "Load more" button
- Not a good fit: if you want content to sharpen into view as it scrolls in, use an entrance component such as Blur Reveal Text
Installation
npx webberui@latest add https://webberui.com/r/progressive-blur.en.jsonOr, once registries are configured in components.json, install it as @webberui/progressive-blur.
Usage
Put ProgressiveBlur inside the relative container you want to mask and name the edge it hugs. It is a pointer-events-none purely visual overlay, so it never blocks scrolling or clicking underneath.
import { ProgressiveBlur } from "@/components/ui/progressive-blur";
<div className="relative h-80 overflow-auto">
{/* your scrolling content */}
<ProgressiveBlur side="top" size={80} />
<ProgressiveBlur side="bottom" size={80} />
</div>side can be "top", "bottom", "left", or "right", and one container can carry several edges at once.
Examples
Fade both ends of a horizontal card row
Set side to "left" and "right"; size then controls the width.
import { ProgressiveBlur } from "@/components/ui/progressive-blur";
<div className="relative">
<div className="flex gap-4 overflow-x-auto px-6 py-4">
{["Plan comparison", "Customer feedback", "Integrations", "Changelog", "Security"].map((label) => (
<div key={label} className="w-56 shrink-0 rounded-xl border p-4 text-sm">
{label}
</div>
))}
</div>
<ProgressiveBlur side="left" size={48} />
<ProgressiveBlur side="right" size={48} />
</div>Blend into the background with tint
Pass tint when backdrop-filter is unavailable, or when you want the edge to melt into the page background. It accepts any CSS color string; usually you set it to the container background color.
<div className="relative h-96 overflow-auto bg-white">
<ul className="space-y-2 p-4">{/* order list */}</ul>
<ProgressiveBlur
side="bottom"
size={120}
tint="#ffffff"
/>
</div>Heavier blur with a percentage size
size can also be a string such as "30%", so the mask follows the container height. Raise blur or layers to make the outermost layer blurrier.
<div className="relative aspect-video overflow-hidden rounded-2xl">
<img src="/screenshots/dashboard.png" alt="Dashboard screenshot" className="size-full object-cover" />
<ProgressiveBlur side="bottom" size="30%" layers={8} blur={0.75} />
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
side | "top" | "bottom" | "left" | "right" | "bottom" | The edge the blur hugs and deepens toward |
size | number | string | 96 | Thickness of the mask along that edge (a number means px) |
layers | number | 6 | Number of stacked layers; the more layers, the smoother the gradation |
blur | number | 0.5 | Blur radius of the first layer (px), doubling with each layer after it |
tint | string | — | Fades from transparent into this color along the edge, which can carry on into the background and cover the content |
className | string | — | Forwarded to the overlay container |
How it works
- Each layer occupies a window
100 / layerswide, with adjacent windows deliberately overlapping to eliminate hard-edged banding; the blur radius doubles layer by layer, so the layer closest to the edge is the blurriest. - With
blurat0.5andlayersat6, the outermost layer blurs by about16px; raiseblurorlayersif you want it heavier. - The overlay is
pointer-events-none, so the content underneath stays scrollable and clickable. - If the browser does not support
backdrop-filter, this layer silently does nothing without affecting the content underneath; when you still need to cover the content in that case, pair it withtintto supply a gradient backing color.
FAQ
Does it work with the Next.js App Router?
Yes. The file already starts with "use client", so import it from a Server Component page as usual. The component never touches window, so the server output matches the client.
Why do I not see any effect?
ProgressiveBlur is absolutely positioned, so its parent needs relative (or any non-static position). It also blurs whatever is behind it, so over a flat single-color background there is nothing visible to blur.
Does it affect performance?
Each layer is one element with backdrop-filter, and layers defaults to 6. There is no animation or JavaScript loop, but on low-end devices with a large area you can lower layers and shrink size.
What do I need to install?
Only clsx and tailwind-merge, which the cn utility uses. If you prefer not to use the CLI, switch to the Manual tab in the installation section and copy the source.