Progressive blur
CSS-driven backdrop blur that grows toward an edge, with masked layers and interruptible staggered transitions.
Examples
Scroll edges
ScrollArea owns overflow and positions the overlays. The viewport fades before sibling blur layers sample the visible surface, keeping text weight stable and scrollbars sharp. Toggle either effect without resetting the scroll position. Reduced motion removes transitions; reduced transparency and forced colors remove both effects.
Right-to-left scrolling
Inline edges follow the scroll direction. Keyboard focus clears the effects so controls stay readable. Pointer input passes through the decorative layers. With Next.js/Turbopack, set experimental.lightningCssFeatures.exclude to ["dir-selector"] to preserve native :dir() matching instead of approximating direction from language.
Composition
Decorative edge overlay
Position the container relatively and place the blur over its content.
- <div>
- background content
- <ProgressiveBlur />
Installation
First install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/ui/progressive-blur.tsx.
npx shadcn@latest add https://control-ui.dev/r/progressive-blur.jsonRaw code
Primary installed source
import type { ComponentProps, CSSProperties } from "react";import type { ProgressiveBlurKnobStyle } from "@/components/control-ui/knob-contracts/progressive-blur-knobs";export type ProgressiveBlurSide = "top" | "bottom" | "inline-start" | "inline-end";export type ProgressiveBlurProps = Omit<ComponentProps<"div">, "children" | "dangerouslySetInnerHTML" | "style" | "tabIndex"> & { side?: ProgressiveBlurSide; visible?: boolean; style?: CSSProperties & ProgressiveBlurKnobStyle;};export function ProgressiveBlur({ side = "bottom", visible = true, ...props }: ProgressiveBlurProps) { return ( <div {...props} data-control-ui="progressive-blur" data-control-family="progressive-blur" data-slot="root" data-side={side} data-visible={visible || undefined} aria-hidden="true" > <span data-control-ui="progressive-blur" data-control-family="progressive-blur" data-slot="layer" /> <span data-control-ui="progressive-blur" data-control-family="progressive-blur" data-slot="layer" /> <span data-control-ui="progressive-blur" data-control-family="progressive-blur" data-slot="layer" /> <span data-control-ui="progressive-blur" data-control-family="progressive-blur" data-slot="layer" /> <span data-control-ui="progressive-blur" data-control-family="progressive-blur" data-slot="layer" /> <span data-control-ui="progressive-blur" data-control-family="progressive-blur" data-slot="layer" /> </div> );}Knobs
Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.
--cui-progressive-blur-* · 5 knobsHow the cascade resolves--cui-progressive-blur-size<length-percentage>3rem--cui-progressive-blur-backdrop-blur<length>16px--cui-progressive-blur-fade-size<length-percentage>0.5rem--cui-progressive-blur-transition-duration<time>var(--duration-base)--cui-progressive-blur-transition-delay<time>calc(var(--cui-progressive-blur-transition-duration) * 0.06)