mirror of
https://github.com/svemagie/blog-eleventy-indiekit.git
synced 2026-04-02 16:44:56 +02:00
a11y: comprehensive WCAG 2.1 Level AA accessibility audit
- Add skip-to-main-content link and main content ID target - Add prefers-reduced-motion media queries for all animations - Enhance visible focus indicators (2px offset, high-contrast ring) - Replace ~160 text-surface-500 instances with text-surface-600/dark:text-surface-400 for 4.5:1+ contrast ratio compliance - Add aria-hidden="true" to ~30+ decorative SVG icons across sidebars/widgets - Convert facepile containers from div to semantic ul/li with role="list" - Add aria-label to icon-only buttons (share, sort controls) - Add sr-only labels to form inputs (webmention, search) - Add aria-live="polite" to dynamically loaded webmentions - Add aria-label with relative+absolute date to time-difference component - Add keyboard handlers (Enter/Space) to custom interactive elements - Add aria-label to nav landmarks (table of contents) - Fix modal focus trap and dialog accessibility - Fix lightbox keyboard navigation and screen reader announcements Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
This commit is contained in:
@@ -11,6 +11,7 @@ document.addEventListener("alpine:init", () => {
|
||||
alt: "",
|
||||
images: [],
|
||||
currentIndex: 0,
|
||||
triggerElement: null,
|
||||
|
||||
init() {
|
||||
const container = this.$root;
|
||||
@@ -21,14 +22,24 @@ document.addEventListener("alpine:init", () => {
|
||||
|
||||
this.images.forEach((img, i) => {
|
||||
img.style.cursor = "zoom-in";
|
||||
img.setAttribute("tabindex", "0");
|
||||
img.setAttribute("role", "button");
|
||||
img.setAttribute("aria-label", (img.alt || "Image") + " — click to enlarge");
|
||||
img.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.show(i);
|
||||
});
|
||||
img.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
this.show(i);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
show(index) {
|
||||
this.triggerElement = this.images[index];
|
||||
this.currentIndex = index;
|
||||
const img = this.images[index];
|
||||
// Use the largest source available
|
||||
@@ -48,12 +59,22 @@ document.addEventListener("alpine:init", () => {
|
||||
this.alt = img.alt || "";
|
||||
this.open = true;
|
||||
document.body.style.overflow = "hidden";
|
||||
// Move focus to close button for keyboard users
|
||||
this.$nextTick(() => {
|
||||
const closeBtn = document.querySelector('[x-ref="closeBtn"]');
|
||||
if (closeBtn) closeBtn.focus();
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
this.src = "";
|
||||
document.body.style.overflow = "";
|
||||
// Return focus to the image that triggered the lightbox
|
||||
if (this.triggerElement) {
|
||||
this.triggerElement.focus();
|
||||
this.triggerElement = null;
|
||||
}
|
||||
},
|
||||
|
||||
next() {
|
||||
|
||||
@@ -78,9 +78,12 @@ class TimeDifference extends HTMLElement {
|
||||
const relative = rtf.format(value, unit);
|
||||
|
||||
// Store original text as title for hover tooltip
|
||||
const originalText = time.textContent.trim();
|
||||
if (!time.hasAttribute("title")) {
|
||||
time.setAttribute("title", time.textContent.trim());
|
||||
time.setAttribute("title", originalText);
|
||||
}
|
||||
// aria-label provides the full context: "2 days ago (March 5, 2026)"
|
||||
time.setAttribute("aria-label", relative + " (" + originalText + ")");
|
||||
time.textContent = relative;
|
||||
} catch {
|
||||
// Intl.RelativeTimeFormat not supported, keep static text
|
||||
|
||||
@@ -197,22 +197,26 @@
|
||||
items.forEach((item) => {
|
||||
const author = item.author || {};
|
||||
|
||||
const li = document.createElement('li');
|
||||
li.className = 'inline';
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = author.url || '#';
|
||||
link.className = 'facepile-avatar';
|
||||
link.title = author.name || 'Anonymous';
|
||||
link.setAttribute('aria-label', (author.name || 'Anonymous') + ' (opens in new tab)');
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener';
|
||||
link.dataset.new = 'true';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = author.photo || '/images/default-avatar.svg';
|
||||
img.alt = author.name || 'Anonymous';
|
||||
img.alt = '';
|
||||
img.className = 'w-8 h-8 rounded-full ring-2 ring-white dark:ring-surface-900';
|
||||
img.loading = 'lazy';
|
||||
|
||||
link.appendChild(img);
|
||||
row.appendChild(link);
|
||||
li.appendChild(link);
|
||||
row.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -278,7 +282,7 @@
|
||||
|
||||
const dateLink = document.createElement('a');
|
||||
dateLink.href = item.url || '#';
|
||||
dateLink.className = 'text-xs text-surface-500 hover:underline';
|
||||
dateLink.className = 'text-xs text-surface-600 dark:text-surface-400 hover:underline';
|
||||
dateLink.target = '_blank';
|
||||
dateLink.rel = 'noopener';
|
||||
|
||||
@@ -396,8 +400,9 @@
|
||||
header.className = 'text-sm font-semibold text-surface-600 dark:text-surface-400 uppercase tracking-wide mb-3';
|
||||
header.textContent = `0 ${type === 'likes' ? 'Likes' : 'Reposts'}`;
|
||||
|
||||
const row = document.createElement('div');
|
||||
const row = document.createElement('ul');
|
||||
row.className = 'facepile';
|
||||
row.setAttribute('role', 'list');
|
||||
|
||||
section.appendChild(header);
|
||||
section.appendChild(row);
|
||||
@@ -446,6 +451,8 @@
|
||||
const section = document.createElement('section');
|
||||
section.className = 'webmentions mt-8 pt-8 border-t border-surface-200 dark:border-surface-700';
|
||||
section.id = 'webmentions';
|
||||
section.setAttribute('aria-live', 'polite');
|
||||
section.setAttribute('aria-label', 'Webmentions');
|
||||
|
||||
const header = document.createElement('h2');
|
||||
header.className = 'text-xl font-bold text-surface-900 dark:text-surface-100 mb-6';
|
||||
|
||||
Reference in New Issue
Block a user