zeroUI

Pause menu

A game pause menu for React: the scene dimmed and blurred behind a panel with the run so far, the ways out, and abandoning the run held.

Install

terminal
# once per project: where @zeroui items come frompnpm dlx shadcn@latest registry add @zeroui=https://zeroui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zeroui/pause-menu

Preview

Hull
x

Paused

Time
34 min
Crates
3 / 5
Earned
4,250 cr

Esc Resume

pause-menu.tsx
import { type KeyboardEvent, useState } from "react";import { Button } from "@/components/zero/button";import { Gauge, GaugeLabel, GaugeValue } from "@/components/zero/gauge";import { HoldButton } from "@/components/zero/hold-button";import { Kbd } from "@/components/zero/kbd";import { Stat, Stats } from "@/components/zero/stats";/* * A pause menu over the game: the scene behind dimmed and blurred, a panel with the run so far * (time, crates, credits, hull), the ways out of it, and abandoning the run held so it never * happens by a slip. Escape (while focus is on the screen) or Resume brings the game back; Escape * again pauses it. The game behind is a drawn stand-in. */export function PauseMenu() {  const [paused, setPaused] = useState(true);  const [abandoned, setAbandoned] = useState(false);  const toggle = (event: KeyboardEvent<HTMLElement>) => {    if (event.key === "Escape") setPaused((p) => !p);  };  return (    <section      aria-label="Game"      onKeyDown={toggle}      className="relative grid min-h-[30rem] w-full max-w-5xl place-items-center overflow-hidden border-(length:--zero-stroke) border-zero-ink p-4"      style={{        background:          "radial-gradient(ellipse at 50% 110%, color-mix(in srgb, var(--color-zero-accent) 20%, transparent), transparent 60%), repeating-linear-gradient(90deg, color-mix(in srgb, var(--color-zero-text) 5%, transparent) 0 1px, transparent 1px 48px), linear-gradient(var(--color-zero-void), var(--color-zero-ground))",      }}    >      {/* the game: a gauge in the corner, so there is something to pause */}      <div className="absolute top-4 left-4 w-56 max-w-[60%]">        <Gauge value={74} max={120}>          <GaugeLabel>Hull</GaugeLabel>          <GaugeValue />        </Gauge>      </div>      {paused ? (        <>          <div            aria-hidden            className="absolute inset-0 bg-[rgb(0_0_0/0.6)] backdrop-blur-sm"          />          <section            aria-labelledby="pause-title"            className="relative grid w-full max-w-md gap-5 border-(length:--zero-stroke) border-zero-ink bg-zero-surface bg-(image:--zero-hatch) p-6"          >            <h2              id="pause-title"              className="origin-left rotate-(--zero-tilt) font-black font-zero text-zero-2xl uppercase italic"            >              Paused            </h2>            <Stats>              <Stat label="Time" value={34} unit=" min" />              <Stat label="Crates" value={3} unit=" / 5" />              <Stat label="Earned" value={4250} unit=" cr" />            </Stats>            <div className="grid gap-2">              <Button                size="lg"                onClick={() => setPaused(false)}                className="w-full"              >                Resume              </Button>              <Button variant="secondary" className="w-full">                Settings              </Button>              <Button variant="secondary" className="w-full">                Map              </Button>              <HoldButton                variant="danger"                duration={1600}                onConfirm={() => setAbandoned(true)}                className="w-full"              >                {abandoned ? "Run abandoned" : "Hold to abandon the run"}              </HoldButton>            </div>            <p className="flex items-center gap-2 font-semibold font-zero-text text-zero-sm text-zero-text-3">              <Kbd>Esc</Kbd> Resume            </p>          </section>        </>      ) : (        <Button variant="secondary" onClick={() => setPaused(true)}>          <Kbd>Esc</Kbd> Pause        </Button>      )}    </section>  );}

Behaviour

Escape, while focus is in the game, pauses and resumes; so does Resume. The panel shows the run so far as Stats, then Resume, Settings and Map. Abandoning the run is a Hold button in red with a longer hold.

The game behind is a stand-in (a hull gauge on a drawn floor). In your game, render the panel over the real scene.

Built from Stats, Button, Hold button, Gauge, Kbd. It installs those with it.