Guide
lostpage is one npm package with 116 404 scenes and 666 ready-made designs. Each scene is a self-contained module (WebGL, Canvas or HTML/CSS) behind the same small API, with thin adapters for every framework. Only the scene you use is downloaded; everything else is code-split away. This guide walks you from install to a fully customised page.
1. Install
Use whichever package manager your project already uses:
npm i lostpageThere are no dependencies. React and Vue are optional peer dependencies, needed only if you use lostpage/react or lostpage/vue.
2. Add it to your app
Pick your framework. Each snippet is a complete 404 route you can paste as it is.
import { LostPage } from "lostpage/react";
export default function NotFound() {
return <LostPage scene="lost-at-sea" />;
}
// Pages Router: same component in pages/404.tsxscene is the only required prop. Everything else falls back to the scene's built-in copy, colours and buttons, and the main button links to /.
3. Pick a design
Every card on the browse page is a scene plus a preset. The scene id goes in scene, the preset in preset. Open any card to see it live, customise it, and copy ready-made code for your framework.
You can also browse from the terminal:
npx lostpage list # all scenes and presets
npx lostpage list ocean # filter by id, category or tag
npx lostpage random # pick one for me4. Change the words
Every piece of text is a prop: eyebrow, code, title and message. Anything you leave out falls back to the preset, then the scene. Edit the fields below to see it live.
<LostPage
scene="mesh-gradient"
title="We moved this page."
message="It lives somewhere new now. The homepage will point you there."
actions={[{ label: "Take me home", href: "/" }]}
/>5. Buttons & routing
actions is a list of buttons. The first one is usually primary (filled); others can be secondary (text links). Each takes an href, an onClick, or both.
<LostPage
scene="planet"
actions={[
{ label: "Fly home", href: "/", icon: "home" },
{ label: "Contact support", href: "/support", variant: "secondary" },
{ label: "Report broken link", onClick: () => report(location.href), variant: "secondary" },
]}
/>Without onNavigate, buttons are ordinary links: a click does a full page load, and middle-click or ⌘-click opens a new tab as you'd expect.
6. Presets
A preset is a curated variation of a scene, with its own colours, settings and copy. Here are a few of lost-at-sea's presets. The preset ids are shown on every design page, and npx lostpage list <scene> prints them too.
<LostPage scene="lost-at-sea" preset="storm" />
7. Colours & fonts
Text, buttons and fonts are driven by CSS variables on the scene root. Override them with the theme prop (names without the --lp- prefix) or from your own CSS.
theme={{ accent: "#ff4d2e", fg: "#ffffff", font: '"Fraunces",Georgia,serif' }}| Variable | Controls |
|---|---|
--lp-bg | Background behind the scene (also the colour shown before it loads). |
--lp-fg | Text colour. |
--lp-muted | Secondary text colour. |
--lp-accent / --lp-accent-fg | Primary button background and text. |
--lp-font / --lp-font-display | Body and headline font stacks. |
--lp-font-mono / --lp-font-serif | Mono and serif stacks some scenes use. |
--lp-code-color / --lp-code-size | Colour and size of the big status code. |
--lp-title-size | Headline size. |
--lp-radius | Button corner radius. |
--lp-pad | Padding around the text layer. |
<LostPage scene="aurora" theme={{ accent: "#ff4d2e", fg: "#fff", font: "'Fraunces', serif" }} />Scenes never load web fonts themselves. If your site already loads a font, reference it in the stack and it's used.
8. Scene settings
Beyond text and colours, every scene exposes its own settings through params: sky colours and wave height for the ocean, particle counts, game speeds, glass dispersion and so on. Open any design and use the Scene settings tab to see and tweak them all. The generated code includes exactly what you changed.
params={{ waveHeight: 0.9, rain: 0.6, lightning: 1, lamp: "#ff5a4a" }}9. Your own content
Keep the animated scene but render your own markup on top. Pass children (React), the default slot (Vue) or child elements (web component), and the built-in text layer steps aside.
<LostPage scene="constellation">
<MyBrandedErrorCard />
</LostPage>10. Other error codes
Nothing is specific to 404. Pass code="500", "403" or "Oops". Scenes that draw the code (3D type, particles, games, signs) render whatever you give them. In Nuxt and SvelteKit, pass the real status from the error object so one page covers every error.
"use client";
import { LostPage } from "lostpage/react";
export default function Error({ reset }: { reset: () => void }) {
return (
<LostPage
scene="server-room"
preset="fire"
code="500"
title="Something broke on our side."
actions={[{ label: "Try again", onClick: () => reset() }, { label: "Home", href: "/", variant: "secondary" }]}
/>
);
}11. Size & placement
By default a scene fills the viewport height (100dvh) and the width of its parent. To use one inside a card, a modal or a section, give the parent a size and pass height="100%". Text and buttons scale with the component itself (CSS container queries), not the window, so it looks right at any size. The thumbnails on this site are live components too.
<div style={{ height: 480, borderRadius: 24, overflow: "hidden" }}>
<LostPage scene="pond" height="100%" />
</div>CLI reference
npx lostpage list [filter] list scenes (filter by id, category or tag)
npx lostpage add <scene>[:preset] write a ready-made 404 page for your framework
--framework next|next-pages|react|remix|vue|nuxt|svelte|sveltekit|astro|angular|solid|html
--out <path> write somewhere else
--force overwrite an existing file
--dry-run print the file instead of writing it
npx lostpage random pick one for youThe framework is detected from your package.json. Use pnpm dlx, yarn dlx or bunx in place of npx if you prefer.
Options reference
| Option | Type | What it does |
|---|---|---|
scene | string | SceneDef | Scene id (lazy-loaded) or a scene object imported from lostpage/scenes/<id>. |
preset | string | One of the scene's presets. Each changes colours, copy and settings. |
code | string | Status code shown. Default "404". |
eyebrow | string | Small label above the code. |
title | string | The headline (an h1). |
message | string | The paragraph under the headline. |
actions | LostAction[] | { label, href?, onClick?, variant?: 'primary' | 'secondary', icon?: 'back' | 'home' | 'arrow' | 'none' } |
theme | Record<string, string> | CSS variables without the --lp- prefix. |
params | object | Scene settings. Every scene's page lists them under Scene settings. |
height | string | CSS height. Default "100dvh"; use "100%" in a sized parent. |
overlay | boolean | Show the built-in text layer. Children replace it automatically. |
motion | "auto" | "reduce" | "full" | auto respects prefers-reduced-motion. |
interactive | boolean | Pointer and keyboard input. Default true. |
maxDpr | number | Cap on device pixel ratio. Default 2. |
onNavigate | (href, event) => void | Called instead of a full page load. Plug in your router. |
seed | number | Seed for anything random in the scene. |
className | string | Extra class on the scene root. |
Web component
import "lostpage/element" once (or load the CDN script) and use <lost-page> anywhere. Attributes are reactive.
| Attribute | Maps to |
|---|---|
scene, preset, code, eyebrow, message, height, motion, seed | the options of the same name |
heading | title (because title shows a browser tooltip) |
primary-label / primary-href | the first button |
secondary-label / secondary-href | a second, text-style button |
theme='{"accent":"#f00"}' | theme (JSON) |
params='{"rain":1}' | params (JSON) |
interactive="false", overlay="false" | turn those off |
JavaScript API
import { mount } from "lostpage";
const handle = mount("#not-found", "nixie", { preset: "argon" });
await handle.ready; // scene loaded and drawing
handle.update({ title: "Still not here." });
handle.pause(); // stop rendering
handle.resume();
handle.destroy(); // remove everything, free the GPUAccessibility
- The text is real HTML: an
h1that also says "Error 404" for screen readers, a paragraph, and links. Canvases are hidden from assistive tech. prefers-reduced-motionis respected. Animated scenes draw one still frame, and interaction-driven ones only redraw on input.- Games only capture the arrow keys and space while they're on screen and focus isn't in a text field.
- Without WebGL, the scene steps aside and the text layer stays.
Performance & SSR
- Zero dependencies. Scenes are 3–20 kB minified, plus a small shared runtime.
- Rendering pauses when the component is off screen or the tab is hidden.
- Shader scenes watch their frame time and lower internal resolution on slow devices, then raise it again.
- Everything is cleaned up on unmount, including GL contexts.
- SSR-safe: nothing touches the DOM until the component mounts in the browser. In Next.js the React adapter is already a client component, so it can be used straight from a server
not-found.tsx. Pass the scene by id (a string) there.
Write your own scene
The scenes are built on a small public API. Write your own and get the text layer, theming, pausing, resizing and reduced-motion handling for free.
import { defineScene, mount } from "lostpage/core";
export const mine = defineScene({
id: "my-404", name: "Mine", description: "Bouncing dots",
category: "Abstract", kind: "2d", motion: "live", tags: [],
layout: "center",
copy: { title: "Not here." },
params: { color: "#ff4d2e" },
setup(ctx) {
const canvas = ctx.canvas(); // sized to the component automatically
const g = canvas.getContext("2d")!;
return {
frame(t) {
g.setTransform(ctx.dpr, 0, 0, ctx.dpr, 0, 0);
g.clearRect(0, 0, ctx.width, ctx.height);
g.fillStyle = ctx.params.color;
g.beginPath();
g.arc(ctx.width / 2, ctx.height / 2 + Math.sin(t * 3) * 60, 30, 0, Math.PI * 2);
g.fill();
},
};
},
});
mount(document.body, mine);ctx gives you the size and DPR, a smoothed pointer, reduced-motion state, a seeded random, canvas(), event helpers that clean themselves up, and navigate(). Shader helpers (shaderScene, gl, glsl) and text helpers (text.textPoints, text.textSDF) are exported too.
FAQ
Does it work with TypeScript?
Yes. Types ship with the package, including autocompletion for scene ids.
Will it slow my site down?
It only runs on your 404 page, only downloads the one scene you use, and pauses whenever it's off screen.
Which browsers?
Current Chrome, Edge, Firefox and Safari (desktop and mobile). WebGL scenes need WebGL2; others work with plain Canvas or CSS.
Can I use it for commercial projects?
Yes. lostpage is free for personal projects, commercial products and client websites, with no attribution required. The one thing you can't do is resell or repackage the designs themselves. See the license.