Utilities✨ Premium

Bookmark Toggle

Notched bookmark that fills with indigo and bounces elastically when tapped, with a label going from "Save" to "Saved!".

CSSToggleAnimation

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Utilities has 43 effects, including 3 free. Explore the category →

3 usage examples

Flagging a question to review during a mock exam — Bookmark Toggle example 1

① Flagging a question to review during a mock exam

WhenDuring Pradivelle's first-aid mock exam, the candidate is unsure about question 7: they pick an answer, tap the "Mark" bookmark at the top of the question, then move on. Box 7 in the grid gets a purple dot and the counter changes to "2 questions to review".
WhyHere the bookmark is a reminder inside a questionnaire: it does not file anything away for later, it flags a question to come back to before handing in. The grid and the counter follow the fx0743:change event, with no other state tracking.
Settingsaria-label="Mark question 7 to come back to it", labels "Mark" / "To review" (data-bmk-on); --bmk-size: 16px, --bmk-gap: 7px, --bmk-on: #7c3aed, --bmk-off: #7b8494, --bmk-label: #475569, --bmk-label-on: #6d28d9, --bmk-label-size: .78rem. The grid dot and the counter are scene elements updated on fx0743:change; in the capture, the scene clicks the real bookmark every 1.1 s.
"Save the recipe" on a cooking page — Bookmark Toggle example 2

② "Save the recipe" on a cooking page

WhenOn a pumpkin soup recipe, under the title, the time and the difficulty, the reader clicks "Save the recipe" to find it in their notebook; the label becomes "Recipe saved!".
WhyHere the bookmark is the page's main action: larger, framed like a button, it keeps its bounce to mark the moment the recipe joins the notebook.
Settings--bmk-size: 24px, --bmk-gap: 12px, --bmk-on: #b45309, --bmk-label-on: #b45309, --bmk-label-size: .95rem; border, white background and padding added to the button by the scene's CSS. Click every 1.2 s in the capture.
A ribbon bookmark at the top of an e-reader page — Bookmark Toggle example 3

③ A ribbon bookmark at the top of an e-reader page

WhenIn an e-reader, the reader stops at page 112; they tap the ribbon hanging at the top right of the page, which turns red, and "Page marked" shows next to it.
WhyThe notched shape is that of a real book ribbon: set against the top edge of the page, the bookmark shows the page's state with no other decoration.
SettingsRoot in position: absolute at the top right of the page; --bmk-size: 26px, --bmk-on: #b91c1c, --bmk-off: #a08c74, --bmk-label-size: .75rem; label placed left of the ribbon by the scene's CSS (flex-direction: row-reverse). Click every 1.25 s in the capture.

How it works

The bookmark is drawn in SVG: a rectangle with rounded top corners, split in a V at the bottom. The bookmark and its label sit in the same <button>: the whole area is clickable.

On click, Enter or Space, aria-pressed goes to true. The bookmark fills with indigo (#6366f1), sinks then bounces (0.4 s, cubic-bezier(.68,-.55,.265,1.55)) and stays scaled at 110 %; the label takes the data-bmk-on text and the #818cf8 colour. Another click brings back the grey and the starting label. The bounce only plays after a click, never on load for a bookmark already saved.

The label colour comes from the CSS (--bmk-label, --bmk-label-on), so it changes without touching the JavaScript. The starting state is read from aria-pressed, and every toggle fires the fx0743:change event (detail.saved) from the root, to store the favourite on the server.

Accessibility

  • Name and state: the button keeps a fixed name (aria-label="Save") and gives its state through aria-pressed: "Save, toggle button, pressed". The visible label, which changes, is hidden from screen readers so it is not read twice.
  • Keyboard: real <button>, Enter and Space, lavender ring around the bookmark and label on keyboard focus.
  • Contrast: off bookmark #70708c at 3.6:1 on the background; saved label #818cf8 at 5.7:1.
  • Reduced motion: the bookmark changes colour in place, without bounce or transition.

Browser compatibility

Inline SVG, CSS and 1.7 KB of dependency-free JavaScript. Measured in Chromium 151, Firefox 153 and WebKit 26.5 (Playwright): code pasted twice (each bookmark toggles on its own), click, Space on the keyboard, touch at 390 px, reduced motion, 0 errors, no name added to window.

Chrome 86+✓ Full (measured)
Firefox 85+✓ Full (measured)
Safari 15.4+✓ Full (measured in WebKit)
Edge 86+✓ Full (Chromium engine)
Mobile iOS 15.4+✓ WebKit engine, not checked on a device
Android Chrome 86+✓ Full (touch measured)

Without JavaScript, the grey bookmark and its label show but do not toggle. The focus ring needs :focus-visible (Chrome 86, Firefox 85, Safari 15.4); in older browsers, everything works without a ring.

The code

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

index.html — structure
<div class="fx-0743-root">
  <div class="bmk-demo">
    <button type="button" class="bmk-btn" aria-pressed="false" aria-label="Save" data-bmk-on="Saved!">
      <svg class="bmk-icon" viewBox="0 0 30 40" aria-hidden="true"><path d="M4 0h22a4 4 0 0 1 4 4v36L15 28 0 40V4a4 4 0 0 1 4-4z"></path></svg>
      <span class="bmk-label" aria-hidden="true">Save</span>
    </button>
  </div>
</div>

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

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Labels (HTML — text of .bmk-label and data-bmk-on) "Save" · "Saved!" Text at rest and once saved; keep the button's aria-label in line with the first one.
Already saved (HTML — aria-pressed="true") false Shows an existing favourite, with no bounce on load.
Bookmark size (CSS — --bmk-size) 30 px (40 px high) The height always follows the 4/3 ratio.
Bookmark colours (CSS — --bmk-off, --bmk-on) #70708c · #6366f1 Keep at least 3:1 with your background for the off bookmark.
Label (CSS — --bmk-label, --bmk-label-on, --bmk-label-size, --bmk-gap) white at 60 % · #818cf8 · .9rem · 20 px Text colours and size, gap with the bookmark.
Frame (CSS — --bmk-height, --bmk-page, --bmk-panel, --bmk-pad) 300 px · #0a0a0f · #1a1a2e · 20 px 0 and transparent to place it in a row or a card.
Server (JS — fx0743:change event) — detail.saved is true or false on every click: store or remove the favourite.

FAQ

Because the button already says it all: its fixed name ("Save") and its state ("pressed"). If the changing label were read too, users would hear two contradicting pieces of information, "Save" and "Saved!".
Yes: delete the <span class="bmk-label">; the button keeps its aria-label, so it stays named. Then plan a click area of at least 24 px, for instance with --bmk-size: 24px.
Build each button with aria-pressed="true" for the items they already saved: the script then shows the filled bookmark and the data-bmk-on label, without playing the bounce.