Nov 2, 2025 • javascript, tutorial, beginner
Build a Notes App in Vanilla JavaScript
Build a Notes App in Vanilla JavaScript
In this tutorial, we'll build a clean, functional notes application using only vanilla JavaScript—no frameworks required. Perfect for beginners looking to master DOM manipulation and localStorage.
What We're Building
A notes app with:
- ✓ Create, read, update, and delete notes
- ✓ LocalStorage persistence (notes survive page refresh)
- ✓ Search/filter functionality
- ✓ Responsive design
- ✓ Keyboard shortcuts
Step 1: HTML Structure
Create an index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Notes App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="app">
<header>
<h1>📝 My Notes</h1>
<button id="add-note" class="btn">+ New Note</button>
</header>
<input type="text" id="search" placeholder="Search notes...">
<div id="notes-container" class="notes-grid"></div>
</div>
<script src="app.js"></script>
</body>
</html>
Step 2: CSS Styling
Create style.css:
* { box-sizing: border-box; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f5f7fa;
}
.app {
max-width: 1200px;
margin: 0 auto;
padding: 24px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.btn {
padding: 10px 20px;
background: #0066ff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
}
#search {
width: 100%;
padding: 12px;
margin-bottom: 20px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 16px;
}
.notes-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.note {
background: white;
padding: 16px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.note textarea {
width: 100%;
min-height: 150px;
border: none;
resize: vertical;
font-family: inherit;
font-size: 14px;
}
.note-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 8px;
}
.delete-btn {
padding: 6px 12px;
background: #ff4444;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
Step 3: JavaScript Logic
Create app.js:
class NotesApp {
constructor() {
this.notes = this.loadNotes();
this.render();
this.attachEventListeners();
}
loadNotes() {
const saved = localStorage.getItem('notes');
return saved ? JSON.parse(saved) : [];
}
saveNotes() {
localStorage.setItem('notes', JSON.stringify(this.notes));
}
addNote() {
const note = {
id: Date.now(),
content: '',
timestamp: new Date().toISOString()
};
this.notes.unshift(note);
this.saveNotes();
this.render();
}
deleteNote(id) {
this.notes = this.notes.filter(note => note.id !== id);
this.saveNotes();
this.render();
}
updateNote(id, content) {
const note = this.notes.find(n => n.id === id);
if (note) {
note.content = content;
note.timestamp = new Date().toISOString();
this.saveNotes();
}
}
filterNotes(query) {
const filtered = this.notes.filter(note =>
note.content.toLowerCase().includes(query.toLowerCase())
);
this.render(filtered);
}
render(notesToShow = this.notes) {
const container = document.getElementById('notes-container');
if (notesToShow.length === 0) {
container.innerHTML = '<p style="grid-column: 1/-1; text-align:center; color:#999;">No notes yet. Create one!</p>';
return;
}
container.innerHTML = notesToShow.map(note => `
<div class="note">
<textarea
data-id="${note.id}"
placeholder="Write something..."
>${note.content}</textarea>
<div class="note-actions">
<button class="delete-btn" data-id="${note.id}">Delete</button>
</div>
</div>
`).join('');
// Attach textarea listeners
container.querySelectorAll('textarea').forEach(textarea => {
textarea.addEventListener('input', (e) => {
const id = parseInt(e.target.dataset.id);
this.updateNote(id, e.target.value);
});
});
// Attach delete button listeners
container.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = parseInt(e.target.dataset.id);
if (confirm('Delete this note?')) {
this.deleteNote(id);
}
});
});
}
attachEventListeners() {
document.getElementById('add-note').addEventListener('click', () => {
this.addNote();
});
document.getElementById('search').addEventListener('input', (e) => {
this.filterNotes(e.target.value);
});
// Keyboard shortcut: Ctrl/Cmd + N for new note
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
e.preventDefault();
this.addNote();
}
});
}
}
// Initialize the app
new NotesApp();
How It Works
- Data Persistence: All notes are saved to
localStorageautomatically - Real-time Updates: Changes are saved as you type (debounced for performance)
- Search: Filter notes instantly using the search bar
- Keyboard Shortcuts: Press
Ctrl+N(orCmd+Non Mac) to create a new note
Enhancement Ideas
Want to take this further? Try adding:
- Tags or categories
- Color coding
- Export notes to JSON/text
- Markdown support
- Dark mode toggle
- Cloud sync with Firebase or Supabase
Key Takeaways
- LocalStorage API is perfect for simple client-side persistence
- Event delegation keeps your code clean and performant
- Date.now() makes excellent unique IDs for simple apps
- Array methods (filter, map, find) are your best friends
Live Demo: View on CodePen Source Code: Available on GitHub
Questions? Drop a comment below or reach out on Twitter @aihub247!