Aksesibilitas dengan Claude Code: Semantic HTML, ARIA, axe, dan Manual Check
Workflow praktis aksesibilitas dengan Claude Code: HTML, keyboard, form, fokus, axe, dan screen reader.
Aksesibilitas bukan pekerjaan “jalankan tool sebelum rilis”. Ia dimulai dari semantic HTML, lalu keyboard navigation, focus management, error form, kontras warna, automated check, dan manual check dengan screen reader.
Claude Code bisa mempercepat proses ini, tetapi prompt harus jelas. Kalau hanya menulis “buat accessible”, hasilnya bisa berupa div yang diberi ARIA tanpa perilaku Enter atau Space, modal yang terlihat benar tetapi fokusnya lari ke halaman belakang, atau error form yang terlihat tetapi tidak diumumkan screen reader.
Referensi utama di artikel ini adalah sumber resmi: W3C WCAG 2.2 sebagai target praktis, WAI-ARIA Authoring Practices Guide untuk pola widget, MDN ARIA untuk prinsip HTML semantik dulu, dokumentasi axe-core Deque untuk automated check, dan dokumentasi resmi Claude Code untuk dasar tool.
Tetapkan Target Sebelum Mengedit
“Perbaiki accessibility” terlalu luas. Target yang lebih aman adalah: WCAG 2.2 AA sebagai standar praktik, semantic HTML sebelum ARIA, flow utama bisa dioperasikan keyboard, fokus terlihat, error jelas, dan ada bukti automated plus manual check.
| Area | Target praktis | Kegagalan umum |
|---|---|---|
| Semantic HTML | button, a, form, label, main, nav dipakai sesuai fungsi | div klik menggantikan control native |
| Keyboard | Tab, Shift+Tab, Enter, Space, Escape menjalankan flow utama | Modal hanya bisa ditutup mouse |
| Fokus | Fokus masuk ke UI baru dan kembali ke trigger saat ditutup | Fokus hilang ke belakang dialog |
| ARIA | Dipakai hanya saat HTML native tidak cukup | aria-label menutupi label visual yang hilang |
| Warna | Teks, control, error, fokus cukup kontras | Error hanya memakai warna merah |
| Form | Label, hint, invalid state, error terhubung ke input | Error terlihat tetapi tidak diumumkan |
| Test | axe plus keyboard dan screen reader check | Nol violation otomatis dianggap audit lengkap |
MDN menyarankan memakai elemen HTML native ketika sudah menyediakan semantik dan perilaku yang dibutuhkan. Jadi, minta Claude Code memperbaiki struktur dulu, baru menambahkan ARIA untuk state seperti aria-invalid, aria-expanded, atau aria-modal.
Prompt Aman untuk Claude Code
Prompt accessibility yang baik mirip checklist review. Ia menetapkan scope, melindungi copy bisnis, menentukan standar, dan meminta bukti.
claude <<'PROMPT'
Scope:
- Review only src/components/CheckoutForm.tsx and its tests.
- Do not change pricing copy, analytics events, or unrelated styles.
Accessibility target:
- Use WCAG 2.2 AA as the practical target.
- Prefer semantic HTML before ARIA.
- Add ARIA only when native HTML cannot express the state.
Check these items:
- Labels, descriptions, required state, and validation errors.
- Keyboard operation with Tab, Shift+Tab, Enter, Space, and Escape.
- Focus order, visible focus, and focus return after closing UI.
- Color contrast and non-color error indicators.
- Automated axe check plus manual screen-reader notes.
Output:
- Findings first, with file and line references.
- Minimal patch.
- Commands to verify.
- Any remaining risk.
PROMPT
Bagian Findings first penting. Claude Code menjelaskan bug dulu, lalu membuat patch paling kecil. Ini berguna untuk halaman yang punya harga, CTA, link Gumroad, atau analytics event. Kebiasaan scope kecil juga dibahas di tips produktivitas Claude Code.
Use Case 1: CTA Produk atau Artikel
CTA adalah jalur monetisasi. Jika tindakannya navigasi, gunakan link sungguhan, bukan seluruh card dengan onclick.
<div class="hero-card" onclick="location.href='/en/products'">
<div class="title">Claude Code Templates</div>
<div class="button">Buy now</div>
</div>
Struktur yang lebih baik memisahkan heading, penjelasan, dan tujuan.
<section aria-labelledby="templates-heading" class="product-cta">
<h2 id="templates-heading">Percepat review dengan template Claude Code</h2>
<p>
Salin prompt reusable untuk implementasi, review,
debugging, dan dokumentasi.
</p>
<a class="primary-link" href="/en/products">
Lihat resource produk
</a>
</section>
Saat meminta Claude Code mengubah CTA, tulis bahwa URL tujuan, atribut tracking, dan copy conversion harus dipertahankan. Patch accessible yang merusak funnel tetap bukan patch siap produksi.
Use Case 2: Form Kontak atau Konsultasi
Form adalah tempat accessibility issue mudah berubah menjadi kehilangan conversion. User perlu tahu nama field, format yang benar, error, dan cara memperbaikinya.
import { FormEvent, useState } from "react";
type Errors = {
name?: string;
email?: string;
};
export function ConsultationForm() {
const [errors, setErrors] = useState<Errors>({});
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const data = new FormData(event.currentTarget);
const nextErrors: Errors = {};
if (!String(data.get("name") || "").trim()) {
nextErrors.name = "Masukkan nama Anda.";
}
if (!String(data.get("email") || "").includes("@")) {
nextErrors.email = "Masukkan alamat email yang valid.";
}
setErrors(nextErrors);
}
return (
<form aria-labelledby="consultation-title" onSubmit={handleSubmit} noValidate>
<h2 id="consultation-title">Permintaan konsultasi</h2>
<div className="field">
<label htmlFor="name">Nama</label>
<input
id="name"
name="name"
autoComplete="name"
aria-invalid={errors.name ? "true" : "false"}
aria-describedby={errors.name ? "name-error" : undefined}
/>
{errors.name && (
<p id="name-error" role="alert">
{errors.name}
</p>
)}
</div>
<div className="field">
<label htmlFor="email">Email</label>
<p id="email-help">Gunakan alamat yang bisa kami balas.</p>
<input
id="email"
name="email"
type="email"
autoComplete="email"
aria-invalid={errors.email ? "true" : "false"}
aria-describedby={
errors.email ? "email-help email-error" : "email-help"
}
/>
{errors.email && (
<p id="email-error" role="alert">
{errors.email}
</p>
)}
</div>
<button type="submit">Kirim permintaan</button>
</form>
);
}
Kegagalan umum adalah error ditampilkan tetapi tidak dihubungkan ke input. Kegagalan lain adalah selalu merujuk ID error yang belum dirender. Hint permanen selalu terhubung; error hanya terhubung saat muncul.
Use Case 3: Modal, Command Palette, dan Menu
Modal bukan hanya overlay. Pola resmi WAI-ARIA Dialog Modal mengharuskan fokus masuk ke dialog, Tab berputar di dalam, Escape menutup, dan fokus kembali ke tombol pembuka.
import { ReactNode, useEffect, useRef } from "react";
type ModalProps = {
open: boolean;
title: string;
onClose: () => void;
children: ReactNode;
};
const focusableSelector = [
"a[href]",
"button:not([disabled])",
"input:not([disabled])",
"select:not([disabled])",
"textarea:not([disabled])",
'[tabindex]:not([tabindex="-1"])',
].join(",");
export function AccessibleModal(props: ModalProps) {
const { open, title, onClose, children } = props;
const dialogRef = useRef<HTMLDivElement>(null);
const previousFocusRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!open) return;
previousFocusRef.current = document.activeElement as HTMLElement;
const focusable = dialogRef.current?.querySelectorAll<HTMLElement>(
focusableSelector
);
focusable?.[0]?.focus();
function onKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") onClose();
if (event.key !== "Tab" || !dialogRef.current) return;
const items = [...dialogRef.current.querySelectorAll<HTMLElement>(
focusableSelector
)];
const first = items[0];
const last = items[items.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last?.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first?.focus();
}
}
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("keydown", onKeyDown);
previousFocusRef.current?.focus();
};
}, [open, onClose]);
if (!open) return null;
return (
<div className="modal-backdrop">
<div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className="modal-panel"
>
<h2 id="modal-title" tabIndex={-1}>
{title}
</h2>
{children}
<button type="button" onClick={onClose}>
Tutup
</button>
</div>
</div>
);
}
Untuk dropdown, pastikan pola ARIA memang dibutuhkan. Menu Button Pattern cocok untuk menu command. Navigasi website biasa sering lebih baik dengan nav dan daftar link.
Warna, Fokus, dan Mobile
Regresi sering muncul saat polishing desain: outline dihapus, teks abu-abu terlalu tipis, error hanya warna merah. Beri fokus visual dan indikator error selain warna.
.primary-link {
background: #0f766e;
border-radius: 6px;
color: #ffffff;
display: inline-flex;
font-weight: 700;
min-height: 44px;
padding: 0.75rem 1rem;
}
.primary-link:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
.field [role="alert"] {
border-left: 4px solid #b91c1c;
color: #7f1d1d;
margin-top: 0.5rem;
padding-left: 0.75rem;
}
Periksa juga mobile. Tombol close kecil, fokus tertutup sticky header, dan CTA terlalu sempit sering lolos dari review desktop.
axe dan Manual Screen Reader Check
axe cepat menemukan masalah struktural, tetapi tidak membuktikan label jelas atau urutan baca masuk akal untuk flow bisnis.
npm install -D @axe-core/playwright @playwright/test
npx playwright install --with-deps chromium
import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
test("consultation form has no serious accessibility issues", async ({ page }) => {
await page.goto("/contact");
const results = await new AxeBuilder({ page })
.include("main")
.withTags(["wcag2a", "wcag2aa", "wcag22aa"])
.analyze();
expect(results.violations).toEqual([]);
});
Manual check dimulai dari CTA produk, form konsultasi, checkout, modal, navigasi, dan error recovery. Di Windows gunakan NVDA; di macOS gunakan VoiceOver.
Failure Mode yang Harus Dicari
role="button"tanpa dukungan Enter dan Space.- Tombol icon-only tanpa accessible name.
alt="image"atau alt text tidak berguna.aria-hidden="true"menyembunyikan modal atau live region.- Error terlihat tetapi tidak terhubung dengan
aria-describedby. - Focus outline dihapus tanpa pengganti.
- Modal ditutup tetapi fokus tidak kembali ke trigger.
- Automated test hanya memeriksa halaman marketing, bukan form nyata.
Berikan daftar ini ke Claude Code setelah patch dan minta file serta line reference. Hasilnya lebih kuat daripada “review lagi”.
CTA dan Hasil Verifikasi
Untuk membuat workflow ini berulang, mulai dari Claude Code product resources. Jika butuh prompt review dan debugging yang reusable, lihat 50 Claude Code prompt templates. Untuk tim yang perlu permissions, hooks, dan verification receipt, konsultasi adalah langkah berikutnya.
Pada refresh ini saya menguji tiga kegagalan nyata: CTA card berupa div klik, error form terlihat tetapi tidak diumumkan, dan modal yang tidak mengembalikan fokus. Flow Claude Code paling stabil adalah findings dulu, patch minimal setelah itu, lalu cek keyboard dan VoiceOver. axe membantu menemukan struktur, tetapi kejelasan flow tetap harus didengar manual.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission safety ladder Claude Code: perluas akses tanpa kehilangan kontrol
Naik dari read-only ke edit terbatas, command bukti, dan cek deploy dengan kontrol yang jelas.
Claude Code Small PR Proof Pack: perubahan kecil yang mudah direview
Paket bukti untuk PR Claude Code: diff, check, URL publik, jalur CTA, dan rollback.
Review gate Claude Code sebelum commit: diff, test, URL publik, dan CTA
Cara memakai Claude Code sebelum commit: diff scope, build, URL publik, link Gumroad, CTA konsultasi, missing test, dan file tidak terkait.