Files
GMW/design/system/14-accessibility.md
T
asepharyana d59b59a7a7 feat: migrate frontend to Astro + expand AI moderation + backend admin/runtime config
Frontend:
- migrate from Vite to Astro (astro.config.mjs, pages/, layouts/)
- add admin panel, settings page, command palette, error boundary
- refactor App.tsx, MascotChatbot, Sidebar, Header, DashboardLayout
- update API client, WebSocket, auth, dashboard features

Backend:
- add admin module and config routes
- refactor middlewares, Redis connection, WebSocket server/bridge
- add runtime config loader

Discord Gateway:
- refactor AI moderation: circuit breaker, concurrency limiter, fallback processor
- add media analysis client, Seaxng search, user profile learner
- add new drizzle migration

Shared:
- extend database schema, add new config fields
2026-07-02 00:02:41 +07:00

335 lines
8.1 KiB
Markdown

# Accessibility — Design for Everyone
> *"The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect."*
> — Tim Berners-Lee
---
## 🎯 Filosofi Aksesibilitas
BETE dirancang untuk **inklusif sejak awal**, bukan retrofit:
1. **Semantic HTML** — Struktur sebelum style
2. **Color-independent** — Informasi tidak hanya disampaikan lewat warna
3. **Keyboard-first** — Semua fitur bisa diakses tanpa mouse
4. **Reduced motion** — Animasi opsional, bukan wajib
---
## 🏆 Target Compliance
| Level | Target | Verification |
|-------|--------|--------------|
| WCAG 2.1 AA | ✅ Mandatory | Automated + manual |
| WCAG 2.1 AAA | ⭐ Recommended | Manual audit |
| Section 508 | ✅ Mandatory | Automated |
| EN 301 549 | ✅ Mandatory | EU compliance |
---
## 🎨 Color Accessibility
### Contrast Ratios Minimum
| Elemen | Teks Normal | Teks Large (≥18px / ≥14px bold) |
|--------|-------------|----------------------------------|
| Body text | 4.5:1 (AA) | 3:1 (AA) |
| UI text (label, badge) | 4.5:1 (AA) | 3:1 (AA) |
| Placeholder | 3:1 (AA large) | — |
| Disabled | 3:1 | 3:1 |
### Color Blindness
- Jangan gunakan **merah-hijau** sebagai satu-satunya pembeda
- Tambahkan **ikon, pola, atau label teks** sebagai secondary encoding
- Gunakan palette color-blind safe (lihat `01-color-system.md`)
```typescript
// Tool: verifikasi kontras otomatis di tests
function checkContrast(foreground: string, background: string): boolean {
const fg = parseOklch(foreground);
const bg = parseOklch(background);
return getContrastRatio(fg, bg) >= 4.5;
}
```
---
## ⌨️ Keyboard Navigation
### Focus Order
```html
<!-- ✅ Semantic order = visual order -->
<nav> <!-- Tab 1 -->
<main> <!-- Tab 2 -->
<h1> <!-- Tab 3 -->
<p> <!-- Tab 4 -->
<button><!-- Tab 5 -->
</main>
<footer> <!-- Tab 6 -->
```
### Focus Indicators
```css
/* Custom focus ring — lebih visible dari browser default */
:focus-visible {
outline: 2px solid var(--clr-primary-400);
outline-offset: 2px;
border-radius: var(--rd-sm);
}
/* ⚠️ NEVER do this */
:focus { outline: none; } /* Membuat keyboard users buta */
```
### Keyboard Shortcuts
```
Tab / Shift+Tab — Navigate forward/backward
Enter / Space — Activate element
Escape — Close modal/dropdown/menu
Arrow keys — Navigate list, tabs, select
Ctrl+K — Command palette
```
### Skip Navigation
```html
<!-- First focusable element on page -->
<a href="#main-content" class="skip-link">
Skip to main content
</a>
```
```css
.skip-link {
position: absolute;
top: -100%;
left: 8px;
padding: 8px 16px;
background: var(--clr-primary);
color: var(--clr-text-on-primary);
z-index: 9999;
}
.skip-link:focus {
top: 8px;
}
```
---
## 🏗️ Semantic HTML Structure
```html
<!-- Dashboard page template -->
<header role="banner">
<nav role="navigation" aria-label="Main navigation">
<ul>
<li><a href="/live" aria-current="page">Live</a></li>
<li><a href="/messages">Messages</a></li>
<li><a href="/settings">Settings</a></li>
</ul>
</nav>
</header>
<main id="main-content" role="main">
<h1>Live Dashboard</h1>
<section aria-labelledby="voice-status">
<h2 id="voice-status">Voice Connections</h2>
<!-- voice content -->
</section>
<section aria-labelledby="active-speakers">
<h2 id="active-speakers">Active Speakers</h2>
<ul role="list" aria-label="Currently speaking users">
<li role="listitem">User 1</li>
<li role="listitem">User 2</li>
</ul>
</section>
</main>
```
---
## ♿ ARIA Patterns
### Dynamic Content (Live Regions)
```html
<!-- Toast notifications — live region -->
<div aria-live="polite" aria-atomic="true" class="toast-container">
<!-- Toasts announced by screen reader -->
</div>
<!-- Loading state -->
<div role="status" aria-live="polite">
<span class="sr-only">Loading messages...</span>
<div class="skeleton" aria-hidden="true"></div>
</div>
<!-- Error state -->
<div role="alert" aria-live="assertive">
<p>Failed to load messages. Please try again.</p>
</div>
```
### Modals
```html
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-desc"
>
<h2 id="modal-title">Confirm Delete</h2>
<p id="modal-desc">This action cannot be undone.</p>
<button onClick={closeModal}>Cancel</button>
<button onClick={confirmDelete}>Delete</button>
</div>
```
### Tabs
```html
<div role="tablist" aria-label="Dashboard tabs">
<button role="tab" aria-selected="true" aria-controls="panel-live" id="tab-live">
Live
</button>
<button role="tab" aria-selected="false" aria-controls="panel-messages" id="tab-messages">
Messages
</button>
</div>
<div role="tabpanel" id="panel-live" aria-labelledby="tab-live">
<!-- Live content -->
</div>
```
---
## 🔇 Reduced Motion
```css
/* Global override */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* GSAP hook — programmatic check */
function prefersReducedMotion(): boolean {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
```
---
## 🖼️ Images & Icons
```tsx
// Icons — always with aria-hidden or label
<MicIcon aria-hidden="true" /> // Decorative
<span role="img" aria-label="Voice active">🎤</span> // Emoji
<Icon icon="mic" aria-label="Microphone" /> // Informative
// Images — always with alt text
<img src={user.avatar} alt={`${user.name}'s avatar`} />
<img src={decorativeBg} alt="" role="presentation" /> // Decorative
```
---
## 🧪 Testing Accessibility
```typescript
// Automated tests
import { axe } from 'jest-axe';
describe('MessageCard', () => {
it('has no accessibility violations', async () => {
const { container } = render(<MessageCard message={mockMessage} />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
// Manual checklist
const a11yChecklist = [
'Keyboard: all interactive elements reachable',
'Focus order matches visual order',
'Screen reader: all content announced',
'Contrast: 4.5:1 minimum for body text',
'Labels: all form elements have labels',
'Alt text: all images have meaningful alt text',
'Reduced motion: animations respect media query',
'Color: information not conveyed by color alone',
];
```
---
## 🧰 Tools & Resources
| Tool | Purpose | Integration |
|------|---------|-------------|
| axe-core | Automated audit | CI pipeline |
| Lighthouse | Performance + a11y | CI pipeline |
| NVDA / VoiceOver | Screen reader | Manual testing |
| Contrast Checker | Color verification | Design phase |
| Tab Tester | Keyboard flow | Manual testing |
---
## ⚠️ A11y Anti-Patterns
### ❌ Color-only indicators
```tsx
// ❌ JANGAN — buta warna tidak bisa membedakan
<Badge className={isBad ? 'bg-red-500' : 'bg-green-500'} />
// ✅ Color + icon + text
<Badge variant={isBad ? 'destructive' : 'success'} icon={isBad ? <X /> : <Check />} />
```
### ❌ Missing focus indicator
```css
/* ❌ JANGAN — menghilangkan focus ring */
*:focus { outline: none; }
/* ✅ Custom focus ring yang visible */
*:focus-visible { outline: 2px solid var(--clr-primary-400); outline-offset: 2px; }
```
### ❌ Non-semantic clickable
```tsx
// ❌ JANGAN — div clickable tanpa role
<div onClick={handleClick}>Click me</div>
// ✅ Gunakan button
<button onClick={handleClick}>Click me</button>
```
---
## 🔗 Referensi
| Sumber | Konsep |
|--------|--------|
| [WCAG 2.1](https://www.w3.org/TR/WCAG21/) | Accessibility standard |
| [A11y Project](https://www.a11yproject.com/) | Accessibility patterns |
| [Inclusive Components](https://inclusive-components.design/) | Accessible component design |
| [axe DevTools](https://www.deque.com/axe/) | Automated testing |
---
*"Desain yang inklusif adalah ingatan yang tak membeda-bedakan — setiap orang berhak atas pengalaman yang utuh."* ❄️🩵