[object Object]

← back to Interiordesignershowroom

a11y: working skip-to-content link (focus-moving, tabindex=-1) + prefers-reduced-motion support (Cody-hardened)

3f3869c6f209127a3a0acee636680fb6f6b94656 · 2026-08-03 11:04:34 -0700 · Steve Abrams

Files touched

Diff

commit 3f3869c6f209127a3a0acee636680fb6f6b94656
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 3 11:04:34 2026 -0700

    a11y: working skip-to-content link (focus-moving, tabindex=-1) + prefers-reduced-motion support (Cody-hardened)
---
 lib/render.js       |  3 ++-
 public/css/site.css | 23 +++++++++++++++++++++++
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/lib/render.js b/lib/render.js
index e221944..d9404b0 100644
--- a/lib/render.js
+++ b/lib/render.js
@@ -257,6 +257,7 @@ function layout({ title, description, canonical, jsonld, image, body, activeNav
 ${(Array.isArray(jsonld) ? jsonld : [jsonld]).filter(Boolean).map((j) => `<script type="application/ld+json">${JSON.stringify(j)}</script>`).join('')}
 </head>
 <body>
+<a class="skip-link" href="#main-content">Skip to content</a>
 ${disclosureBar()}
 <header class="site-header">
   <a class="brand" href="/" aria-label="${esc(SITE.name)} — home">
@@ -266,7 +267,7 @@ ${disclosureBar()}
   <nav class="site-nav" aria-label="Main navigation">${nav}</nav>
   ${roomMenu}
 </header>
-<main id="main-content">${body}</main>
+<main id="main-content" tabindex="-1">${body}</main>
 <footer class="site-footer">
   <nav aria-label="Footer navigation">
     <a href="/shop">Shop</a>
diff --git a/public/css/site.css b/public/css/site.css
index b52b118..6ab360f 100644
--- a/public/css/site.css
+++ b/public/css/site.css
@@ -1823,3 +1823,26 @@ body.admin-view .card{outline:2px solid #e6c98a;outline-offset:-2px}
 .breadcrumbs a:hover { color: var(--accent); text-decoration: underline; }
 .breadcrumbs [aria-current="page"] { color: var(--ink); font-weight: 500; }
 .breadcrumbs .crumb-sep { color: var(--line); }
+
+/* ── a11y: skip-to-content link (visible only on keyboard focus) ─────────── */
+.skip-link {
+  position: absolute; left: 8px; top: -60px; z-index: 1000;
+  background: var(--ink); color: #fff; padding: 10px 16px; border-radius: 0 0 8px 8px;
+  text-decoration: none; font-size: .85rem; font-weight: 500;
+  transition: top .15s ease;
+}
+.skip-link:focus-visible, .skip-link:focus { top: 0; outline: 2px solid var(--accent); outline-offset: 2px; }
+/* the skip target is programmatically focused — no full-width ring around all of <main> */
+#main-content:focus { outline: none; }
+
+/* ── a11y: honor prefers-reduced-motion (vestibular safety) ──────────────── */
+/* Users who ask their OS to reduce motion get near-instant transitions and no
+   looping animations (spin, hotspotPulse, pulse-bg, the hamburger morph, etc.). */
+@media (prefers-reduced-motion: reduce) {
+  *, *::before, *::after {
+    animation-duration: .001ms !important;
+    animation-iteration-count: 1 !important;
+    transition-duration: .001ms !important;
+    scroll-behavior: auto !important;
+  }
+}

← 85a3efa seo+integrity: per-page OG images + saved rooms/guides/catal  ·  back to Interiordesignershowroom  ·  refine: shared fmtStamp (storefront+admin, NaN/future-safe), 81d733e →