Getting started
blobscape scatters large, low-opacity shapes behind a page. The layout comes from a seed, usually the URL path, so each page keeps the same blobs on every visit, and the server and browser draw exactly the same thing.
Install
npm install blobscapeReact is an optional peer dependency (18 or 19). The core, imported from blobscape, has no dependencies and runs anywhere. The React component lives at blobscape/react.
React
<Blobscape> is a layer, not a wrapper. Put it inside the element it should sit behind, and give that element blobscapeParentStyle:
import { blobscapeParentStyle } from "blobscape";
import { Blobscape } from "blobscape/react";
<div style={blobscapeParentStyle}>
<Blobscape seed={pathname} />
{children}
</div>blobscapeParentStyle is { position: "relative", isolation: "isolate" }. The first line makes the layer cover this element. The second keeps the layer (which uses z-index: -1) from slipping behind the element's own background. In development, <Blobscape> warns in the console if the parent is missing either. Import it from blobscape, as above: imported from blobscape/react (a client module) into a server component, it arrives as an empty reference and the styles silently drop out.
Content column, full-width blobs
The common layout is a narrow content column with blobs across the whole browser. Put <Blobscape> in the column and add fullBleed: the layer keeps the column's height and breaks out to the full viewport width.
<main style={{ ...blobscapeParentStyle, maxWidth: 720, margin: "0 auto" }}>
<Blobscape seed={pathname} fullBleed />
<article>…</article>
</main>Before the page hydrates, a full-bleed layer is 100vw wide, which includes the vertical scrollbar on systems that show one. To rule out a brief horizontal scrollbar, add overflow-x: clip to body.
Next.js
<Blobscape> is a client component (it ships with "use client") and renders on the server too. Pass the seed explicitly, for example from usePathname() in a client component:
"use client";
import { usePathname } from "next/navigation";
import { Blobscape } from "blobscape/react";
export function Blobs() {
return <Blobscape seed={usePathname()} fullBleed />;
}Pinned to the viewport
attach="fixed" pins the layer to the viewport so the blobs stay put while content scrolls. The layer covers one screen height.
Presets
Spread a preset, then override anything:
import { presets } from "blobscape";
<Blobscape seed={pathname} {...presets.polygons} fill="#8a5cf6" />| Preset | Look |
|---|---|
blobs | Smooth organic blobs, 600–1200px. |
polygons | Sharp angular shards, 600–1200px, about one per 800px of height. |
pebbles | Polygons with softened corners, a little smaller and closer. |
confetti | Many small shapes: a light texture rather than accents. |
The playground loads each preset into a real page and gives you the code for whatever you end up with.
How the layout grows
On the server, and on the first render in the browser, <Blobscape> draws 4000px of blobs (initialHeight). After mount it measures the real height and adds blobs below as the page grows. The layout is built in horizontal bands, each seeded on its own, so a taller page only ever adds blobs at the bottom: nothing already on screen moves.
Accessibility
The layer is decorative: aria-hidden="true" and pointer-events: none. Keep an eye on contrast (the default opacity is 0.4) so text over the blobs stays readable.
Stability
The same seed and options always produce the same output on the server and in the browser.
From 1.0, the same seed, options and major version produce the same output, byte for byte: any change to generated output, presets included, is a new major version, so upgrading within a major version never moves your blobs. Before 1.0, a minor version (0.x → 0.y) may still change output; patch versions never do.