|
| 1 | +# Accessibility Audit — 2026-03-17 |
| 2 | + |
| 3 | +**Tool**: axe-core 4.10.0 (WCAG 2A, 2AA, 2.1AA, best-practice ruleset) |
| 4 | +**Pages audited**: https://gosqlx.dev (homepage), https://gosqlx.dev/docs/getting-started |
| 5 | +**Auditor**: Claude Code automated audit |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## axe-core Violations Summary |
| 10 | + |
| 11 | +| Page | Critical | Serious | Moderate | Minor | Passes | Incomplete | |
| 12 | +|------|----------|---------|----------|-------|--------|------------| |
| 13 | +| Homepage (`/`) | 0 | 1 | 0 | 1 | 38 | 1 | |
| 14 | +| `/docs/getting-started` | 0 | 2 | 1 | 1 | 40 | 0 | |
| 15 | + |
| 16 | +**Total unique violations across both pages: 4 distinct rules triggered** |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## Detailed Violations |
| 21 | + |
| 22 | +### Homepage Violations |
| 23 | + |
| 24 | +#### serious: scrollable-region-focusable |
| 25 | +- **Description**: Ensure elements that have scrollable content are accessible by keyboard |
| 26 | +- **WCAG Criterion**: WCAG 2.1 SC 2.1.1 (Keyboard, Level A) |
| 27 | +- **Nodes affected**: 1 |
| 28 | +- **Example HTML**: `<pre class="p-4 text-[13px] leading-relaxed font-mono text-zinc-400 overflow-x-auto max-h-[320px]">` |
| 29 | +- **Fix**: Add `tabindex="0"` to each `<pre>` block that has `overflow-x-auto` or `overflow-y-auto` so keyboard users can scroll into the region. For the hero code block and AST output panel specifically. |
| 30 | + |
| 31 | +#### minor: image-redundant-alt |
| 32 | +- **Description**: Ensure image alternative text is not repeated as adjacent visible text |
| 33 | +- **WCAG Criterion**: Best practice (not a hard WCAG fail, but degrades screen-reader UX) |
| 34 | +- **Nodes affected**: 2 (both logo images — header and footer) |
| 35 | +- **Example HTML**: `<img alt="GoSQLX" ...>` inside `<a>` that also contains `<span>GoSQLX</span>` |
| 36 | +- **Fix**: Set `alt=""` on the logo `<img>` when the adjacent `<span>GoSQLX</span>` text is already present, making the image purely decorative. The link itself is adequately labelled by the text node. |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +### /docs/getting-started Violations |
| 41 | + |
| 42 | +#### serious: color-contrast |
| 43 | +- **Description**: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds (4.5:1 for normal text, 3:1 for large text) |
| 44 | +- **WCAG Criterion**: WCAG 2.1 SC 1.4.3 (Contrast Minimum, Level AA) |
| 45 | +- **Nodes affected**: 19 |
| 46 | +- **Example HTML**: |
| 47 | + - `<a class="hover:text-white transition-colors" href="/docs">Docs</a>` — nav links at ~65% lightness on dark background |
| 48 | + - `<span>Getting Started</span>` — breadcrumb text |
| 49 | +- **Fix**: Nav link color `lab(65.6464 ...)` (~`#9ca3af` / zinc-400) on the dark `#18181b` background gives approximately 3.5:1 — below 4.5:1 required for 14px normal text. Increase to `text-zinc-300` (`#d4d4d8`) or lighter to achieve ≥4.5:1. Breadcrumb spans and sidebar section labels have the same issue. |
| 50 | + |
| 51 | +#### serious: scrollable-region-focusable |
| 52 | +- **Description**: Same as homepage — `<pre>` code blocks with overflow are not keyboard-reachable |
| 53 | +- **WCAG Criterion**: WCAG 2.1 SC 2.1.1 (Keyboard, Level A) |
| 54 | +- **Nodes affected**: 13 (all code blocks on the getting-started page) |
| 55 | +- **Example HTML**: |
| 56 | + - `<pre><code class="language-bash">go install ...</code></pre>` |
| 57 | + - `<pre>` (inline code blocks throughout) |
| 58 | +- **Fix**: Same as homepage — add `tabindex="0"` to all `<pre>` elements that may overflow. Consider a shared `CodeBlock` component that applies this automatically. |
| 59 | + |
| 60 | +#### moderate: landmark-unique |
| 61 | +- **Description**: Ensure landmarks are unique — two `<nav>` elements on the page share the same role with no distinguishing accessible name |
| 62 | +- **WCAG Criterion**: WCAG 2.1 SC 1.3.6 (Identify Purpose, Level AAA) / Best practice |
| 63 | +- **Nodes affected**: 1 |
| 64 | +- **Example HTML**: `<nav class="mx-auto max-w-7xl flex items-center ...">` |
| 65 | +- **Fix**: Add `aria-label` to each `<nav>` to distinguish them. E.g., `aria-label="Primary"` on the top navbar, `aria-label="Docs sidebar"` on the docs sidebar nav, `aria-label="Breadcrumb"` on breadcrumb navigations. |
| 66 | + |
| 67 | +#### minor: image-redundant-alt |
| 68 | +- **Description**: Same as homepage — logo image alt duplicates adjacent text |
| 69 | +- **WCAG Criterion**: Best practice |
| 70 | +- **Nodes affected**: 2 (header and footer logo) |
| 71 | +- **Fix**: Same as homepage — set `alt=""` on the logo `<img>`. |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## Structural Checks |
| 76 | + |
| 77 | +### Heading Hierarchy — Homepage |
| 78 | + |
| 79 | +``` |
| 80 | +H1: Parse SQL at the speed of Go |
| 81 | +H2: Built for Production |
| 82 | + H3: Multi-Dialect |
| 83 | + H3: Thread-Safe |
| 84 | + H3: Zero-Copy |
| 85 | + H3: Object Pooling |
| 86 | + H3: Security Scanner |
| 87 | + H3: MCP Server |
| 88 | +H2: Simple, Powerful API |
| 89 | +H2: AI-Ready SQL Tools |
| 90 | +H2: IDE Integration |
| 91 | +H2: Ready to parse SQL at the speed of Go? |
| 92 | +H3: Product (footer) |
| 93 | +H3: Resources (footer) |
| 94 | +H3: Community (footer) |
| 95 | +``` |
| 96 | + |
| 97 | +**Assessment**: Hierarchy is well-formed. Single H1, logical H2 sections, H3 subsections. No heading levels are skipped. Footer H3s are semantically reasonable as section labels. No issues found. |
| 98 | + |
| 99 | +--- |
| 100 | + |
| 101 | +### Skip Link |
| 102 | + |
| 103 | +- **Status**: MISSING — no skip-to-main-content link found on homepage or docs page |
| 104 | +- **WCAG Criterion**: WCAG 2.1 SC 2.4.1 (Bypass Blocks, Level A) |
| 105 | +- **Impact**: Keyboard-only users must tab through the entire navigation bar on every page load before reaching main content. |
| 106 | +- **Fix**: Add a visually-hidden skip link as the very first focusable element in `<body>`, revealed on focus: |
| 107 | + |
| 108 | +```jsx |
| 109 | +// In your root layout (e.g., app/layout.tsx), before <Header />: |
| 110 | +<a |
| 111 | + href="#main-content" |
| 112 | + className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-indigo-600 focus:text-white focus:rounded" |
| 113 | +> |
| 114 | + Skip to main content |
| 115 | +</a> |
| 116 | +``` |
| 117 | + |
| 118 | +Then add `id="main-content"` to the `<main>` element. |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +### Image Alt Text — Homepage |
| 123 | + |
| 124 | +| Image | Alt Text | Status | |
| 125 | +|-------|----------|--------| |
| 126 | +| Logo (header) | "GoSQLX" | Redundant — adjacent span also says "GoSQLX"; use `alt=""` | |
| 127 | +| GitHub Stars badge | "GitHub Stars" | OK | |
| 128 | +| Tests badge | "Tests" | OK | |
| 129 | +| Go Report Card badge | "Go Report Card" | OK | |
| 130 | +| GoDoc badge | "GoDoc" | OK | |
| 131 | +| Logo (footer) | "GoSQLX" | Redundant — same issue as header logo; use `alt=""` | |
| 132 | + |
| 133 | +**Overall**: All images have `alt` attributes present. The only issue is the two logo instances where the alt duplicates visible text (see violation above). Feature-section icons appear to be SVG/CSS, not `<img>` tags — not audited here but should be checked if they convey meaning. |
| 134 | + |
| 135 | +--- |
| 136 | + |
| 137 | +### Focus Indicators |
| 138 | + |
| 139 | +- **Focus CSS rules found via stylesheet inspection**: None detected from accessible stylesheets (external Next.js chunks are cross-origin and unreadable by `cssRules`) |
| 140 | +- **Tailwind default**: Tailwind v3 removes the browser default `outline` on focus for many elements (`outline: 2px solid transparent`). The site uses `focus-visible:ring-*` utilities in Tailwind, which are applied via class strings in components. |
| 141 | +- **Observed in snapshot**: Interactive elements (buttons, links, nav items) do not expose explicit focus-ring class names in the axe output. |
| 142 | +- **Recommendation**: Audit interactively by tabbing through the page. Confirm `focus-visible:ring-2 focus-visible:ring-indigo-500` (or equivalent) is applied to all interactive elements: nav links, CTA buttons ("Get Started", "Try Playground"), tab switcher buttons ("Parse", "Format", "Validate", "Lint"), and footer links. |
| 143 | +- **Known gap**: The API tab buttons (`<button "Parse">`, `<button "Format">` etc.) in the homepage code demo should be verified to have visible focus rings. |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +## Color Contrast Analysis |
| 148 | + |
| 149 | +Based on computed styles captured from the live page: |
| 150 | + |
| 151 | +| Element | Color (approx hex) | Background | Estimated Ratio | WCAG AA Pass? | |
| 152 | +|---------|-------------------|------------|-----------------|---------------| |
| 153 | +| Nav links (Docs, Playground, Blog…) | ~`#a1a1aa` (zinc-400) | `#18181b` (zinc-950) | ~3.5:1 | FAIL (need 4.5:1 for 14px) | |
| 154 | +| "Get Started" button text | ~`#000000` | `#ffffff` | 21:1 | PASS | |
| 155 | +| Code panel labels (query.sql, AST Output) | ~`#71717a` (zinc-500) | dark panel bg | ~3.0:1 | FAIL (need 4.5:1 for 12px) | |
| 156 | +| Footer links | ~`#a1a1aa` (zinc-400) | `#18181b` | ~3.5:1 | FAIL (need 4.5:1 for 14px) | |
| 157 | +| Logo text "GoSQLX" (nav) | ~`#fafafa` (zinc-50) | `#18181b` | ~17:1 | PASS | |
| 158 | + |
| 159 | +> Note: Ratios estimated using the WCAG relative luminance formula on the captured RGB values. Exact values depend on actual rendered background. The dark-themed site uses zinc-400/zinc-500 text on zinc-950 backgrounds throughout — this color pairing recurs at least 19 times (per axe node count on docs page). |
| 160 | +
|
| 161 | +--- |
| 162 | + |
| 163 | +## Priority Fixes (WCAG 2.1 AA Compliance) |
| 164 | + |
| 165 | +Ranked by severity and user impact: |
| 166 | + |
| 167 | +1. **Add skip-to-main-content link** — WCAG 2.4.1 (Level A) — Effort: **Low** |
| 168 | + - Affects all keyboard and screen-reader users on every page. Single change in root layout. No visual impact for mouse users. |
| 169 | + |
| 170 | +2. **Fix color contrast for nav links, breadcrumbs, code labels** — WCAG 1.4.3 (Level AA) — Effort: **Low/Med** |
| 171 | + - 19+ affected nodes on docs pages. Change `text-zinc-400` → `text-zinc-300` (or lighter) for all body/nav link text on dark backgrounds. Update the Tailwind color tokens used for secondary text in the dark theme. This fixes the most-cited violation by axe. |
| 172 | + |
| 173 | +3. **Add `tabindex="0"` to all scrollable `<pre>` code blocks** — WCAG 2.1.1 (Level A) — Effort: **Low** |
| 174 | + - Affects 1 element on homepage, 13 on docs page (all code examples). Create a shared `CodeBlock` component (or update the existing one) to always include `tabindex="0"` when `overflow-*` is applied. Keyboard users currently cannot scroll horizontally into long code examples. |
| 175 | + |
| 176 | +4. **Add `aria-label` to distinguish multiple `<nav>` landmarks** — Best practice / WCAG 1.3.6 — Effort: **Low** |
| 177 | + - Affects docs pages with sidebar. Adds `aria-label="Primary"` to header nav, `aria-label="Documentation sidebar"` to sidebar nav. One-line fix per nav element. |
| 178 | + |
| 179 | +5. **Fix redundant logo alt text** — Best practice — Effort: **Low** |
| 180 | + - Change `alt="GoSQLX"` to `alt=""` on logo `<img>` in header and footer links where the adjacent `<span>GoSQLX</span>` text provides the accessible name. |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +## Additional Observations |
| 185 | + |
| 186 | +- **Console errors**: Two errors on every page load — `_vercel/insights/script.js` 404 and a CSP block on an external analytics script. These do not affect accessibility but indicate a broken analytics integration. |
| 187 | +- **Stats section counter animation**: The animated counters ("0+" ops/sec, "0μs" latency) start at 0 before animating to real values. Screen readers will announce "0" initially. Consider using `aria-live="polite"` with a debounced update, or providing static values with a `<noscript>` fallback. |
| 188 | +- **Interactive code demo (homepage)**: The tab buttons ("Parse", "Format", "Validate", "Lint") lack visible labels for two of the four buttons in the axe snapshot (`button "Format"`, `button "Validate"`, `button "Lint"` have no text in some states). Confirm these have accessible names in all states. |
| 189 | +- **"Open Full Playground" link**: Contains an `<img>` with no captured alt in the axe snapshot — verify this is `alt=""` with `aria-hidden="true"` since the link text already conveys the purpose. |
0 commit comments