Quick reference untuk web accessibility best practices. WCAG guidelines, ARIA attributes, testing tools, dan checklist lengkap.
Prinsip-prinsip dasar Web Content Accessibility Guidelines yang harus dipenuhi untuk membuat website yang accessible.
| Principle | Description |
|---|---|
| Perceivable | Content dapat dipersepsi oleh user |
| Operable | UI dapat dioperasikan via keyboard/input lain |
| Understandable | Content dan operasi dapat dipahami |
| Robust | Bekerja di berbagai teknologi & browser |
Level-level kepatuhan WCAG untuk mengukur seberapa accessible website kita.
Persyaratan kontras warna yang harus dipenuhi agar text mudah dibaca oleh semua orang.
| Content Type | WCAG AA | WCAG AAA |
|---|---|---|
| Normal text (< 24px) | 4.5:1 | 7:1 |
| Large text (≥ 24px) | 3:1 | 4.5:1 |
| UI components | 3:1 | - |
Penggunaan elemen HTML yang tepat sesuai dengan fungsinya untuk membantu assistive technology.
<!-- Use proper elements -->
<button>Click</button> <!-- NOT <div onclick> -->
<a href="/page">Link</a> <!-- NOT <span onclick> -->
<nav>...</nav> <!-- NOT <div class="nav"> -->
<main>...</main> <!-- NOT <div id="main"> -->
<header>...</header>
<footer>...</footer>
<article>...</article>
<section>...</section><!-- Correct hierarchy -->
<h1>Page Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
<h3>Subsection</h3>
<h2>Section</h2>
<!-- Don't skip levels -->
<h1>Title</h1>
<h3>Section</h3> <!-- Skipped h2! -->Rules:
<h1> per page<!-- Informative image -->
<img src="chart.png" alt="Sales increased 50% in Q4 2024" />
<!-- Decorative image -->
<img src="divider.svg" alt="" />
<!-- Linked image -->
<a href="/profile">
<img src="avatar.jpg" alt="Go to profile page" />
</a>
<!-- Icon with text -->
<button>
<svg aria-hidden="true">...</svg>
Delete
</button>
<!-- Icon without text -->
<button aria-label="Delete item">
<svg aria-hidden="true">...</svg>
</button>Tips:
<!-- Label association -->
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
<!-- Implicit label -->
<label>
Email
<input type="email" name="email" />
</label>
<!-- Hint text -->
<label for="password">Password</label>
<input
type="password"
id="password"
aria-describedby="pwd-hint"
/>
<span id="pwd-hint">Min 8 characters</span>
<!-- Error message -->
<label for="username">Username</label>
<input
type="text"
id="username"
aria-invalid="true"
aria-describedby="username-error"
/>
<span id="username-error" role="alert">
Username already taken
</span>
<!-- Radio group -->
<fieldset>
<legend>Choose plan</legend>
<label><input type="radio" name="plan" value="free" /> Free</label>
<label><input type="radio" name="plan" value="pro" /> Pro</label>
</fieldset>Essential keys:
Tab - Next focusable elementShift + Tab - Previous elementEnter - Activate link/buttonSpace - Activate button, check checkboxArrow keys - Navigate within componentEsc - Close dialog/menuAll functionality must be keyboard accessible!
/* Never do this */
*:focus {
outline: none;
}
/* Custom focus style */
button:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Better - focus-visible (keyboard only) */
button:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}<div role="button">Custom Button</div>
<div role="dialog">Modal</div>
<div role="navigation">Nav</div>
<div role="alert">Error message</div>
<div role="status">Loading...</div>
<div role="progressbar">Progress</div>Catatan: Prefer semantic HTML over ARIA roles!
| Attribute | Usage | Example |
|---|---|---|
aria-label | Label element | <button aria-label="Close">×</button> |
aria-labelledby | Reference label | <div aria-labelledby="title"> |
aria-describedby | Extra description | <input aria-describedby="hint"> |
aria-hidden | Hide from screen readers | <span aria-hidden="true">✓</span> |
aria-live | Announce changes | <div aria-live="polite"> |
aria-expanded | Expandable state | <button aria-expanded="false"> |
aria-pressed | Toggle button | <button aria-pressed="true"> |
aria-invalid | Invalid input | <input aria-invalid="true"> |
aria-required | Required field | <input aria-required="true"> |
aria-disabled | Disabled state | <button aria-disabled="true"> |
<!-- Polite - wait for pause -->
<div aria-live="polite">Message sent!</div>
<!-- Assertive - interrupt immediately -->
<div aria-live="assertive">Error occurred!</div>
<!-- Atomic - read entire region -->
<div aria-live="polite" aria-atomic="true">
<p>3 new messages</p>
</div>.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}<button>
<span aria-hidden="true">×</span>
<span class="sr-only">Close dialog</span>
</button><body>
<a href="#main" class="skip-link">Skip to main content</a>
<header>...</header>
<main id="main">...</main>
</body>.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
}
.skip-link:focus {
top: 0;
}/* Minimum 44×44px (WCAG AAA) */
.button {
min-width: 44px;
min-height: 44px;
padding: 12px 24px;
}<!-- Custom button -->
<div
role="button"
tabindex="0"
onkeydown="handleKeyDown(event)"
onclick="handleClick()">
Custom Button
</div>
<script>
function handleKeyDown(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
}
</script>Requirements:
role - Define semanticstabindex="0" - Make focusableEnter and Space<div
role="dialog"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc"
aria-modal="true">
<h2 id="dialog-title">Confirm Delete</h2>
<p id="dialog-desc">Are you sure you want to delete this item?</p>
<button onclick="confirmDelete()">Delete</button>
<button onclick="closeDialog()">Cancel</button>
</div>Best practices:
Escaria-modal="true"<!-- Video with captions -->
<video controls>
<source src="video.mp4" type="video/mp4" />
<track
kind="captions"
src="captions.vtt"
srclang="id"
label="Indonesian"
default
/>
</video>
<!-- Audio with transcript -->
<audio controls>
<source src="audio.mp3" type="audio/mpeg" />
</audio>
<details>
<summary>Transcript</summary>
<p>Full transcript here...</p>
</details># Lighthouse (Chrome DevTools)
# DevTools → Lighthouse → Accessibility → Run
# axe DevTools (Browser extension)
# Install from Chrome/Firefox store
# Command line
npm install -g @axe-core/cli
axe https://yoursite.com| Wrong | Correct |
|---|---|
<div onclick> | <button> |
| Only placeholder, no label | Label + placeholder |
Removing :focus outline | Custom focus style |
| Low contrast (2:1) | Sufficient contrast (4.5:1) |
| Color only for meaning | Color + icon/text |
| Skipping heading levels | Logical hierarchy |
| Empty link text | Descriptive link text |
| Auto-play video/audio | User-controlled media |
| Time limits without option | Extendable/disablable |
<div aria-live="polite">Status message</div>
<div aria-live="assertive" role="alert">Error!</div><button aria-pressed="false">Toggle Off</button>
<button aria-pressed="true">Toggle On</button>
<button aria-expanded="false">Collapsed</button>
<button aria-expanded="true">Expanded</button>
<button disabled>Disabled Button</button><!-- Works without JS -->
<details>
<summary>Click to expand</summary>
<p>Content here...</p>
</details>
<!-- Enhanced with JS if available -->
<script>
// Add custom behavior
</script>"The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect." — Tim Berners-Lee
Accessibility = Better UX for everyone! 🌐♿
Login atau daftar akun gratis untuk membaca cheat sheet ini.