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:

terminal
npm i lostpage

There 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.

app/not-found.tsx
import { LostPage } from "lostpage/react";

export default function NotFound() {
  return <LostPage scene="lost-at-sea" />;
}

// Pages Router: same component in pages/404.tsx

scene 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:

terminal
npx lostpage list           # all scenes and presets
npx lostpage list ocean     # filter by id, category or tag
npx lostpage random         # pick one for me

4. 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.

NotFound.tsx
<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.

NotFound.tsx
<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' }}
VariableControls
--lp-bgBackground behind the scene (also the colour shown before it loads).
--lp-fgText colour.
--lp-mutedSecondary text colour.
--lp-accent / --lp-accent-fgPrimary button background and text.
--lp-font / --lp-font-displayBody and headline font stacks.
--lp-font-mono / --lp-font-serifMono and serif stacks some scenes use.
--lp-code-color / --lp-code-sizeColour and size of the big status code.
--lp-title-sizeHeadline size.
--lp-radiusButton corner radius.
--lp-padPadding around the text layer.
NotFound.tsx
<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.

ACME INC.
Your own component
Rendered on top of any scene, in place of the built-in text.
NotFound.tsx
<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.

app/error.tsx
"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.

Section.tsx
<div style={{ height: 480, borderRadius: 24, overflow: "hidden" }}>
  <LostPage scene="pond" height="100%" />
</div>

CLI reference

terminal
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 you

The framework is detected from your package.json. Use pnpm dlx, yarn dlx or bunx in place of npx if you prefer.

Options reference

OptionTypeWhat it does
scenestring | SceneDefScene id (lazy-loaded) or a scene object imported from lostpage/scenes/<id>.
presetstringOne of the scene's presets. Each changes colours, copy and settings.
codestringStatus code shown. Default "404".
eyebrowstringSmall label above the code.
titlestringThe headline (an h1).
messagestringThe paragraph under the headline.
actionsLostAction[]{ label, href?, onClick?, variant?: 'primary' | 'secondary', icon?: 'back' | 'home' | 'arrow' | 'none' }
themeRecord<string, string>CSS variables without the --lp- prefix.
paramsobjectScene settings. Every scene's page lists them under Scene settings.
heightstringCSS height. Default "100dvh"; use "100%" in a sized parent.
overlaybooleanShow the built-in text layer. Children replace it automatically.
motion"auto" | "reduce" | "full"auto respects prefers-reduced-motion.
interactivebooleanPointer and keyboard input. Default true.
maxDprnumberCap on device pixel ratio. Default 2.
onNavigate(href, event) => voidCalled instead of a full page load. Plug in your router.
seednumberSeed for anything random in the scene.
classNamestringExtra class on the scene root.

Web component

import "lostpage/element" once (or load the CDN script) and use <lost-page> anywhere. Attributes are reactive.

AttributeMaps to
scene, preset, code, eyebrow, message, height, motion, seedthe options of the same name
headingtitle (because title shows a browser tooltip)
primary-label / primary-hrefthe first button
secondary-label / secondary-hrefa second, text-style button
theme='{"accent":"#f00"}'theme (JSON)
params='{"rain":1}'params (JSON)
interactive="false", overlay="false"turn those off

JavaScript API

main.js
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 GPU

Accessibility

Performance & SSR

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.

my-scene.ts
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.