@vnkr-labs/a11y
v1.0.0
Published
VNKR Design System — Accessibility utilities (WCAG 2.1 AA): focusTrap, announce, skipLink, colorContrast
Downloads
208
Maintainers
Readme
@vnkr-labs/a11y
Accessibility utilities cho VNKR Design System.
Bám sát WCAG 2.1 AA — yêu cầu bắt buộc cho dịch vụ công Việt Nam.
Zero dependencies. Vanilla JS — dùng được với mọi framework.
Cài đặt
npm install @vnkr-labs/a11y
# hoặc
pnpm add @vnkr-labs/a11yAPI
focusTrap(element) — Bẫy focus trong modal
Khi modal mở, Tab/Shift+Tab chỉ cycle trong modal. ESC dispatch event vnkr:close.
import { focusTrap } from '@vnkr-labs/a11y';
// Khi mở modal
const cleanup = focusTrap(document.getElementById('my-modal'));
// Khi đóng modal
cleanup();
// Lắng nghe ESC
modal.addEventListener('vnkr:close', () => closeModal());announce(message, politeness?) — Screen reader notification
import { announce } from '@vnkr-labs/a11y';
// Thông báo lịch sự (khi tác vụ hoàn thành)
announce('Đã lưu bài viết thành công');
// Thông báo khẩn (lỗi, cảnh báo quan trọng)
announce('Lỗi: Không thể kết nối mạng', 'assertive');Tự động inject ARIA live region ẩn vào DOM — không cần thêm markup.
skipLink(targetId?, label?) — Bỏ qua điều hướng
WCAG 2.4.1 — Bypass Blocks (Level A). Chỉ hiển thị khi keyboard focus.
import { skipLink } from '@vnkr-labs/a11y';
// Gọi 1 lần khi app init
skipLink(); // → link đến #main-content
skipLink('article', 'Chuyển đến bài viết'); // custom<!-- Đảm bảo main content có id tương ứng -->
<main id="main-content">...</main>colorContrast(fg, bg) — Tính contrast ratio
import { colorContrast, checkContrast } from '@vnkr-labs/a11y';
// Tính ratio
colorContrast('#0A3D62', '#FFFFFF') // → 8.59
colorContrast('#FC7339', '#FFFFFF') // → 2.91
// Kiểm tra pass/fail WCAG
checkContrast('#0A3D62', '#FFFFFF')
// → { ratio: 8.59, AA: true, AAA: true, level: 'AAA' }
checkContrast('#FC7339', '#FFFFFF')
// → { ratio: 2.91, AA: false, AAA: false, level: 'FAIL' }
// Text lớn (>=18pt) — threshold thấp hơn
checkContrast('#FC7339', '#FFFFFF', 'large')
// → { ratio: 2.91, AA: false, AAA: false, level: 'FAIL' }
// UI components
checkContrast('#0095FF', '#FFFFFF', 'ui')
// → { ratio: 3.43, AA: true, AAA: false, level: 'AA' }VNKR token contrast results (với nền trắng #FFFFFF):
| Token | Màu | Ratio | WCAG |
|---|---|---|---|
| --brand | #0A3D62 | 8.59 | ✅ AAA |
| --brand-alt | #1E6FA5 | 4.51 | ✅ AA |
| --color-error | #FF3D71 | 3.56 | ⚠️ AA (large) |
| --color-success | #00D68F | 1.84 | ❌ FAIL |
| --brand-orange | #FC7339 | 2.91 | ❌ FAIL |
| --brand-green | #8EFF6C | 1.37 | ❌ FAIL (nền tối OK) |
inertOthers(activeElement) — Ẩn phần tử ngoài modal
import { inertOthers } from '@vnkr-labs/a11y';
// Khi mở modal
const cleanup = inertOthers(modalEl);
// Khi đóng modal
cleanup();getFocusable(container) — Lấy focusable elements
import { getFocusable } from '@vnkr-labs/a11y';
const focusableEls = getFocusable(document.getElementById('form'));
// → [input, button, select, ...]Media query helpers
import { prefersReducedMotion, prefersHighContrast, prefersDarkMode } from '@vnkr-labs/a11y';
// Disable animation nếu user yêu cầu
if (prefersReducedMotion()) {
element.style.transition = 'none';
element.style.animation = 'none';
}
// High contrast mode
if (prefersHighContrast()) {
// Dùng màu đậm hơn
}
// Dark mode preference
if (prefersDarkMode()) {
document.documentElement.dataset.theme = 'dark';
}Keyboard helpers
import { isActivationKey, isNavigationKey, generateId } from '@vnkr-labs/a11y';
// Custom interactive element
div.addEventListener('keydown', (e) => {
if (isActivationKey(e)) handleClick(); // Enter hoặc Space
if (isNavigationKey(e)) handleNavigation(e); // Arrow keys
});
// ARIA relationship
const id = generateId('tooltip'); // → 'vnkr-tooltip-a3f2'
button.setAttribute('aria-describedby', id);
tooltip.id = id;WCAG 2.1 Checklist
| Criterion | Function | Level |
|---|---|---|
| 1.4.3 Contrast (Minimum) | checkContrast() | AA |
| 1.4.6 Contrast (Enhanced) | checkContrast() | AAA |
| 2.1.1 Keyboard | focusTrap(), isActivationKey() | A |
| 2.4.1 Bypass Blocks | skipLink() | A |
| 2.4.3 Focus Order | focusTrap(), getFocusable() | A |
| 3.3.1 Error Identification | announce('...', 'assertive') | A |
| 4.1.3 Status Messages | announce() | AA |
| 1.3.1 Info and Relationships | inertOthers(), generateId() | A |
Peer Packages
@vnkr-labs/tokens— CSS design tokens@vnkr-labs/ui— UI component library (đã dùng focusTrap nội bộ)
VNKR Labs — vnkr.vn
