A sticky-note style notes app built with Next.js 16 (App Router), React 19, and CSS Modules only (no Tailwind, no UI kit).
- Add, edit, delete notes
- Pin / unpin notes (pinned notes float to their own section)
- Favorite notes + a Favorites filter
- 6 note paper colors, changeable per note
- Live search across title + content
- Light / dark mode (persisted, respects system preference on first load)
- Persisted to
localStorage— notes survive a refresh - Export a note as a
.txtfile - Copy a note to the clipboard
- Fully responsive, keyboard-focus visible, respects
prefers-reduced-motion
- Next.js 16.2.12 (App Router, Turbopack)
- React 19.2.4
- CSS Modules (zero CSS frameworks)
next/font/googlefor Fraunces (display), Inter (body), IBM Plex Mono (meta/labels)
npm install
npm run devOpen http://localhost:3000.
npm test
npm run test:e2e
npm run lintThe unit suite covers note helpers, local storage, and note composition. The
Playwright suite covers adding, searching, favoriting, persistence, and delete
confirmation. Install the Playwright browser once with
npx playwright install chromium if it is not already available locally.
First
npm run dev/npm run buildneeds internet access once, sincenext/fontdownloads and self-hosts the three Google Fonts at build time. After that it works fully offline.
app/
layout.js root layout, font loading
page.js page state: notes, theme, search, filters
page.module.css
globals.css design tokens (CSS variables) + reset
components/
Header.js / .module.css brand, search, theme toggle
ComposeNote.js / .module.css add/edit form
NoteCard.js / .module.css note card + actions
lib/
notes.js note colors, id + timestamp helpers
storage.js localStorage read/write helpers
- Every component owns one
<Component>.module.cssfile; classes are imported asstyles.xxxand are scoped automatically — no naming collisions to worry about. - Design tokens (colors, spacing, shadows, radii) live as CSS custom properties
in
app/globals.css, then get reused inside modules withvar(--token). This is what makes light/dark mode a single attribute swap (data-theme) instead of a second set of styles. :global(...)is used sparingly inNoteCard.module.cssto react to thedata-themeattribute on<html>, since that lives outside the component.