Getting started
Plop! is a hand-lettered sticker UI kit for real-time operations apps: dispatch consoles, field service, ticketing. Press it like you mean it.
Install
pnpm add @plop-ui/react @plop-ui/fonts # or: npm i @plop-ui/react @plop-ui/fonts
@plop-ui/react brings @plop-ui/tokens with it. React 19 is a peer dependency.
Add the styles and the provider
// app root (any React app)
import "@plop-ui/fonts/fonts.css"; // Sketchpop @font-face rules
import "@plop-ui/react/styles.css"; // tokens + component styles
import { PlopProvider, Button } from "@plop-ui/react";
export function App() {
return (
<PlopProvider surface="poster">
<Button variant="tomato" buddy>Send report</Button>
</PlopProvider>
);
}
PlopProvider sets the surface (the paper everything sits on), light or dark, haptics, sound and reduced motion. It also renders the SVG filters that make outlines wobble, so put it once near the root. In Next.js, follow Using with Next.js instead: it loads the fonts with next/font.
Good to know
The paper only covers what's inside the provider. PlopProvider paints the surface on its own box, so on a short page the paper stops where your content ends (and the browser's default 8px body margin shows as a white frame). To cover the whole screen, put the paper on <body>, give it the full height, and let the provider skip painting:
<body className="plop-surface" style={{ margin: 0, minHeight: "100vh" }}>
<PlopProvider surface="poster" paint={false}>{children}</PlopProvider>
</body>
The full height matters: the paper's soft light is sized to the page, so on a page shorter than the screen it would repeat and leave a faint line.
Buttons stretch in a grid or a column. A grid (or a flex column) stretches its children to full width, buttons included. That's standard CSS, not the sticker. To keep a button its natural size, set justify-self: start on it in a grid, align-self: flex-start in a flex column, or wrap it in a <div>.
What's in v0.1
| Piece | What it is |
|---|---|
PlopProvider | Surface, theme, haptics, sound and reduced-motion settings |
Button, IconButton | Die-cut sticker buttons that peel, squish and spring |
Tag | Washi-tape status labels, optionally removable |
Switch, Checkbox | Inputs with a sleepy/awake knob and a hand-drawn tick |
StatusTimeline | Where an incident is now, as an ordered list |
useHaptics, useBoil | Phone haptics and "boiling" ink lines |
onColor | Picks readable text for any fill at runtime |
Principles
- Stickers, not boxes. Things you press are die-cut stickers with a paper border and a hatched shadow.
- One ink. All lines and text use the ink color. Spot colors fill shapes but never draw lines.
- Imperfect on purpose, but seeded. Tilt and wobble come from a seed, so a component looks the same on every render and in SSR.
- Every press answers. Squish on press, spring on release, a tap on phones. Motion only happens in response to a person.
- Readable first. Lettering is for short labels and headings. Anything people read in full is upright Sketchpop Regular at 17px or larger.