Sticky Note Board
Colourful sticky notes on a cork board: drag, add, and crumple to delete.
This is a WebberUI Pro component
The install command below needs a licence to run; without one it returns 401.
A cork board rendered with CSS texture, covered in colourful sticky notes with slight rotation and tape / pushpin decorations: drag them freely, double-click to edit the text, press ✕ to crumple one into a paper ball and toss it away, and add a new auto-coloured note with one click in the bottom right — good for to-do walls, brainstorming boards, and other playful scenarios.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add "https://webberui.com/r/sticky-note-board.en.json?t=<install token>"Sticky Note Board is a React sticky notes board component built with Motion and Tailwind CSS. Notes can be dragged, edited with a double-click, added, and removed, and every change comes back as the full array through onChange so you can persist it yourself.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<StickyNoteBoard/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- Brainstorming boards and retrospective boards in team tools
- A to-do or memo area on a personal dashboard
- Interactive message walls on event pages or course platforms
- A hands-on interactive section in a product tour or portfolio
- Not a good fit: workflows that need grouped, ordered columns, such as a task board split by status; use a dedicated kanban or list component instead
Installation
npx webberui@latest add "https://webberui.com/r/sticky-note-board.en.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/sticky-note-board.
Usage
import { StickyNoteBoard, type StickyNote } from "@/components/ui/sticky-note-board";
const notes: StickyNote[] = [
{ id: "milk", text: "Buy milk after work 🥛", x: 24, y: 22, color: 0 },
{ id: "gym", text: "Gym Wed 19:00", x: 200, y: 120, color: 2 },
];
<StickyNoteBoard notes={notes} height={400} onChange={(next) => console.log(next)} />Examples
Save to localStorage
notes is only the initial data, so read the saved content on the client first and then render the board; write it back on every onChange.
"use client";
import * as React from "react";
import { StickyNoteBoard, type StickyNote } from "@/components/ui/sticky-note-board";
const KEY = "my-board-notes";
export function PersistentBoard() {
const [initial, setInitial] = React.useState<StickyNote[] | null>(null);
React.useEffect(() => {
const saved = localStorage.getItem(KEY);
setInitial(saved ? (JSON.parse(saved) as StickyNote[]) : []);
}, []);
if (!initial) return null;
return (
<StickyNoteBoard
notes={initial}
onChange={(next) => localStorage.setItem(KEY, JSON.stringify(next))}
/>
);
}Sync to a backend
Drag end, committed edits, adds, and removals all fire onChange, so you can send the latest array straight to your API.
<StickyNoteBoard
notes={notesFromServer}
height={480}
addLabel="Add idea"
onChange={(next) => {
void fetch("/api/boards/retro-42", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ notes: next }),
});
}}
/>Fixed colours, no add button
Set a palette index with color (0–5 are yellow, pink, green, blue, orange, purple) and hide the add button with showAdd={false} so users only rearrange the existing notes.
const notes: StickyNote[] = [
{ id: "bug", text: "Login page breaks in Safari", x: 24, y: 24, color: 1 },
{ id: "idea", text: "Add a progress bar to checkout", x: 200, y: 40, color: 2 },
{ id: "todo", text: "Update the docs by Friday", x: 110, y: 170, color: 3 },
];
<StickyNoteBoard notes={notes} showAdd={false} height={320} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
notes | StickyNote[] | [] | Initial note data; from then on the component manages it internally and reports changes through onChange |
onChange | (notes: StickyNote[]) => void | — | Returns the latest note array after any drag, edit, addition, or crumple |
height | number | 380 | Cork board height (px) |
addLabel | string | "Add a sticky note" | Text of the add button |
showAdd | boolean | true | Whether to show the add button in the bottom right |
className | string | — | Forwarded to the outermost (wooden frame) container |
StickyNote type
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier, also the deterministic hash seed for the rotation angle and the pushpin / tape style |
text | string | Text content on the note |
x | number | X coordinate relative to the board's top-left corner (px) |
y | number | Y coordinate relative to the board's top-left corner (px) |
color | number | Swatch index 0–5 (yellow/pink/green/blue/orange/purple); when unspecified, the colour is derived from the id hash |
Accessibility
- Every note is keyboard-focusable:
Enterenters edit mode, arrow keys move it (Shiftaccelerates),Delete/Backspacecrumples and deletes it - Notes use
role="group"witharia-labelto read out their content, and the crumple button has an explicitaria-label - While editing,
Esccancels and restores the original text, andCmd/Ctrl + Enteror blurring submits - When the user has "reduce motion" enabled at the system level, the reveal, pick-up scaling, and paper-crumple animations all become plain fade in / fade out
- The rotation angle and decoration style are produced by a deterministic hash of the
id, so SSR and client rendering match
FAQ
Does it work with the Next.js App Router?
Yes. The file already starts with "use client", so you can import it from a Server Component page as usual. Each note's rotation and decoration come from a deterministic hash of its id, so server and client renders match.
Why doesn't the board update when I change notes?
notes is read only on the first render; after that the component manages its own state. To swap in entirely new data, change the component's key so it remounts.
How do I store note positions?
Each note's x and y are pixel coordinates relative to the board's top-left corner. After a drag or an arrow-key move they are rounded to integers and returned through onChange. Save the returned array as-is and pass it back as notes to restore the board.
Can it be used with a keyboard?
Yes. Every note is focusable: Enter edits, arrow keys move it (hold Shift for bigger steps), and Delete or Backspace removes it. While editing, Esc cancels and Cmd/Ctrl + Enter commits.
What are the dependencies?
motion, lucide-react, clsx, and tailwind-merge. The cork texture is pure CSS gradients, so no image files are needed.