Progressive blur
Free · MITBlur that grows stronger toward the edge of a scroll container, built from stacked backdrop-filter layers; shown on a scrolling bookings list.
Installation
$ npx shadcn@latest add https://goodui.dev/r/progressive-blur.jsonThe file lands in components/goodui/progressive-blur.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/progressive-blur works too.
Usage
import { ProgressiveBlur } from "@/components/goodui/progressive-blur"
const bookings = [
{ time: "09:00", client: "Maya Chen", service: "Deep clean · 3 bed" },
{ time: "11:30", client: "Leo Park", service: "Move-out clean" },
{ time: "13:00", client: "Ana Silva", service: "Windows" },
{ time: "15:30", client: "Sam Ortiz", service: "Office · weekly" },
{ time: "17:00", client: "Iris Novak", service: "Carpet steam" },
]
export function TodayList() {
return (
<div className="relative h-72 overflow-hidden rounded-xl border bg-card">
<ul className="h-full divide-y overflow-y-auto pb-16">
{bookings.map((b) => (
<li key={b.time} className="flex gap-4 px-4 py-3 text-sm">
<span className="w-12 font-mono text-muted-foreground">{b.time}</span>
<span className="font-medium">{b.client}</span>
<span className="ms-auto text-muted-foreground">{b.service}</span>
</li>
))}
</ul>
<ProgressiveBlur side="bottom" size={80} fadeColor="var(--card)" />
</div>
)
}When to use
- The bottom (or top) edge of a scrolling list, so items soften away instead of being cut off: bookings, messages, notifications.
- Under a sticky header or floating toolbar, where content should blur as it scrolls beneath.
- Image cards with a caption at the bottom, where a blur band keeps the text readable over the photo.
- Hinting that a horizontal row continues: a plain mask fade is cheaper; marquee has one built in for moving rows.
- Long, fast-scrolling pages on low-end phones: stacked backdrop-filter layers cost GPU time. Use a gradient fade (fadeColor alone, or a CSS mask) there.
- Hiding content for privacy or paywalls: blur is not access control.
| If you need | Use |
|---|---|
| Bottom of a scroll list | side="bottom" (default) |
| Under a sticky header | side="top" |
| Also fade into the card color | fadeColor="var(--card)" |
| Smoother | layers={8} |
| Cheaper | layers={3} |
| Fixed height band | size={96} (px) or size="6rem" |
Examples
Blur under a sticky header
<div className="relative h-[480px] overflow-hidden">
<header className="absolute inset-x-0 top-0 z-20 h-14 px-4">Messages</header>
<ProgressiveBlur side="top" size={88} blur={10} />
<div className="h-full overflow-y-auto pt-14">{/* messages */}</div>
</div>API reference
| Prop | Type | Default |
|---|---|---|
| side Edge the blur sits on; it grows toward that edge. | "top" | "bottom" | "left" | "right" | "bottom" |
| size Size of the blurred band (any CSS length). | string | number | "35%" |
| blur Blur in px at the very edge. | number | 8 |
| layers Number of stacked layers; more is smoother, fewer is cheaper. | number | 6 |
| fadeColor Also fade to this color at the edge (e.g. "var(--card)"). Off when undefined. | string | — |
Also accepts every <div> attribute.
Accessibility
- The blur is aria-hidden and has pointer-events: none, so content under it stays clickable and scrollable.
- Leave scroll padding (pb-16 or similar) so the last item can scroll fully out from under the blur.
- Give the scroll container tabIndex={0} and an aria-label (unless it already holds focusable items), so keyboard users can scroll it.
Motion
- No animation: the blur is static and only changes as content scrolls beneath it, so reduced motion needs no special handling.
- Each layer is one backdrop-filter element; the blur doubles per layer up to `blur` px (8). Fewer layers are cheaper, more are smoother (default 6).
- Keep it to a few instances per screen, especially on mobile.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Place it inside a relative (and usually overflow-hidden) wrapper, as a sibling of the scroll container, not inside the scrolling element.
- size accepts a number (px) or any CSS length; blur is px at the very edge.
- side uses physical edges (top, bottom, left, right); it does not flip in RTL.
- fadeColor should match the surface behind the list (var(--card), var(--background)).
- It renders at z-10; raise sticky headers above it if they should stay sharp.
FAQ
Why use several layers instead of one blur?
A single backdrop-filter has one strength. Stacking masked layers whose blur doubles toward the edge makes the blur grow smoothly.
Browsers without backdrop-filter?
The layers then do nothing; add fadeColor so the edge still fades.
Related
An endless horizontal or vertical marquee with faded edges that pauses on hover, shown with customer logos, testimonials and a live bookings ticker.
Eight surfaces (elevated, glass, gradient border, neo, inset…) with shadow, padding, radius and accent stripes, plus hover layers (lift, 3D tilt, pointer spotlight, glow, scale) and a looping border beam.
Notification stack where new items spring in from the top, older ones glide down, slightly dimmed in the depth variant, and items beyond the limit blur away.
A soft spotlight and a lit border follow the pointer across a card; in a group, neighboring cards' borders light up too.