# Back-to-Top Button — Design Spec **Date:** 2026-04-20 **Status:** Approved --- ## Context The TODO list includes "add back-to-top button". Article pages can be long and users currently have no quick way to return to the top. The button should integrate cleanly with the existing hacker-aesthetic theme, follow Alpine.js patterns already in use, and meet WCAG 2.1 AA accessibility standards. --- ## Design Decisions | Decision | Choice | Rationale | |---|---|---| | Position | Fixed bottom-right | Conventional, expected UX | | Shape | Circle solid | Consistent with theme energy; always-visible purple matches accent | | Trigger | 33% scroll depth | Balanced — not too early, not too late | | Animation | Slide up + fade (300ms) | Reuses existing `slideUp` keyframe | | Pages | `.Kind "page"` only | Articles and singles — same condition as reading progress bar | | Implementation | Inline Alpine.js in a partial | Consistent with `social-share.html`, `header.html` patterns | --- ## Behaviour - **Appears** when `window.scrollY / (document.documentElement.scrollHeight - window.innerHeight) >= 0.33` - **Disappears** when scroll drops back below 33% - **On click**: smooth-scrolls to top (`window.scrollTo({ top: 0, behavior: 'smooth' })`) - **Reduced motion**: CSS global `prefers-reduced-motion` rule already suppresses all animations; the button still appears/disappears (via Alpine `x-show`) but without the slide animation. The `smooth` scroll behavior should also be skipped — handled in the click handler by checking `window.matchMedia('(prefers-reduced-motion: reduce)').matches`. - **z-index**: `z-40` — sits below toasts (`z-50`), modals (`z-50`), and reading progress bar (`z-9999`). Toasts stack above it naturally. --- ## Visual Spec ``` Position: fixed bottom-6 right-6 (24px from edges) Size: 44×44px circle Default: bg: var(--accent) #a855f7, glow: box-shadow 0 0 12px rgba(168,85,247,0.4) Hover: bg: #9333ea (one shade darker), glow: box-shadow 0 0 20px var(--accent) Icon: Feather Icons `chevron-up` (already loaded globally via CDN) Entrance: slideUp keyframe, 300ms ease-out Exit: opacity fade (Alpine x-show transition), 200ms ``` --- ## Accessibility - `aria-label="Back to top"` on the button element - `role="button"` implicit (native ` ``` **`.back-to-top` CSS class** (added to `main.css` `@layer components`): ```css .back-to-top { @apply fixed bottom-6 right-6 z-40 w-11 h-11 rounded-full flex items-center justify-center; background: var(--accent); box-shadow: 0 0 12px rgba(168, 85, 247, 0.4); transition: background 200ms ease, box-shadow 200ms ease; color: #fff; } .back-to-top:hover { background: #9333ea; box-shadow: 0 0 20px var(--accent); } .back-to-top:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } ``` --- ## Verification 1. Start Hugo dev server (`hugo server`) 2. Open any article page 3. Scroll past 33% — button slides up from bottom-right 4. Scroll back up — button disappears 5. Click button — smooth scroll to top 6. Tab to button — focus ring visible 7. Press Enter while focused — smooth scroll to top 8. Test with `prefers-reduced-motion: reduce` in browser devtools — button appears without animation, click scrolls instantly 9. Confirm toasts (if triggered) render above the button 10. Check on mobile (narrow viewport) — button sits at bottom-right without overlapping content