Hover✨ Premium

Bookmark Toggle

A bookmark button whose ribbon drops into place on click with a slight bounce, the background fills and the icon switches from outline to solid — a save animation in pure CSS with visual haptic feedback.

CSSToggleAnimation

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Hover has 48 effects, including 5 free. Explore the category →

3 usage examples

Saving a job posting — job-search dashboard for an engineering recruitment agency — Bookmark Toggle example 1

① Saving a job posting — job-search dashboard for an engineering recruitment agency

WhenEach listing has a bookmark button in its corner; on click the ribbon drops into place to signal the offer is saved. 40×48 px button, flush with the card edge.
WhyThe drop animation physically mimics sliding a bookmark into a folder, immediately legible without explanation.
Settings40×48 px button in the corner of each job card, dense list on a dark background.
Save an article — header of an article detail page (Quorvande) — Bookmark Toggle example 2

② Save an article — header of an article detail page (Quorvande)

WhenAt the bottom of an article page header (Quorvande): a large 48×48 px button with the label "Save article" lets the reader save the piece in one click. The ribbon drops and the label turns purple.
WhyA large named button makes the action explicit where space allows; the drop animation visually confirms the save without a pop-up.
Settings48×48 px button with text label, placed at the bottom of the article header on a dark background.
Bookmark on thumbnails in an inspiration board — 3×2 grid — Bookmark Toggle example 3

③ Bookmark on thumbnails in an inspiration board — 3×2 grid

WhenIn a grid-based inspiration board, each coloured tile carries a small 30×30 px bookmark button overlaid in the top-right corner; on click, the ribbon activates to signal the image is saved.
WhyThe icon placed directly on the tile visually ties the button to the image and keeps the grid dense; the drop animation confirms the action without interrupting browsing.
Settings30×30 px button overlaid on each tile, semi-transparent black background, 3-column × 2-row grid.

How it works

The .fx-0362-root root (250 px tall, rgb(26, 26, 36) background) centers two bookmark buttons side by side in .mi-bookmark-scene. Each .mi-bookmark-btn button (40×48 px) holds a ribbon SVG whose edges are drawn in stroke and interior colored by fill. A ::before pseudo-element forms the fill background, initially off-screen above the button (translateY(-100%)).

On click, the .saved class is added to the button, triggering three simultaneous transitions. Background: the ::before drops from translateY(-100%) to translateY(0) over 0.4 s with a cubic-bezier(0.34, 1.56, 0.64, 1) that gives a slight overshoot before settling. SVG: stroke and fill transition from outline color to opaque white over 0.25 s. Label: the adjacent text color shifts to its active value. A confirmation tick animation (@keyframes savedCheck) draws 0.2 s after the background starts, so the three changes feel sequential rather than simultaneous.

The script finds every .fx-0362-root, wires the buttons inside each root, and updates aria-pressed on each click. The initial state is read from the .saved class already present in the HTML: a preloaded saved button already carries aria-pressed="true". The two buttons in the demo are independent and states do not propagate between component copies.

Accessibility

  • Keyboard: each bookmark button is a real <button>; Tab reaches it, Space or Enter toggles it. The focus outline (:focus-visible) appears on keyboard navigation only.
  • Announced state: aria-pressed goes from false to true on click. The aria-label (Save / Favorites depending on the button) describes the action; the screen reader announces pressed or not pressed.
  • Reduced motion: under prefers-reduced-motion: reduce, the translateY transition on the background is removed and the @keyframes savedCheck animation is disabled. The saved state stays readable through the background and icon color change.
  • Touch: buttons respond normally to touch events. Their 40×48 px target meets the recommended minimum (44×44 px).

Browser compatibility

Pure CSS (transitions, pseudo-elements, overshooting cubic-bezier) + 0.4 KB of JavaScript (classList, aria-pressed), no dependency. Verified in Chromium 151: drop animation, fill, two independent buttons, two independent component copies, aria-pressed updated, 0 errors. The preview captures show the animation auto-replayed by the scene code: this behaviour is not included in the sold code.

Chrome 49+✓ Full (measured)
Firefox 55+✓ Full
Safari 9.1+✓ Full
Edge 79+✓ Full (Chromium engine)
Mobile iOS 9.3+✓ Full
Android Chrome 49+✓ Full (Chromium engine)

Without JavaScript, the button is read-only (state can no longer be toggled). Without CSS transitions, the background and icon change color instantly with no drop animation.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0362-root">
  <div class="mi-bookmark-scene">
    <button class="mi-bookmark-btn" aria-pressed="false" aria-label="Save">
      <svg viewBox="0 0 24 24">
        <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
      </svg>
    </button>
    <span class="mi-bookmark-text">Save</span>
  </div>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Active background color (CSS — background on .mi-bookmark-btn::before and .mi-bookmark-btn.saved::before) Indigo blue #6366f1 Switch to #f59e0b (amber) or #10b981 (emerald). Keep the active SVG color (.mi-bookmark-btn.saved svg) and label (.mi-bookmark-btn.saved ~ .mi-bookmark-text) consistent.
Drop bounce (CSS — cubic-bezier on the ::before transform transition) cubic-bezier(0.34, 1.56, 0.64, 1) (slight overshoot) ease-out gives a clean drop with no bounce; raise the second parameter above 1 (e.g. 1.8) for a more pronounced rebound.
Button size (CSS — width / height of .mi-bookmark-btn) 40×48 px Reduce to 32×40 px for a dense list; enlarge to 48×56 px for a standalone use.
Number of buttons in the demo (HTML — number of .mi-bookmark-btn in the scene) 2 buttons Add or remove buttons in .mi-bookmark-scene. Each button must have its own aria-label describing the item to save.
Pre-saved state (HTML — .saved class on the button) No button saved on load Add .saved directly in the HTML to show a button as already saved on page load; the JS reads this state and sets aria-pressed accordingly.

FAQ

Yes for the unsaved→saved transition: the .saved class is added and the drop animation replays. But the reverse (saved→unsaved) has no exit animation by default: the background disappears instantly. To add a removal animation, declare @keyframes on class removal or use animation-direction: reverse.
Yes, by adding the .saved class directly in the HTML. The script reads the initial class state and sets aria-pressed accordingly: aria-pressed="true" on a button already marked .saved.
No. Each button has its own .saved class state and its own aria-pressed. One can be saved while the other is not, independently. Likewise, two copies of the component on the same page have entirely separate states.