A progressive blur along one edge of a scrolling area or image: stacked layers of backdrop blur that fade from sharp to blurred, so content softens as it nears the edge. Pure decoration: it ignores the pointer and is hidden from assistive technology.
Optional, and kept apart.
Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.
import { SelvedgeBlur } from "@rdloom/react";
const lines = ["Quarterly review notes", "Invoices sent this week", "Stock reserved for orders", "Payments waiting on approval", "Refunds issued today", "New accounts created", "Support tickets closed"];
// The blur sits on the bottom edge of a positioned parent and softens what runs under it.
export default function SelvedgeBlurBasicExample() {
return (
<div className="flex w-full justify-center">
<div className="relative h-48 w-72 overflow-hidden rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)] p-4">
<ul className="m-0 flex list-none flex-col gap-3 p-0 text-sm">
{lines.map((line) => (
<li key={line}>{line}</li>
))}
</ul>
<SelvedgeBlur position="bottom" />
</div>
</div>
);
}Installation
npx rdloom add selvedge-blurCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.
It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/selvedge-blur.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { SelvedgeBlur } from "@rdloom/react";
<div className="flex w-full justify-center">
<div className="relative h-48 w-72 overflow-hidden rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)] p-4">
<ul className="m-0 flex list-none flex-col gap-3 p-0 text-sm">
{lines.map((line) => (
<li key={line}>{line}</li>
))}
</ul>
<SelvedgeBlur position="bottom" />
</div>
</div>Scroll edges
import { SelvedgeBlur } from "@rdloom/react";
const items = Array.from({ length: 14 }, (_, i) => `Activity entry ${i + 1}`);
// Blur both ends of a scrolling list. The list stays fully scrollable and clickable underneath.
export default function SelvedgeBlurScrollEdgesExample() {
return (
<div className="flex w-full justify-center">
<div className="relative w-72 rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)]">
<div tabIndex={0} aria-label="Recent activity" role="region" className="h-48 overflow-auto p-4 outline-none focus-visible:ring-2 focus-visible:ring-[var(--rd-color-focus-ring)]">
<ul className="m-0 flex list-none flex-col gap-3 p-0 text-sm">
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
<SelvedgeBlur position="top" size="20%" strength={6} />
<SelvedgeBlur position="bottom" size="20%" strength={6} />
</div>
</div>
);
}Horizontal
import { SelvedgeBlur } from "@rdloom/react";
const tags = ["Design", "Build", "Ship", "Measure", "Learn", "Repeat", "Review", "Plan"];
// start and end follow the reading direction, so this also works in right-to-left layouts.
export default function SelvedgeBlurHorizontalExample() {
return (
<div className="flex w-full justify-center">
<div className="relative w-80 overflow-hidden rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)] p-4">
<div className="flex w-[34rem] gap-3 text-sm">
{tags.map((tag) => (
<span key={tag} className="rounded-full border border-[var(--rd-color-border-default)] px-3 py-1">
{tag}
</span>
))}
</div>
<SelvedgeBlur position="start" size="20%" strength={6} />
<SelvedgeBlur position="end" size="20%" strength={6} />
</div>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
positionWhich edge of the parent is blurred. start and end follow the reading direction. | "top" | "bottom" | "start" | "end" | "bottom" |
strengthBlur radius in px at the very edge. | number | 8 |
sizeHow far the blur reaches in from the edge, any CSS length. | string | "30%" |
layersNumber of stacked blur layers. More layers give a smoother fade. | number | 5 |
Accessibility
Role presentation, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
Not interactive.
Screen readers announce
- Not announced
What your code must do
- Decoration only: aria-hidden, never focusable, and pointer-events none so the content below stays clickable and selectable
- It does not move, so there is nothing to pause; the blur is the same for visitors who prefer reduced motion
- It only softens what is under it: do not put content the reader needs to read inside the blurred zone
- In forced-colors (high contrast) mode the blur is removed, so text is never obscured
- Place it inside a positioned parent (position: relative), above the content in stacking order
Guidelines
Use it when
- The edge of a scrolling list or feed that continues off screen
- The bottom of a card with an image behind text
Avoid it when
- Over text that must stay readable
- Where backdrop-filter is not wanted for performance reasons on long pages
Don't
- Covering interactive controls, which become hard to see
- Using a large strength with a tiny size, which looks like a hard band