How To Implement Favs In Eleventy
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:
- /2025/05/29/favs-in-eleventy/: {count: 0}
- /2025/05/28/dailydogo1279/: {count: 2}
- ...
Fav-API
We will create two endpoints:
GET /api/favs/get/{:id}
So for example: GET /api/favs/get/2025/05/29/favs-in-eleventy/
POST /api/favs/toggle {id: id, action: fav or unfav}
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);
}
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. ↩︎
-
← Previous
DailyDogo 1279 🐶 -
Next →
DailyDogo 1280 🐶