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:

  1. ✅ Create a few notes
  2. ✅ Pin your favorite
  3. ✅ Search for specific notes
  4. ✅ Edit and delete notes
  5. ✅ 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.