webdevpuneet.com

Bookmark Toggle — Free Save Button HTML CSS JS Snippet

Bookmark Toggle · Buttons · Plain HTML, CSS & JS · Live preview

CategoryButtons

Copy & paste

Source Code

<div class="bk-list" id="bkList"></div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Outline-to-filled icon
One SVG path morphs via an .on class — no icon swap.
Spring pop
An overshoot cubic-bezier gives a tactile save snap.
Reliable replay
The reflow trick restarts the animation every save.
Optimistic count
The number updates instantly on tap, ±1.
Abbreviated counts
fmt() shows 1.2k-style figures for large numbers.
Accessible toggle
Real button with aria-pressed and aria-label.
Per-item state
Each row owns its data in a local closure.
No dependency
Pure HTML/CSS/JS — no icon library.

About this UI Snippet

Bookmark Toggle — Animated Save Button with Count

Screenshot of the Bookmark Toggle snippet rendered live

A bookmark toggle is the save-for-later button beside an article, product, or post — the flag or ribbon that fills when you tap it and tracks how many people saved the item. This snippet builds a polished, accessible one in plain HTML, CSS, and vanilla JavaScript: an outline-to-filled icon with a springy pop animation and an optimistic save count, all with no dependency.

Outline-to-filled with one SVG path

The icon is a single <path> whose fill is none by default and switches to the accent color when saved — toggled purely by an .on class on the button. There's no icon swap or second graphic: the same flag morphs from outline to solid via CSS, which keeps the markup tiny and the transition (fill .2s) clean.

A spring "pop" on save

When you bookmark an item the icon plays a bkPop keyframe — scaling up past 1 then settling — using a cubic-bezier(.34,1.56,.64,1) overshoot ease that gives it a tactile, rewarding snap. The animation is restarted reliably with the void btn.offsetWidth reflow trick so it replays on every save, and it only fires on the save direction (not un-save) to reinforce the positive action.

Optimistic count updates

Clicking flips item.saved and immediately adjusts item.saves by ±1, then re-renders — the count moves the instant you tap rather than waiting for a server round-trip. This optimistic pattern is what makes save buttons feel responsive; in production you'd POST the change and only roll back if the request fails. A fmt() helper abbreviates large counts (1240 → 1.2k) the way social UIs do.

Accessible toggle semantics

Each button is a real <button> with aria-label and an aria-pressed that reflects the saved state, so screen readers announce "Bookmark, pressed" and keyboard users can Tab and toggle with Enter or Space — no roles or key handling to wire up.

Per-item encapsulation

Each row builds its own paint() closure over its data, so state stays local to the item and there's no shared lookup or re-render of the whole list on every click. That structure scales to long feeds and maps directly onto a component model where each card owns its saved state.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA list of items renders, each with a save count and a bookmark button.
  2. 2
    Click the bookmarkThe flag fills with color and pops with a springy animation.
  3. 3
    Watch the countThe save count increments immediately as an optimistic update.
  4. 4
    Click again to un-saveThe flag returns to an outline and the count decrements.
  5. 5
    Tab and press EnterThe button is keyboard operable with an aria-pressed state.
  6. 6
    Wire to your APIPOST the change inside the click handler and roll back on error.

Real-world uses

Common Use Cases

Save for later reading lists
Let readers bookmark articles beside an article card, with an outline flag that fills through an .on class on the same SVG path.
Wishlist alternatives
Offer a flag-style option instead of a heart like a favorite button, with an optimistic saved count that updates instantly.
Feeds and content lists
Save posts in a social post card, with a spring pop from an overshooting cubic-bezier giving a tactile save snap.
Product saves
Let shoppers save items from a product card, with a reflow trick restarting the animation on every save.
Optimistic state learning
Study a reference for optimistic toggle buttons, where aria-pressed conveys state and the displayed number moves plus or minus one.
Related: Contact Picker Button
See the Contact Picker Button for a related buttons pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

The bookmark is a single SVG path with fill: none. Adding an .on class to the button sets fill to the accent color with a short CSS transition, so the same outline shape becomes solid. There's no second icon or image — one path handles both states.

Clicking immediately flips the saved flag and adjusts the count by plus or minus one before any network call, so the number reacts the instant you tap. In production you'd send the change to your server in the same handler and only revert if the request fails, which keeps the UI feeling instant.

The pop is a positive reinforcement for the save action, so paint() triggers it only when the new state is saved, not when un-saving. It's restarted with the void offsetWidth reflow trick so it replays reliably on every save rather than only the first time.

Yes. Each control is a native button with an aria-label and an aria-pressed attribute that mirrors the saved state, so assistive tech announces it as a pressed or unpressed toggle. Being a real button, it's focusable and operable with Enter or Space without any extra key handling.

Hold the saved flag and count in state per item and bind the .on class and aria-pressed to it. Do the optimistic ±1 in the click handler alongside your API call. For the pop, toggle an animation class with a key change or by removing and re-adding it. In Tailwind, switch the path fill with a conditional class on the saved state.