A responsive note-taking app with Markdown rendering (react-markdown + remark-gfm), tagging system, auto-save, and Zustand persistence.
Level: 2 · Status: ✅ Built · Live Demo · Source Code
This project teaches you to build a content creation tool with live Markdown preview. Notes are written in Markdown and rendered as rich HTML in real-time. Combined with a tagging system and auto-save, it's a productivity tool you might actually use daily. It introduces the pattern of separating input format (Markdown) from display format (HTML).
- Framework: React 18, TypeScript, Vite
- Styling: Tailwind CSS
- State: Zustand with localStorage persistence
- Markdown: react-markdown + remark-gfm (GitHub Flavored Markdown)
- Forms: React Hook Form + Zod
- Icons: Lucide React
- Deployment: Netlify
-
Build the note model in Zustand. Note:
{ id, title, content (Markdown), tags: string[], createdAt, updatedAt }. Persisted to localStorage. CRUD actions. -
Build the Markdown editor. A split-pane view: textarea on the left (raw Markdown input), rendered preview on the right (react-markdown output). Live preview updates as you type.
-
Configure react-markdown + remark-gfm. GFM adds: tables, strikethrough, task lists, and autolinks. These render correctly in the preview without any extra work.
-
Implement the tagging system. Add/remove tags per note. Tag input with autocomplete from existing tags. Filter notes by tag in the sidebar. Color-coded tag badges.
-
Add auto-save. Debounce content changes (500ms). On each debounced trigger, update the note in Zustand (which persists to localStorage). Show "saved" indicator. No manual save button needed.
-
Build the note list sidebar. List all notes with title and date. Search across titles and content. Sort by recently modified. Click to load in editor.
-
Deploy on Netlify. Static site. All data lives in localStorage — no backend needed.
- react-markdown renders Markdown to React components (not raw HTML). This is safer than
dangerouslySetInnerHTML— no XSS risk from Markdown content. - Auto-save with debounce: use a
useEffectwith a timer. On every content change, reset the timer. When the timer fires (no changes for 500ms), persist. This prevents saving on every keystroke. - Extension: add note folders/notebooks, Markdown toolbar buttons, export as PDF, or sync across devices (would need a backend).