08 - Build a Notes App
Project Overview
Build a fully-functional notes app using vanilla JavaScript. This project combines everything you've learned: DOM manipulation, events, arrays, objects, and localStorage.
Features
- ✅ Create, edit, and delete notes
- ✅ Search/filter notes
- ✅ Pin important notes
- ✅ Save to localStorage (persists after page reload)
- ✅ Clean, modern UI
Step 1: HTML Structure
Create index.html:
<!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="container">
<header>
<h1>📝 My Notes</h1>
<input
type="text"
id="search"
placeholder="Search notes..."
class="search-input"
/>
</header>
<div class="add-note">
<input
type="text"
id="note-title"
placeholder="Note title..."
class="note-input"
/>
<textarea
id="note-content"
placeholder="Note content..."
class="note-textarea"
rows="4"
></textarea>
<button id="add-btn" class="btn btn-primary">Add Note</button>
</div>
<div id="notes-container" class="notes-grid"></div>
</div>
<script src="app.js"></script>
</body>
</html>Step 2: CSS Styling
Create style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 30px;
}
h1 {
color: white;
font-size: 2.5rem;
margin-bottom: 20px;
}
.search-input {
width: 100%;
max-width: 500px;
padding: 12px 20px;
border: none;
border-radius: 25px;
font-size: 16px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.add-note {
background: white;
padding: 25px;
border-radius: 15px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.note-input, .note-textarea {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
margin-bottom: 15px;
font-family: inherit;
transition: border-color 0.3s;
}
.note-input:focus, .note-textarea:focus {
outline: none;
border-color: #667eea;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn-primary {
background: #667eea;
color: white;
}
.notes-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.note-card {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
}
.note-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.note-card.pinned {
border: 2px solid #ffd700;
background: #fffef0;
}
.note-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 10px;
color: #333;
word-break: break-word;
}
.note-content {
color: #666;
line-height: 1.5;
margin-bottom: 15px;
word-break: break-word;
}
.note-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn-small {
padding: 6px 12px;
font-size: 14px;
}
.btn-secondary {
background: #f0f0f0;
color: #333;
}
.btn-warning {
background: #ffc107;
color: white;
}
.btn-danger {
background: #dc3545;
color: white;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: white;
font-size: 1.25rem;
}Step 3: JavaScript — Data Structure
Create app.js and start with the data structure:
// Notes array — will hold all notes
let notes = [];
// Load from localStorage on page load
function loadNotes() {
const stored = localStorage.getItem("notes");
notes = stored ? JSON.parse(stored) : [];
renderNotes();
}
// Save to localStorage
function saveNotes() {
localStorage.setItem("notes", JSON.stringify(notes));
}
// Note structure
function createNote(title, content) {
return {
id: Date.now(),
title,
content,
pinned: false,
createdAt: new Date().toISOString()
};
}Step 4: Add Note Functionality
// DOM elements
const titleInput = document.getElementById("note-title");
const contentInput = document.getElementById("note-content");
const addBtn = document.getElementById("add-btn");
const notesContainer = document.getElementById("notes-container");
const searchInput = document.getElementById("search");
// Add note
addBtn.addEventListener("click", () => {
const title = titleInput.value.trim();
const content = contentInput.value.trim();
if (!title || !content) {
alert("Please fill in both title and content");
return;
}
const note = createNote(title, content);
notes.unshift(note); // add to beginning
saveNotes();
renderNotes();
// Clear inputs
titleInput.value = "";
contentInput.value = "";
titleInput.focus();
});
// Allow Enter key in title to focus content
titleInput.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
contentInput.focus();
}
});Step 5: Render Notes
function renderNotes(filter = "") {
// Sort: pinned first, then by date
const sortedNotes = [...notes].sort((a, b) => {
if (a.pinned && !b.pinned) return -1;
if (!a.pinned && b.pinned) return 1;
return new Date(b.createdAt) - new Date(a.createdAt);
});
// Filter by search term
const filteredNotes = sortedNotes.filter((note) => {
const searchTerm = filter.toLowerCase();
return (
note.title.toLowerCase().includes(searchTerm) ||
note.content.toLowerCase().includes(searchTerm)
);
});
// Render
if (filteredNotes.length === 0) {
notesContainer.innerHTML = `
<div class="empty-state">
${filter ? "No notes found" : "No notes yet. Create your first note!"}
</div>
`;
return;
}
notesContainer.innerHTML = filteredNotes
.map((note) => createNoteHTML(note))
.join("");
// Attach event listeners
attachNoteListeners();
}
function createNoteHTML(note) {
return `
<div class="note-card ${note.pinned ? 'pinned' : ''}" data-id="${note.id}">
<h3 class="note-title">${escapeHTML(note.title)}</h3>
<p class="note-content">${escapeHTML(note.content)}</p>
<div class="note-actions">
<button class="btn btn-small btn-warning pin-btn">
${note.pinned ? '📌 Unpin' : '📌 Pin'}
</button>
<button class="btn btn-small btn-secondary edit-btn">✏️ Edit</button>
<button class="btn btn-small btn-danger delete-btn">🗑️ Delete</button>
</div>
</div>
`;
}
// Escape HTML to prevent XSS
function escapeHTML(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}Step 6: Note Actions
function attachNoteListeners() {
// Pin/Unpin
document.querySelectorAll(".pin-btn").forEach((btn) => {
btn.addEventListener("click", (e) => {
const id = parseInt(e.target.closest(".note-card").dataset.id);
togglePin(id);
});
});
// Edit
document.querySelectorAll(".edit-btn").forEach((btn) => {
btn.addEventListener("click", (e) => {
const id = parseInt(e.target.closest(".note-card").dataset.id);
editNote(id);
});
});
// Delete
document.querySelectorAll(".delete-btn").forEach((btn) => {
btn.addEventListener("click", (e) => {
const id = parseInt(e.target.closest(".note-card").dataset.id);
deleteNote(id);
});
});
}
function togglePin(id) {
const note = notes.find((n) => n.id === id);
if (note) {
note.pinned = !note.pinned;
saveNotes();
renderNotes();
}
}
function deleteNote(id) {
if (confirm("Delete this note?")) {
notes = notes.filter((n) => n.id !== id);
saveNotes();
renderNotes();
}
}
function editNote(id) {
const note = notes.find((n) => n.id === id);
if (!note) return;
const newTitle = prompt("Edit title:", note.title);
if (newTitle === null) return; // cancelled
const newContent = prompt("Edit content:", note.content);
if (newContent === null) return; // cancelled
if (newTitle.trim() && newContent.trim()) {
note.title = newTitle.trim();
note.content = newContent.trim();
saveNotes();
renderNotes();
}
}Step 7: Search Functionality
// Real-time search
searchInput.addEventListener("input", (e) => {
renderNotes(e.target.value);
});Step 8: Initialize App
// Load notes when page loads
document.addEventListener("DOMContentLoaded", () => {
loadNotes();
titleInput.focus();
});Complete Code Structure
Your final app.js should have this structure:
// 1. State
let notes = [];
// 2. Data functions
function createNote(title, content) { /* ... */ }
function loadNotes() { /* ... */ }
function saveNotes() { /* ... */ }
// 3. DOM elements
const titleInput = document.getElementById("note-title");
const contentInput = document.getElementById("note-content");
// ... etc
// 4. Event handlers
addBtn.addEventListener("click", () => { /* ... */ });
searchInput.addEventListener("input", () => { /* ... */ });
// 5. Render functions
function renderNotes(filter = "") { /* ... */ }
function createNoteHTML(note) { /* ... */ }
// 6. Actions
function togglePin(id) { /* ... */ }
function editNote(id) { /* ... */ }
function deleteNote(id) { /* ... */ }
function attachNoteListeners() { /* ... */ }
// 7. Initialize
document.addEventListener("DOMContentLoaded", () => {
loadNotes();
titleInput.focus();
});Try It Out
Open index.html in your browser and:
- ✅ Create a few notes
- ✅ Pin your favorite
- ✅ Search for specific notes
- ✅ Edit and delete notes
- ✅ Refresh the page — notes persist!
Bonus Features
Try adding these on your own:
- Categories/Tags — organize notes by topic
- Color coding — let users pick colors for notes
- Export — download notes as JSON or text file
- Markdown support — render markdown in notes
- Drag and drop — reorder notes
- Dark mode — toggle dark/light theme
What You've Built
This project demonstrates:
- DOM Manipulation — creating and updating elements
- Event Handling — buttons, inputs, keyboard events
- Data Management — CRUD operations (Create, Read, Update, Delete)
- localStorage — persisting data in the browser
- Array Methods — map, filter, sort, find
- Functions — organizing code into reusable pieces
- Modern JavaScript — template literals, destructuring, arrow functions
Congratulations! You've built a complete, functional web application with vanilla JavaScript.