A tiny, fast notes app as a pure frontend demo. No backend or bundler required β just open it via a local web server.
Pairs with notes-backend. This frontend is fully standalone (localStorage), the backend is optional.
Tech stack: Vanilla JS (ES Modules), HTML5, CSS (Design Tokens, Light/Dark Mode), localStorage, ESLint (Flat Config), Prettier
- Features
- Demo
- Requirements
- Quick Start
- Project Structure
- Theming
- Searching with #tags
- Backup (Export/Import)
- Keyboard Shortcuts
- Linting & Formatting
- Scripts
- Commits & Changelog
- Data & Privacy
- Roadmap
- Deployment
- Troubleshooting
- License
- Related
- βοΈ Create / edit / delete notes
- π Pin important notes to keep them on top
- π Live search (title & content)
- #οΈβ£ #tags in search (AND filter; tags are extracted on-the-fly from title/content)
- β¨ Search highlighting (
<mark>for text; highlighted tag chips) - β¬οΈβ¬οΈ Export / Import (JSON; merge or replace existing notes)
- π Dark Mode with system detection & toggle (persisted in
localStorage) - πΎ Persistence via
localStorage - β¨οΈ Shortcut:
Ctrl/Cmd + Entersaves a new note - βΏ A11y:
aria-livefor the list, visually hidden labels, clear focus ring - π± Responsive: two-column layout, stacks on mobile
- Browser: recent versions of Chrome/Edge, Firefox, or Safari.
- Node.js (optional β dev tools only): Node 18+ (recommended 20+) and npm. Not needed to use the app, but helpful for
npm run lint/formatand CI. - Local web server: required because of ES Modules (see Quick Start for options).
You do not need a bundler. Simply run a local server in the project root.
# 1) Clone the repo
git clone https://github.com/rluetken-dev/notes-frontend.git
cd notes-frontend
# 2) (optional) Install dev dependencies for lint/format
npm install
# 3) Start a local server (pick one)
# β VS Code: "Live Server" extension β open index.html β "Go Live"
# β npx: npx http-server -c-1 -p 5173
# β python: python -m http.server 5173
# 4) Open in the browser
# http://localhost:5173Why a server? Because of ES Modules (
<script type="module" src="src/app.js">). Openingindex.htmlviafile://blocks module loading due to CORS and file protocol restrictions.
notes-frontend/
ββ index.html # App shell & markup (DE UI, English code comments)
ββ styles.css # Design tokens, layout, components, dark mode
ββ src/
β ββ app.js # Orchestration: state, render, events
β ββ storage.js # loadNotes()/saveNotes() (localStorage)
β ββ theme.js # Theme controller (toggle, system, persistence)
β ββ time.js # now(), timeAgo() (de-DE)
β ββ dialogs.js # confirmDialog() using the modal
β ββ backup.js # exportNotes()/parseImportedFile()/mergeNotes()
β ββ utils.js # generateId(), escapeHtml(), sort, match, tags, highlight
ββ assets/
β ββ screenshot-light.png
β ββ screenshot-dark.png
ββ eslint.config.mjs # ESLint Flat Config (ESM, browser)
ββ .prettierrc.json # Prettier config
ββ .prettierignore
ββ package.json # npm scripts (lint/format), dev deps
ββ COMMITS.md # Conventional Commits Leitfaden
ββ .github/
β ββ workflows/
β ββ ci.yml # minimal CI (Node 20 + Prettier check)
ββ README.md # you are here
- Design tokens in
:root(light defaults) + dark variants via@media (prefers-color-scheme: dark). - Explicit override via
html[data-theme="light|dark"](set by the toggle button). - No hard-coded colors in components β everything derives from tokens.
Token highlights: --bg, --text, --muted, --card, --border, --primary, --on-primary, --danger, --on-danger, --input-border, --accent, --accent-bg, --accent-ring, --empty-border, --empty-bg, --empty-text.
- Write tags anywhere in title or content using
#like-this(letters, digits,_and-). - Query supports free text and tags together.
- All tags must match (AND). Free text matches if it appears in title or content.
Examples
#workβ notes tagged#work#work #inboxβ notes that have both tagsmeetingβ full-text searchmeeting #workβ full-text and tag filter
- Export (.json): downloads a file like
notes-frontend-YYYYMMDD-HHMMSS.json. - Import (.json): choose a file; you can Replace all notes or Merge with existing ones.
- Merge rule: for identical
ids, the item with the newerupdatedAtwins.
- Merge rule: for identical
- The Export button is disabled when there are no notes yet.
JSON shape
{
"app": "notes-frontend",
"version": 1,
"exportedAt": 1690000000000,
"notes": [
{
"id": "β¦",
"title": "β¦",
"content": "β¦",
"createdAt": 1690000000000,
"updatedAt": 1690000000000,
"pinned": false
}
]
}Tip: localStorage is origin-scoped β http://localhost:5173 and http://127.0.0.1:5173 are different stores.
- Save new note:
Ctrl/Cmd + Enter - Theme toggle: click
π- Right-click on
πβ reset to System
- Right-click on
# Lint (Flat Config)
npm run lint
# Format (Prettier)
npm run formatNotes:
- ESLint uses the Flat Config (
eslint.config.mjs). Remove legacy.eslintrc.*files. - Prettier handles formatting; ESLint focuses on code quality.
npm run formatβ format code with Prettiernpm run format:checkβ verify formattingnpm run lintβ ESLint (Flat Config)
This repo follows Conventional Commits to keep history and changelogs clean.
See COMMITS.md for the rules, allowed types, scopes, and examples.
Examples
feat(ui): add search highlight for tagsfix(storage): handle empty import file gracefullydocs: update README with screenshotschore(prettier): format markdown
Releases
- Tag releases as
vX.Y.Z(SemVer). - Keep release notes concise and link the compare view.
- All data is stored locally in the browser via
localStorage. - No data is sent to any server.
- You can clear storage manually in your browser at any time.
- Backups are plain JSON files; review them before sharing.
- Note: storage is origin-scoped (
http://localhost:5173βhttp://127.0.0.1:5173).
- Tag management (rename/delete, suggestions while typing)
- Result snippets (show a short context around search hits)
- Sorting options (e.g., by
updatedAt,title) - Markdown preview (read-only render)
- PWA (offline, installable)
- IndexedDB storage (scales better than localStorage)
- Tests: unit (Vitest) & end-to-end (Playwright)
- A11y polish: focus trap in modals, improved keyboard navigation
- GitHub Pages: static hosting β publish the
mainbranch or adocs/folder. - Netlify/Vercel: set up as a βStatic Siteβ, leave the build command empty, publish the repo root.
ES Modules wonβt load?
- Use
http://localhost:β¦(notfile://). - Ensure your HTML has:
<script type="module" src="src/app.js"></script>
- Check the server root: the browser must find
/src/app.jsrelative toindex.html.
ESLint complains about import/export?
- Confirm you use the Flat Config:
eslint.config.mjsat the project root. - Remove legacy
.eslintrc.*files. - If needed, set
languageOptions.sourceType = "module"in the flat config.
Dark Mode doesnβt change?
- Make sure the
#theme-togglebutton exists inindex.html. - Check for a hard-coded
data-themeon<html>that could force a theme. - Open DevTools β Console for
localStorageerrors (e.g., private mode).
Export button is disabled?
- Thatβs by design when there are no notes yet. Create a note first.
Import seems to do nothing?
- Verify the JSON format (see Backup section).
- On conflicts (same
id), the note with the newerupdatedAtwins. - Reminder:
localStorageis origin-scoped (localhostβ127.0.0.1).
Search feels off?
- Tag filters are AND combined:
#work #inboxrequires both. - Free-text matches title or content; highlighting is simple substring matching.
MIT β feel free to use, learn, and extend.
