-
Notifications
You must be signed in to change notification settings - Fork 0
Accessibility Quick Reference
github-actions[bot] edited this page Sep 2, 2026
·
46 revisions
Quick reference card for implementing accessible features in nself-chat.
// Context
import { useAccessibility } from '@/contexts/accessibility-context'
// Hooks
import { useFocusTrap, useAnnouncer, useArrowNavigation } from '@/hooks/use-a11y'
// Utilities
import { announce, getMessageLabel, checkContrast } from '@/lib/a11y'// Icon-only button
<button aria-label="Send message">
<SendIcon />
</button>
// With screen reader text
<button>
<SendIcon />
<span className="sr-only">Send message</span>
</button><label htmlFor="email">Email</label>
<input
id="email"
type="email"
aria-invalid={!!error}
aria-describedby={error ? "email-error" : undefined}
/>
{error && <span id="email-error" role="alert">{error}</span>}const modalRef = useFocusTrap(isOpen, { returnFocus: true, onEscape: onClose })
return (
<div ref={modalRef} role="dialog" aria-modal="true" aria-labelledby="title">
<h2 id="title">Modal Title</h2>
{/* content */}
</div>
)const listRef = useRef(null)
useArrowNavigation(listRef, { orientation: 'vertical', loop: true })
return (
<div ref={listRef} role="list">
{items.map((item, i) => (
<a key={item.id} tabIndex={i === 0 ? 0 : -1}>
{item.name}
</a>
))}
</div>
)- Semantic HTML (
<button>,<nav>,<main>, etc.) - Proper ARIA labels
- Keyboard accessible
- Focus visible
- Color contrast β₯ 4.5:1
- Touch targets β₯ 44Γ44px
- Has accessible name (label, aria-label, or aria-labelledby)
- Has appropriate role
- States communicated (aria-expanded, aria-pressed, etc.)
- Keyboard operable (Enter/Space for activation)
- All inputs have labels
- Required fields marked with aria-required
- Errors linked with aria-describedby
- Error messages have role="alert"
- Loading states have aria-busy
- Focus trap active
- Focus returns on close
- ESC key closes
- role="dialog" and aria-modal="true"
- Labeled with aria-labelledby
| Attribute | Use Case | Example |
|---|---|---|
aria-label |
Label for icon-only elements | <button aria-label="Close">Γ</button> |
aria-labelledby |
Reference to label element | <dialog aria-labelledby="title"> |
aria-describedby |
Additional description | <input aria-describedby="hint"> |
aria-expanded |
Expandable elements | <button aria-expanded={isOpen}> |
aria-pressed |
Toggle buttons | <button aria-pressed={isActive}> |
aria-invalid |
Form validation | <input aria-invalid={!!error}> |
aria-busy |
Loading states | <div aria-busy={isLoading}> |
aria-live |
Dynamic content | <div aria-live="polite"> |
aria-hidden |
Decorative elements | <span aria-hidden="true">π</span> |
aria-modal |
Modal dialogs | <div role="dialog" aria-modal="true"> |
import { checkContrast, hexToRgb } from '@/lib/a11y'
const fg = hexToRgb('#333333')
const bg = hexToRgb('#FFFFFF')
const result = checkContrast(fg, bg, { fontSize: 16 })
// result.ratio: 12.63
// result.level: 'AAA'
// result.passes.AA: trueimport { suggestAccessibleColor } from '@/lib/a11y'
const better = suggestAccessibleColor('#777777', '#FFFFFF', 4.5)
// Returns adjusted color that meets ratio| Shortcut | Action |
|---|---|
Alt+A |
Open accessibility menu |
Tab |
Next element |
Shift+Tab |
Previous element |
Escape |
Close modal/dialog |
Enter / Space
|
Activate button |
| Shortcut | Action |
|---|---|
Arrow Up/Down |
Previous/Next item |
Arrow Left/Right |
Previous/Next (horizontal) |
Home |
First item |
End |
Last item |
Enter / Space
|
Select item |
import { announce } from '@/lib/a11y'
// Polite (wait for pause)
announce('Message sent')
// Assertive (interrupt immediately)
announce('Error occurred', 'assertive')import { getMessageLabel } from '@/lib/a11y'
const label = getMessageLabel('Hello world', 'John Doe', new Date(), {
isEdited: true,
hasAttachments: true,
})
// "Message from John Doe, just now, edited, 1 attachment: Hello world"const modalRef = useFocusTrap(isOpen, {
returnFocus: true, // Return focus on close
onEscape: onClose, // Close on ESC key
})const formRef = useRef(null)
useFocusFirstInput(formRef, true)import { focusElement } from '@/lib/a11y'
const handleOpen = () => {
const element = document.getElementById('first-input')
if (element) focusElement(element as HTMLElement)
}<span className="sr-only">Hidden from view but announced by screen readers</span>/* Automatic with :focus-visible */
button:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}const { settings } = useAccessibility()
<div data-high-contrast={settings.highContrast}>
{/* Automatically styled via CSS */}
</div>const prefersReducedMotion = usePrefersReducedMotion()
<motion.div
animate={prefersReducedMotion ? {} : { scale: 1.1 }}
>
{/* No animation if reduced motion */}
</motion.div>- Keyboard Only: Unplug mouse, navigate with Tab
- Screen Reader: Enable NVDA/VoiceOver
- High Contrast: Enable in accessibility menu
- Zoom: Test at 200% zoom
- Mobile: Test touch targets
# Run accessibility tests
pnpm test:a11y
# Check specific file
pnpm test src/components/MyComponent.test.tsx// Icon without label
<button><TrashIcon /></button>
// Click handler on div
<div onClick={handleClick}>Click me</div>
// Placeholder as label
<input placeholder="Email" />
// Color-only indicator
<span style={{ color: 'red' }}>Error</span>
// Missing alt text
<img src="photo.jpg" />// Icon with label
<button aria-label="Delete"><TrashIcon /></button>
// Use button element
<button onClick={handleClick}>Click me</button>
// Proper label
<label htmlFor="email">Email</label>
<input id="email" type="email" />
// Icon + text for errors
<span role="alert">
<ErrorIcon /> Error: Invalid input
</span>
// Descriptive alt text
<img src="photo.jpg" alt="Team photo at 2026 retreat" />| Resource | Link |
|---|---|
| Full Guide | /docs/Accessibility-Guide.md |
| Examples | /src/components/accessibility/examples.tsx |
| WCAG 2.1 | https://www.w3.org/WAI/WCAG21/quickref/ |
| ARIA Practices | https://www.w3.org/WAI/ARIA/apg/ |
- Check
/docs/Accessibility-Guide.md - Review
/src/components/accessibility/examples.tsx - Test with screen reader
- Ask team lead or accessibility expert
Last Updated: February 1, 2026
nself-chat v0.3.0 | GitHub | Issues | Discussions | Demo
Edit this page | MIT License | Β© 2026
(See π Security section below for 2FA, PIN Lock, and security audits.)
(Search lives in π Reference below.)
- π¬ Advanced Messaging
- π E2EE Setup
- π Search Setup
- π Call Management
- πΊ Live Streaming
- π₯οΈ Screen Sharing
- πΉ Video Calling
- ποΈ Voice Calling
- π± Mobile Optimization
- π§ͺ Testing
- π i18n
- π API Overview
- π Complete Reference
- π» API Examples
- π€ Bot API
- π Auth API
- π GraphQL Schema
- π Deployment Overview
- π³ Docker
- βΈοΈ Kubernetes
- β Helm Charts
- β Production Checklist
- π Production Validation
- π’ Multi-Tenant
- ποΈ Architecture
- π Diagrams
- ποΈ Database Schema
- π Project Structure
- π TypeScript Types
- π SPORT Reference
- π 2FA
- π¬ Messaging
- π Call Management
- π Call State Machine
- π E2EE
- πΊ Live Streaming
- π± Mobile Calls
- π PIN Lock
- π Polls
- π₯οΈ Screen Sharing
- π Search
- π Social Media
- ποΈ Voice Calling
- π Security Overview
- π‘οΈ Security Audit
- β‘ Performance
- π Best Practices
- π 2FA
- π PIN Lock
- π E2EE
- π‘οΈ E2EE Audit
v1.0.0 β’ 2026