Skip to main content
Martin Hähnel

How To Implement Favs In Eleventy

⚠️ Heads-Up! ⚠️

🚧 Even though the functionality now exists and works, this post itself is a work-in-progress. 🚧

An easy way to implement favs - favorites, likes, kudos or whatever you want to call them - is by using a little bit of HTML, CSS and JS, a simple API with two routes and a key-value store.[1]

Overview

What is a simple fav system at its essence? Under every post is an interactive element, that allows readers to "fav" a post. "Faving" is an anonymous action that just increments - or in the case of "unfaving" decrements a counter on that post.

Anyone who's reading a post can click a button and the counter is either going up by one, or down by one.

We remember if somebody has faved a post by writing to their local storage. That also means that we don't need to save any data about the user. Which is good from a privacy perspective.

Key Value Store

There isn't lots to say about this one, except we should talk about the question how do we even refer to a post, we need some kind of ID. But in eleventy we do not have a database table with rows for our posts we could use. Instead we will use the url (minus the domain). So our KV-Store would looks something like this:

Fav-API

We will create two endpoints:

So for example: GET /api/favs/get/2025/05/29/favs-in-eleventy/

So for example: POST /api/favs/toggle with this JSON:

{
	"id": "/2025/05/29/favs-in-eleventy/",
	"action": "fav" //or "unfav"
}

The get endpoint return the current count for a given ID, so we can display it and the post creates and/or either increments or decrements the current favcount for a given ID.

Frontend

Template

To show the fav button on all posts we just add it to the post.njk which is used by default to display blogposts:

<div class="post-footer">
  <button data-fav-button title="Fav this post!" class="fav-button" aria-label="Add to favorites">
    <span class="star" aria-hidden="true">☆</span>
    <span class="count" aria-live="polite">0</span>
  </button>
</div>
CSS

Let's have a look at the css file first:

:root {
  --gold-star: #f5c518;
}

.fav-button {
  cursor: pointer;
  background: none;
  border: none;
  font-size: 1.2em;
  color: var(--color-gray-50);
  display: inline-flex;
  align-items: center;
  gap: 0.2em;
}

.fav-button .star {
  font-size: 1.5em;
  transition: color 0.2s ease;
}

.fav-button.faved {
  color: var(--gold-star);
}

.fav-button:hover {
  background: none;
}

.fav-button:hover .star {
  color: var(--gold-star);
}

And the post-footer class is in the main index.css file and looks like this:

/* Post Footer (favs) */
.post-footer {
  display: flex;
  justify-content: center;
}
JS

And here's the JavaScript we use to wire up the fav button:

document.addEventListener("DOMContentLoaded", () => {
  setupFavButton();
});

function setupFavButton() {
  const button = document.querySelector(".fav-button");
  if (!button) {
    return;
  }

  const postId = window.location.pathname.replace(/\/$/, "");
  const localKey = `fav_${postId}`;
  const countEl = button.querySelector(".count");
  const starEl = button.querySelector(".star");

  async function updateCount() {
    try {
      const res = await fetch(`/api/favs/get${postId}`);
      const data = await res.json();
      if (countEl) countEl.textContent = data.count.toString();
    } catch (err) {
      console.error("Failed to fetch fav count", err);
    }
  }

  async function toggleFav() {
    const faved = localStorage.getItem(localKey) === "true";
    const action = faved ? "unfav" : "fav";

    try {
      const res = await fetch("/api/favs/toggle", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ id: postId, action }),
      });

      const data = await res.json();
      if (countEl) countEl.textContent = data.count.toString();

      // update state
      localStorage.setItem(localKey, action === "fav" ? "true" : "false");
      updateVisual(action === "fav");
    } catch (err) {
      console.error("Failed to toggle fav", err);
    }
  }

  function updateVisual(faved) {
    button.classList.toggle("faved", faved);
    if (starEl) starEl.textContent = faved ? "★" : "☆";
  }

  // initial state
  const faved = localStorage.getItem(localKey) === "true";
  updateVisual(faved);
  updateCount();

  button.addEventListener("click", toggleFav);
}

  1. I will be using Cloudflare's tools, as this blog is (still) based on their developer platform, but it shouldn't be too hard to adapt this to your setup. Hopefully. ↩︎