A scroll region whose content dissolves into a blur at the edges, built from masked backdrop-filter layers.
New
Fade edges
Scroll the specimen. The top edge fades the content in and the bottom edge dissolves it into a blur, one slice at a time.
Section 01, 2 columns
Reading the specimen
Type is read in long measures, so the specimen sets a narrow column and lets the line breathe. The blur at the edges should make the top of the next section arrive rather than appear.
Section 02, 3 columns
Weight and measure
Four weights share one rhythm. The optical sizes step down through the set, and the tracking opens as the measure gets smaller.
Section 03, 1 column
A note on colour
The surface tone carries the specimen and the ink does the reading. Nothing here needs a border to be legible, so the space between sections is the only line on the page.
Section 04, 2 columns
Spacing at the baseline
An 8pt grid with a 1.5 line height. The spacing between sections is double the spacing inside one, and the eye uses that to find the joins.
Section 05, 3 columns
The last measure
The final section is the one that scrolls under the blur, so it is the one worth reading twice. Everything above it stays sharp until the ramp reaches it.
The specimen starts sharp at the top edge.
Live previewUse keyboard to interact
Implementation
1"use client";23import {4 useMemo,5 type CSSProperties,6 type HTMLAttributes,7 type ReactNode,8 type Ref,9} from "react";1011export type GradualBlurProps = Omit<12 HTMLAttributes<HTMLDivElement>,13 "children"14> & {15 /** The scrollable content. */16 children: ReactNode;17 /** Accessible name for the scroll region. */18 label?: string;19 /** The strongest blur in the ramp, in pixels. */20 blur?: number;21 /** How many slices the ramp is built from. */22 layers?: number;23 /** The height of the edge band, in pixels. */24 bandSize?: number;25 /** A ref to the scrollable viewport, for callers that drive the scroll. */26 viewportRef?: Ref<HTMLDivElement>;27 /** Additional class name for the root. */28 className?: string;