registry:ui · NativeHTML
progressive blur — content scrolls UNDER a pinned head and diffuses
The same-layer-sticky practice made literal: a list scrolling inside its own container gets a pinned head, and whatever passes beneath it diffuses instead of slicing. Stacked backdrop-filter bands, each masked to its own gradient rung, ramp blur from ~clear at the inner edge to the full stack at the scrollport edge. Zero JS: the pin is a sticky h-0 root (absolutely positioned overlays scroll away — probed), and the reveal rides the CSS scroll timeline, so nothing blurs while the list rests at the top.
progressive blur
THE docs-rail composition (left): the head pins at the edge, the list scrolls UNDER it through the progressive band — scroll the card and watch the entries diffuse beneath the pinned title/filter. Right: the always-painted static band at the bottom edge, the Magic UI default posture.
sticky head · reveal scroll — the rail law
components
- entry-01
- entry-02
- entry-03
- entry-04
- entry-05
- entry-06
- entry-07
- entry-08
- entry-09
- entry-10
- entry-11
- entry-12
- entry-13
- entry-14
- entry-15
- entry-16
- entry-17
- entry-18
position bottom · reveal static
- entry-01
- entry-02
- entry-03
- entry-04
- entry-05
- entry-06
- entry-07
- entry-08
- entry-09
- entry-10
- entry-11
- entry-12
- entry-13
- entry-14
- entry-15
- entry-16
- entry-17
- entry-18
<script lang="ts">
import ProgressiveBlur from '@ui/progressive-blur';
</script>
<!-- an EARLY child of the scroll container — the sticky h-0 root
pins to the scrollport edge through the whole scroll range -->
<div class="relative h-72 overflow-auto">
<ProgressiveBlur position="top" reveal="scroll" class="z-[5]" />
<!-- the sticky head pins ABOVE the band (z-10); the list scrolls
under both through the progressive fade -->
<div class="sticky top-0 z-10">…title + filter…</div>
<!-- …the scrolling list… -->
</div>the technique
How the ladder works
Each layer blurs everything painted beneath it — including the earlier layers — so stacking masked runs compounds. With the default eight levels, layer i owns the rung between i·12.5% and (i+3)·12.5% of the band: near the inner edge only the half-pixel layer contributes; at the scrollport edge the whole 0.5→64px stack is active. The reveal animates opacity on EACH LAYER (an opacity on a common ancestor would create a backdrop root and the layers would sample nothing beneath themselves), driven by scroll(nearest) over --jx-pblur-ramp (72px default).
/* layer i of n — the whole ladder is this one formula */
step = 100 / n;
mask: linear-gradient(to edge,
transparent i·step, opaque (i+1)·step,
opaque (i+2)·step, transparent (i+3)·step);
backdrop-filter: blur(levels[i]px);types
Types
The band hangs from any scrollport edge; the pin dialect picks the positioning law; reveal chooses resting paint or scroll-in fade.
The band hangs into the viewport from that edge via a sticky h-0 root.
Two pinned roots — keeps both edges progressive instead of one uniform-blur element.
The inline-axis edges of a horizontal-overflow strip; inline renders the start+end pair as two roots.
sticky = the zero-height root inside a scroller (any edge); grid = a position-free item of the host one-cell grid — inline edges only, else it is a compile error.
The outer share (0–100) holding the ladder peak — the ramp compresses inboard for strips with a blank control lane at the clip edge.
static paints always (Magic UI parity); scroll fades the ladder in with the nearest scroller — inline edges keep the static law.
usage
Usage
Mount the band as an EARLY child of the scroll container, before the sticky head it sits under.
<script lang="ts">
import ProgressiveBlur from '@ui/progressive-blur';
</script>
<!-- an EARLY child of the scroll container — the sticky h-0 root
pins to the scrollport edge through the whole scroll range -->
<div class="relative h-72 overflow-auto">
<ProgressiveBlur position="top" reveal="scroll" class="z-[5]" />
<!-- the sticky head pins ABOVE the band (z-10); the list scrolls
under both through the progressive fade -->
<div class="sticky top-0 z-10">…title + filter…</div>
<!-- …the scrolling list… -->
</div>a11y
Accessibility
Pure scenery: the band is aria-hidden, pointer-events-none decoration over the scroller's real content.
ARIA
| Attribute | Value | Description |
|---|---|---|
| aria-hidden | true | The band is decoration; screen readers skip it entirely |
| pointer-events | none | The band never intercepts pointer input over the scrolling content |
| scroll() | @supports-gated | Engines without scroll timelines keep the resting clean state — never a wrongly-painted band |
theming
Theming
No density footprint — the band is dimensionless chrome; its one token tunes the scroll-in ramp distance.
- entry-01
- entry-02
- entry-03
- entry-04
- entry-05
- entry-06
- entry-07
- entry-08
- entry-01
- entry-02
- entry-03
- entry-04
- entry-05
- entry-06
- entry-07
- entry-08
- entry-01
- entry-02
- entry-03
- entry-04
- entry-05
- entry-06
- entry-07
- entry-08
- entry-01
- entry-02
- entry-03
- entry-04
- entry-05
- entry-06
- entry-07
- entry-08
| Token | Default | Source |
|---|---|---|
| --jx-pblur-ramp | 72px | component |
| height | '6rem' | component |
axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. A FLAT subtractive veil by design (the 减色墨律's own child): the band's FIRST-TIME Defaults contract carries all eight axes with NO owns — no elevation rung, nothing to stamp absent an explicit lane. The supply chain is the point: the axis tree stays continuous through a band.
ProgressiveBlur · universal props
…scrolls…
<ProgressiveBlur pin="grid" position="top" density="small" />api
API
Props from the ProgressiveBlurProps interface (a dialect-discriminated union); the component's MECHANISM ships no script at all — the props lane is JS (resolution + stamps), the popover zero-script precedent.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| pin | 'sticky' | 'grid' | 'sticky' | How the band pins to its edge. sticky = the zero-layout sticky root inside a scroller (any edge); grid = a position-free item of the host one-cell grid (grid-area 1/1 + justify-self per edge, inline edges only). |
| position | 'top' | 'bottom' | 'both' | 'start' | 'end' | 'inline' | 'bottom' | Which scrollport edge(s) the band hangs from: both = the block pair, inline = the start+end pair (the horizontal-overflow strip). Narrows to start | end under pin=grid — other combos are compile-time errors (the runtime fallback is start). |
| hold | number | 0 | Grid dialect only: the outer share of the band (0–100, clamped) that holds the ladder peak instead of ramping — the ramp compresses into the inboard (100-hold)% for strips whose readable content parks inboard of the clip edge. |
| height | string | '6rem' | Band size along its hang axis — any definite CSS length (px/rem); % unsupported. |
| blurLevels | number[] | [0.5, 1, 2, 4, 8, 16, 32, 64] | Per-layer blur px, inner-edge first; at least 2 levels (fewer falls back to the default ladder). |
| reveal | 'static' | 'scroll' | 'static' | static = always painted; scroll = fades in with the nearest scroller (@supports-gated; inline edges keep the always-painted static law). |
| class | string | '' | Class passthrough to each pinned root. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |