This is a solution to the Note-taking web app challenge on Frontend Mentor. Frontend Mentor challenges help you improve your coding skills by building realistic projects.
Users should be able to:
- Create, read, update, and delete notes
- Archive notes
- View all their notes
- View all archived notes
- View notes with specific tags
- Search notes by title, tag, and content
- Select their color theme (light / dark / system)
- Select their font theme (sans-serif / serif / monospace)
- Receive validation messages if required form fields aren't completed
- Navigate the whole app and perform all actions using only their keyboard
- View the optimal layout for the interface depending on their device's screen size
- See hover and focus states for all interactive elements on the page
- Bonus: Create an account, log in, and reset their password (localStorage-based auth simulation)
Desktop (1440px)
Mobile (375px)
- Solution URL: GitHub Repository
- Live Site URL: fsdev-note-taking-web-app-dev.vercel.app
- Semantic HTML5 markup (
header,nav,main,aside,article,section) - CSS custom properties (design tokens in
variables.css) - CSS Modules for component-scoped styles
- CSS Grid (3-column desktop shell)
- Flexbox (sidebar, note cards, meta bar)
- Responsive design: 1440px desktop → 768px tablet → 375px mobile
- React 19 with TypeScript
- Vite — build tool and dev server
- React Router v6 — client-side routing and auth guard
- react-markdown — markdown preview in note viewer
- Context API +
useReducerfor notes state with localStorage persistence - Vitest + @testing-library/react — unit and component tests
CSS data-* attribute selectors for mobile view toggling
Rather than conditionally rendering panels in React (which causes layout thrashing), I applied a data-mobile-view attribute to the shell and toggled visibility purely in CSS:
[data-mobile-view='list'] > .detail { display: none; }
[data-mobile-view='detail'] > .list { display: none; }This keeps the DOM stable while giving React full control of the state.
Portal-based accessible dialog
The ConfirmDialog uses createPortal to render outside the note panel, with role="alertdialog", focus trap, and Escape key handling — all without a third-party modal library.
useReducer + localStorage as a lightweight store
A single reducer handles CREATE / UPDATE / DELETE for notes. Persisting to localStorage in a useEffect that subscribes to state kept the logic simple and testable in isolation without mocking React.
function reducer(state: Note[], action: Action): Note[] {
switch (action.type) {
case 'CREATE': return [action.note, ...state];
case 'UPDATE': return state.map(n => n.id === action.id ? { ...n, ...action.patch } : n);
case 'DELETE': return state.filter(n => n.id !== action.id);
}
}- Replace the localStorage auth simulation with a real backend (Node.js / Supabase) to fulfil the full-stack bonus requirement.
- Add note sharing and collaboration features.
- Explore optimistic UI updates and offline support via a service worker.
- React
createPortaldocs — essential for the accessible modal dialog - WAI-ARIA Authoring Practices — Dialog Pattern — guided the focus trap and keyboard behaviour
- CSS Modules with Vite — zero-config scoped styles
This project was built with Claude Code (claude.ai/code) as an AI pair-programmer.
What worked well:
- Claude planned the full 15-commit roadmap, breaking the challenge into isolated, reviewable slices (design tokens → shell → notes CRUD → auth → responsive → tests → README).
- Generating boilerplate for Context providers, CSS Module files, and test suites was significantly faster with AI assistance.
- When browser automation screenshots failed (download path not accessible), Claude switched to injecting
html2canvasfrom CDN and later tocapture-website-cli— iterating until the approach worked.
What to watch:
- AI-generated CSS sometimes needed a second pass to match the Figma specs exactly (especially spacing and icon alignment at tablet breakpoints).
- Always verify generated test files run green before committing — a few tests needed environment adjustments (
jsdomvs Node for localStorage mocks).




