Halftone Background
Halftone background React component: a continuous halftone dot background whose density shifts with scroll progress and whose dots swell near the cursor, drawn with CSS gradients.
A full-bleed printed halftone texture. The dot lattice is a repeating CSS radial-gradient, so no images are needed; a second mask-image layer creates a one-sided dense-to-sparse tone ramp, like a printed halftone screen. As you scroll, the dot radius grows smoothly from minDot to maxDot, and a layer of larger dots appears around the cursor through a circular mask. Content passed as children sits above the dots.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add halftone-background.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<HalftoneBackground/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/halftone-background.en.jsonOr, once registries are configured in components.json, install it as @webberui/halftone-background.
Usage
import { HalftoneBackground } from "@/components/ui/halftone-background";
<HalftoneBackground
direction="radial"
className="min-h-[480px] text-neutral-900/15 dark:text-white/15"
>
<section className="flex min-h-[480px] items-center justify-center">
<h1 className="text-4xl font-semibold">印刷質感,一路延伸</h1>
</section>
</HalftoneBackground>Props
| Prop | Type | Default | Description |
|---|---|---|---|
color | string | "currentColor" | Dot color; defaults to currentColor, paired with the root's text-neutral-900/10 dark:text-white/15 so it follows the theme |
cell | number | 14 | Halftone cell size (px); one dot per cell |
minDot | number | 1 | Dot radius (px) at the start of the scroll range |
maxDot | number | 4 | Dot radius (px) at the end of the scroll range; cursor-layer dots are 1.6× this value |
direction | "top" | "bottom" | "radial" | "radial" | Tone ramp direction: top = dense at top, bottom = dense at bottom, radial = dense in the center |
scrollDriven | boolean | true | Whether the dot radius follows scroll progress between minDot and maxDot |
cursor | boolean | true | Whether enlarged dots appear around the cursor |
cursorRadius | number | 140 | Radius (px) of the enlarged area around the cursor |
angle | number | 0 | Rotation of the dot lattice (degrees), like a print screen angle |
scrollContainer | React.RefObject<HTMLElement | null> | — | Optional scroll container; when set, its overall scroll progress (0→1) drives the dot radius, otherwise the component's own position in the viewport is tracked |
children | React.ReactNode | — | Content layered above the dots |
className | string | — | Passed through to the root container |
How it works
- Pure CSS drawing: the main layer is
radial-gradient(circle, var(--wb-halftone-color) calc(var(--wb-halftone-dot) * 1px), transparent …)with abackground-sizeofvar(--wb-halftone-cell); no canvas and no images. - Scroll driving:
useScrolltracks the component from entering the bottom of the viewport to leaving the top (offset: ["start end", "end start"]), maps that to a dot radius, smooths it with a spring (stiffness 120, damping 30) and writes it to--wb-halftone-dot. PassscrollContainerwhen it lives inside a fixed-height scroll box. - Cursor layer: the same lattice with a dot radius of
maxDot × 1.6, revealed only around the cursor bymask-image: radial-gradient(circle var(--wb-halftone-radius) at var(--mx) var(--my), …);pointermoveupdates the position through a rAF throttle, and the radius springs back to 0 on leave. - Screen angle: when
angleis not 0, the lattice lives in a child layer whose side is width + height and is rotated, so it covers the box at any angle. - Overridable CSS variables:
--wb-halftone-color,--wb-halftone-cell(default 14px),--wb-halftone-dot,--wb-halftone-radius(default 140px),--wb-halftone-ease-duration(cursor layer fade, default 400ms).
Accessibility
- Both dot layers are purely decorative, with
aria-hidden="true"andpointer-events-none, so they never get in the way of clicking or reading the content above. - On touch devices (
pointer: coarse) there is no hover, so the cursor layer is turned off automatically. - When the user has reduce motion enabled, the dot radius is pinned at mid density (the average of
minDotandmaxDot) and no longer reacts to scroll or the cursor.
Falling Petals
Seasonal falling background: sakura petal, maple leaf and snowflake presets, per-particle tumbling and sway with wind turbulence, adjustable density and wind, optionally accelerated by scroll.
Floating Card Field
A field of cards drifting at different depths in 3D space: pointer parallax varies by depth, scrolling pulls the whole field back and scatters it, and distant cards blur for depth of field.