Build a Notes App with JavaScript localStorage

Build a notes app with plain JavaScript that lets a user create, edit, delete, and permanently save notes using localStorage.

8 min read

A JavaScript notes app lets a user write a note, save it, edit it later, and delete it when it is no longer needed, with every note still there after closing and reopening the page. It builds directly on the array-and-render pattern from a todo list, but adds editing, which means updating one specific item in place instead of only adding or removing.

You will build a form for creating notes, a list that displays them, and edit and delete controls on each note. Every change is saved with localStorage so nothing is lost on refresh.

What You Will Build

The app needs to support:

  • Creating a note from a title and content input.
  • Displaying every saved note in a list.
  • Editing an existing note's content.
  • Deleting a note.
  • Keeping every note saved between visits.

Step 1: Build the HTML

Start with a form for new notes and an empty container for the list.

htmlhtml
<form id="noteForm">
  <input type="text" id="noteTitle" placeholder="Title" />
  <textarea id="noteContent" placeholder="Write a note..."></textarea>
  <button type="submit">Save Note</button>
</form>
<div id="notesList"></div>

The list container starts empty. JavaScript will build one block of markup per saved note whenever the list changes, the same rebuild-from-data approach used in a todo list app.

Step 2: Store Notes as an Array of Objects

Decide the shape of a note before writing any DOM code.

javascriptjavascript
const form = document.getElementById("noteForm");
const titleInput = document.getElementById("noteTitle");
const contentInput = document.getElementById("noteContent");
const notesList = document.getElementById("notesList");
 
let notes = [];

Each note will be an object with an id, a title, and content. The id is what makes editing possible later, since it lets a specific note be found and updated without touching any of the others.

Step 3: Render the Notes List

Write one function that clears the list and rebuilds it from the current notes array, including an edit and delete button on each note.

javascriptjavascript
function createActionButton(label, noteId, action) {
  const button = document.createElement("button");
  button.textContent = label;
  button.dataset.id = noteId;
  button.dataset.action = action;
  return button;
}

This small helper builds one button with a label, the note's id, and an action name, since the edit and delete buttons only differ by those three values.

javascriptjavascript
function createNoteCard(note) {
  const card = document.createElement("div");
  const title = document.createElement("h4");
  title.textContent = note.title;
  const content = document.createElement("p");
  content.textContent = note.content;
  card.append(
    title,
    content,
    createActionButton("Edit", note.id, "edit"),
    createActionButton("Delete", note.id, "delete")
  );
  return card;
}

The title and content are set with textContent instead of being inserted as an HTML string, since they come directly from what the user typed. Using textContent treats that text as plain text rather than markup, which matters if a note ever contains characters like < or &.

javascriptjavascript
function renderNotes() {
  notesList.innerHTML = "";
  notes.forEach((note) => notesList.appendChild(createNoteCard(note)));
}

renderNotes clears the list and appends a fresh card for every note in the array, built by the helpers above. Every other function in this app calls renderNotes after it changes the array, so the page never falls out of sync with the real data.

Step 4: Create and Delete Notes

Creating a note responds to the form submission, and deleting responds to a click on the list.

javascriptjavascript
form.addEventListener("submit", (event) => {
  event.preventDefault();
 
  notes.push({
    id: Date.now(),
    title: titleInput.value.trim(),
    content: contentInput.value.trim(),
  });
 
  form.reset();
  saveNotes();
  renderNotes();
});

Submitting the form adds a new note object to the array using the current time as a simple id, clears the form, saves, and re-renders. Deleting uses one listener on the list container instead of one per button.

javascriptjavascript
notesList.addEventListener("click", (event) => {
  const id = Number(event.target.dataset.id);
  if (event.target.dataset.action === "delete") {
    notes = notes.filter((note) => note.id !== id);
    saveNotes();
    renderNotes();
  }
});

This single listener reads which note and which action the click belongs to, then filters the matching note out of the array before saving and re-rendering. Handling every click this way is a pattern called event delegation, covered in more depth in the JavaScript event delegation guide.

Step 5: Edit an Existing Note

Editing is the one action that updates a note in place instead of adding or removing one.

javascriptjavascript
notesList.addEventListener("click", (event) => {
  const id = Number(event.target.dataset.id);
  if (event.target.dataset.action === "edit") {
    const newContent = prompt("Edit your note:");
    if (newContent === null) return;
 
    notes = notes.map((note) =>
      note.id === id ? { ...note, content: newContent } : note
    );
    saveNotes();
    renderNotes();
  }
});

This handler asks for new content with a simple prompt, then builds a new array where only the matching note gets its content replaced. Every other note in the array stays exactly as it was, which is why matching on id matters so much in this app.

Step 6: Save and Load Notes

Saving after every change makes the notes app remember its data between visits.

javascriptjavascript
function saveNotes() {
  localStorage.setItem("notes", JSON.stringify(notes));
}
 
function loadNotes() {
  const saved = localStorage.getItem("notes");
  notes = saved ? JSON.parse(saved) : [];
  renderNotes();
}
 
loadNotes();

Saving converts the notes array to a string before writing it to storage, and loading reverses that conversion when the page starts. For more on storing structured data like this, see the storing complex objects in localStorage guide.

Common Mistakes

MistakeWhy it breaksFix
Editing a note by searching the DOM instead of the arrayThe change is lost the next time the list re-rendersUpdate the matching object in the array first, then re-render
Forgetting to save after editing or deletingRefreshing the page brings back the old, unsaved versionCall the save function after every change, not just after creating a note
Using array position instead of an id to find a noteDeleting or reordering notes shifts positions and targets the wrong oneAlways match on a stable id, never on array index

Next Step

You now have the full create, edit, delete, and save pattern used across most small JavaScript apps. If you have not built the JavaScript calculator yet, it is a good next project for practicing state tracking without a list involved.

Rune AI

Rune AI

Key Insights

  • Store each note as an object with an id, title, and content, not as a single block of text.
  • Use one render function that rebuilds the notes list from the array after every change.
  • Editing a note means updating the matching object in the array, then saving and re-rendering.
  • Save the notes array to localStorage after every change so nothing is lost on refresh.
  • Load saved notes once when the page starts so the list is ready immediately.
RunePowered by Rune AI

Frequently Asked Questions

Why does each note need its own id?

An id lets the app find one exact note to edit or delete without affecting any other note. Without an id, the app would have no reliable way to tell two notes apart.

Is localStorage a good place to store notes long term?

For a small personal project, yes. localStorage keeps data in the browser until it is cleared, but it is tied to one browser and device, so it is not a substitute for saving notes to a real account or server.

Why re-render the whole notes list instead of updating one note in place?

Rebuilding the list from the notes array every time keeps the page and the underlying data from ever drifting apart, which matters more than the small performance cost for a list this size.

Conclusion

A notes app combines everything from the earlier mini projects: an array of objects as the source of truth, a render function that rebuilds the page from that array, and localStorage to make changes permanent. This create, edit, delete, and save pattern is the foundation of most small data-driven apps you will build next.