Stop Shipping Spinners: A No-JS Back-to-Top That Actually Respects Users
AI, culture, and friction as a feature. Notes from the edge of the interface. Originals live at vibeaxis.com — weekly Reality Patch Notes. Ship it → vibeaxis.com/subscribe
Micro-UX > macro hand-waving. Here’s how to build something useful without duct-taping another script to your site.
We threw a dart at “modern UX” and hit a loading spinner. Again. If your site needs a whole JS stack to scroll to the top, congrats—you’ve optimized vibes, not users.
This is about restraint. You don’t need React to move 44px of UI. You need intent, a click target, and some CSS that doesn’t fight gravity.
The constraint (and why it matters)
Must work offline (no CDNs, no build step).
Must respect a11y (keyboard + screen readers).
Must add ~0 ms runtime overhead.
Must show only when the page is long enough to be useful.
The pattern
One anchor at the top.
One anchor button at the bottom (mobile only).
Smooth scroll if the browser supports it; zero drama if it doesn’t.
HTML at the top of <body>
<div id="va-top" aria-hidden="true"></div>
Back-to-top link (footer or injected)
<a href="#va-top" class="va-backtop" aria-label="Back to top">
<!-- inline SVG arrow, aria-hidden="true" -->
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path d="M12 5l-7 7h4v7h6v-7h4l-7-7z"/>
</svg>
</a>
CSS (tiny, mobile-first)
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
.va-backtop {
position: fixed;
right: max(16px, env(safe-area-inset-right));
bottom: max(16px, calc(env(safe-area-inset-bottom) + 16px));
width: 44px; height: 44px; border-radius: 999px;
display: grid; place-items: center; z-index: 9999;
background: #0a0a0a; color: #fff;
border: 1px solid rgba(255,255,255,.08);
box-shadow: 0 6px 20px rgba(0,0,0,.25);
text-decoration: none;
}
/* Hide on wider screens (sticky header already exists) */
@media (min-width: 900px) { .va-backtop { display: none; } }
That’s it. No event listeners. No “debounced scroll observers.” Just a link that… links. If your browser can’t smooth-scroll, it still works. If the user disables motion, it respects them. If JS dies, it doesn’t.
But when does it show?
On our site, we render it only on long posts (e.g., >900 words). That’s a server-side check so the button doesn’t exist when it isn’t needed. Micro-UX means contextual, not ubiquitous.
A11y sanity
It’s a link, so keyboard users already know how to use it.
aria-label="Back to top"announces purpose without visual clutter.Contrast is high; size is finger-friendly. Basic respect.
Why “no-JS” isn’t a gimmick
Because most “tiny” scripts become permanent residents. One day it’s a scroll helper; tomorrow it’s analytics, animations, and some mystery polyfill squatting in your render path. Removing code is a feature.
Micro-UX is the art of shipping one job done well. You can layer JS later if you can prove it adds value. Start with the dumb version that always works.
When JS is worth it
Scroll-aware visibility (e.g., only show after 600px scrolled).
Intersection-based tweaks (e.g., hide when a sticky header overlaps).
Analytics (if you’re measuring interaction cost/benefit).
Just don’t default to a framework because your thumb twitched.
Steal this for other problems
“Jump to comments”
“Skip to section”
“Return to reading position”
All of those can start life as anchors + CSS. When you need enhancements, enhance. Until then, don’t rent a bulldozer to plant a houseplant.
TL;DR
You don’t need JS for everything.
Links work. CSS works. Users notice speed more than sparkle.
Micro-UX is subtraction with taste.
If you want a production-ready WordPress version, we open-sourced ours as a tiny plugin. It’s mobile-only by default and respects reduced motion. https://vibeaxis.com/vax-micro-ux/