From d08007cdd7665edaa8b7aa0745a80e349eed3656 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Thu, 26 Mar 2026 19:51:21 -0400 Subject: [PATCH] audit pass 6: accessibility, ARIA, and keyboard fixes - JS: fix checkbox/radio required validation using .checked not .value - JS: guard _cpTrigger.focus() with document.contains() check - JS: add arrow/Home/End key navigation to tab groups (WCAG 2.1) - JS: clamp context menu left edge with Math.max(8, ...) to prevent off-screen - JS: fix wizard _show() to removeAttribute aria-hidden on active step - HTML: add role="region" + aria-label to notification panel - HTML: convert Assigned To span+div to label+select with for/id association - HTML: add role="article" tabindex="0" aria-label to all kanban cards - HTML: remove aria-hidden="false" anti-pattern from wizard active step - CSS/HTML/JS: replace aria-hidden="false" show-hook with :not([aria-hidden]) so open state is represented by absent attribute rather than false value Co-Authored-By: Claude Sonnet 4.6 --- base.css | 52 ++++++++++++++++++++++++++++++++++++++----- base.html | 66 ++++++++++++++++++++++++++++--------------------------- base.js | 28 ++++++++++++++++++----- 3 files changed, 102 insertions(+), 44 deletions(-) diff --git a/base.css b/base.css index 1f37600..cd94c17 100644 --- a/base.css +++ b/base.css @@ -1792,6 +1792,7 @@ select option:checked { transition: transform 0.2s ease, opacity 0.2s ease; box-shadow: var(--glow-cyan); } +.lt-menu-btn:active { opacity: 0.7; } .lt-menu-btn:focus-visible { outline: 1px solid var(--accent-cyan); outline-offset: 2px; } .lt-menu-btn.open span:nth-child(1) { transform: translateY(6px) rotate(45deg); } .lt-menu-btn.open span:nth-child(2) { opacity: 0; } @@ -2472,7 +2473,9 @@ select option:checked { transform: translateX(-50%) translateY(4px); } [data-tooltip]:hover::before, -[data-tooltip]:hover::after { +[data-tooltip]:focus-visible::before, +[data-tooltip]:hover::after, +[data-tooltip]:focus-visible::after { opacity: 1; transform: translateX(-50%) translateY(0); } @@ -2490,7 +2493,9 @@ select option:checked { transform: translateX(-50%) translateY(-4px); } [data-tooltip][data-tooltip-pos="bottom"]:hover::before, -[data-tooltip][data-tooltip-pos="bottom"]:hover::after { +[data-tooltip][data-tooltip-pos="bottom"]:focus-visible::before, +[data-tooltip][data-tooltip-pos="bottom"]:hover::after, +[data-tooltip][data-tooltip-pos="bottom"]:focus-visible::after { transform: translateX(-50%) translateY(0); } @@ -2551,6 +2556,12 @@ select option:checked { box-shadow: var(--glow-orange); font-weight: 700; } +.lt-page-btn:focus-visible { + outline: 2px solid var(--accent-cyan); + outline-offset: 1px; + border-color: var(--accent-cyan); + color: var(--accent-cyan); +} .lt-page-btn:disabled, .lt-page-btn[aria-disabled="true"] { opacity: 0.35; @@ -2583,7 +2594,8 @@ select option:checked { width: 100%; text-align: left; } -.lt-accordion-header:hover { background: var(--bg-tertiary); color: var(--accent-cyan); } +.lt-accordion-header:hover { background: var(--bg-tertiary); color: var(--accent-cyan); } +.lt-accordion-header:active { background: var(--bg-tertiary); opacity: 0.8; } .lt-accordion-header:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; } .lt-accordion-header[aria-expanded="true"] { color: var(--accent-orange); } .lt-accordion-icon { @@ -2641,6 +2653,7 @@ select option:checked { transition: color 0.15s; } .lt-alert-close:hover { color: var(--accent-red); } +.lt-alert-close:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; } .lt-alert.dismissed { max-height: 0 !important; opacity: 0; padding-top: 0; padding-bottom: 0; pointer-events: none; } @@ -2655,7 +2668,21 @@ select option:checked { font-family: var(--font-mono); font-size: 0.8rem; } -.lt-toggle input { display: none; } +/* Visually hidden but still keyboard-focusable */ +.lt-toggle input { + position: absolute; + opacity: 0; + width: 1px; height: 1px; + margin: -1px; padding: 0; + border: 0; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} +.lt-toggle input:focus-visible ~ .lt-toggle-track { + outline: 2px solid var(--accent-cyan); + outline-offset: 2px; +} .lt-toggle-track { width: 36px; height: 18px; @@ -2909,6 +2936,7 @@ input[type="range"].lt-range::-moz-range-thumb { transition: all 0.15s; } .lt-code-copy:hover { border-color: var(--accent-cyan); color: var(--accent-cyan); } +.lt-code-copy:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; } .lt-code-copy.copied { border-color: var(--accent-green); color: var(--accent-green); } .lt-code-block pre { margin: 0; @@ -4275,6 +4303,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc white-space: nowrap; } .lt-context-menu-item:hover { background: var(--accent-cyan-dim); color: var(--accent-cyan); } +.lt-context-menu-item:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; background: var(--accent-cyan-dim); color: var(--accent-cyan); } .lt-context-menu-item.is-danger:hover { background: var(--accent-red-dim); color: var(--accent-red); } .lt-context-menu-item .icon { width: 1rem; text-align: center; opacity: 0.7; font-size: 0.75rem; } .lt-context-menu-item kbd { @@ -4900,6 +4929,9 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas .lt-lightbox-close:hover, .lt-lightbox-prev:hover, .lt-lightbox-next:hover { color: var(--accent-cyan); border-color: var(--accent-cyan-border); box-shadow: var(--box-glow-cyan); } +.lt-lightbox-close:focus-visible, +.lt-lightbox-prev:focus-visible, +.lt-lightbox-next:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; color: var(--accent-cyan); } .lt-lightbox-caption { position: fixed; bottom: 3rem; @@ -4947,6 +4979,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas border-radius: 2px; } .lt-sidebar-group-label:hover { color: var(--text-secondary); } +.lt-sidebar-group-label:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 1px; border-radius: 2px; } .lt-sidebar-group-label .chevron { font-size: 0.5rem; transition: transform 0.2s ease; @@ -5066,7 +5099,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas transition: opacity 0.15s ease, transform 0.15s ease; overflow: hidden; } -.lt-notif-panel[aria-hidden="false"] { +.lt-notif-panel:not([aria-hidden]) { opacity: 1; transform: scale(1); pointer-events: auto; @@ -5111,6 +5144,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas transition: background 0.1s; } .lt-notif-item:hover { background: var(--bg-tertiary); } +.lt-notif-item:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; background: var(--bg-tertiary); } .lt-notif-item--unread { background: rgba(0, 212, 255, 0.04); } .lt-notif-dot { @@ -5179,7 +5213,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas right: 0; transform-origin: top right; } -.lt-dropdown-panel[aria-hidden="false"] { +.lt-dropdown-panel:not([aria-hidden]) { opacity: 1; transform: scale(1); pointer-events: auto; @@ -5206,6 +5240,12 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas background: var(--bg-tertiary); color: var(--text-primary); } +.lt-dropdown-item:focus-visible { + outline: 2px solid var(--accent-cyan); + outline-offset: -2px; + background: var(--bg-tertiary); + color: var(--text-primary); +} .lt-dropdown-item--danger { color: var(--accent-red); } .lt-dropdown-item--danger:hover { background: rgba(255,45,85,0.1); color: var(--accent-red); } diff --git a/base.html b/base.html index 723b990..0d5320c 100644 --- a/base.html +++ b/base.html @@ -123,34 +123,34 @@
-