accessibility-compliance

安装量: 273
排名: #7719

安装

npx skills add https://github.com/wshobson/agents --skill accessibility-compliance

Accessibility Compliance Master accessibility implementation to create inclusive experiences that work for everyone, including users with disabilities. When to Use This Skill Implementing WCAG 2.2 Level AA or AAA compliance Building screen reader accessible interfaces Adding keyboard navigation to interactive components Implementing focus management and focus trapping Creating accessible forms with proper labeling Supporting reduced motion and high contrast preferences Building mobile accessibility features (iOS VoiceOver, Android TalkBack) Conducting accessibility audits and fixing violations Core Capabilities 1. WCAG 2.2 Guidelines Perceivable: Content must be presentable in different ways Operable: Interface must be navigable with keyboard and assistive tech Understandable: Content and operation must be clear Robust: Content must work with current and future assistive technologies 2. ARIA Patterns Roles: Define element purpose (button, dialog, navigation) States: Indicate current condition (expanded, selected, disabled) Properties: Describe relationships and additional info (labelledby, describedby) Live regions: Announce dynamic content changes 3. Keyboard Navigation Focus order and tab sequence Focus indicators and visible focus states Keyboard shortcuts and hotkeys Focus trapping for modals and dialogs 4. Screen Reader Support Semantic HTML structure Alternative text for images Proper heading hierarchy Skip links and landmarks 5. Mobile Accessibility Touch target sizing (44x44dp minimum) VoiceOver and TalkBack compatibility Gesture alternatives Dynamic Type support Quick Reference WCAG 2.2 Success Criteria Checklist Level Criterion Description A 1.1.1 Non-text content has text alternatives A 1.3.1 Info and relationships programmatically determinable A 2.1.1 All functionality keyboard accessible A 2.4.1 Skip to main content mechanism AA 1.4.3 Contrast ratio 4.5:1 (text), 3:1 (large text) AA 1.4.11 Non-text contrast 3:1 AA 2.4.7 Focus visible AA 2.5.8 Target size minimum 24x24px (NEW in 2.2) AAA 1.4.6 Enhanced contrast 7:1 AAA 2.5.5 Target size minimum 44x44px Key Patterns Pattern 1: Accessible Button interface ButtonProps extends React . ButtonHTMLAttributes < HTMLButtonElement

{ variant ? : "primary" | "secondary" ; isLoading ? : boolean ; } function AccessibleButton ( { children , variant = "primary" , isLoading = false , disabled , ... props } : ButtonProps ) { return ( < button // Disable when loading disabled = { disabled || isLoading } // Announce loading state to screen readers aria-busy = { isLoading } // Describe the button's current state aria-disabled = { disabled || isLoading } className = { cn ( // Visible focus ring "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2" , // Minimum touch target size (44x44px) "min-h-[44px] min-w-[44px]" , variant === "primary" && "bg-primary text-primary-foreground" , ( disabled || isLoading ) && "opacity-50 cursor-not-allowed" , ) } { ... props }

{ isLoading ? ( <

< span className = " sr-only "

Loading </ span

< Spinner aria-hidden = " true " /> </

) : ( children ) } </ button

) ; } Pattern 2: Accessible Modal Dialog import * as React from "react" ; import { FocusTrap } from "@headlessui/react" ; interface DialogProps { isOpen : boolean ; onClose : ( ) => void ; title : string ; children : React . ReactNode ; } function AccessibleDialog ( { isOpen , onClose , title , children } : DialogProps ) { const titleId = React . useId ( ) ; const descriptionId = React . useId ( ) ; // Close on Escape key React . useEffect ( ( ) => { const handleKeyDown = ( e : KeyboardEvent ) => { if ( e . key === "Escape" && isOpen ) { onClose ( ) ; } } ; document . addEventListener ( "keydown" , handleKeyDown ) ; return ( ) => document . removeEventListener ( "keydown" , handleKeyDown ) ; } , [ isOpen , onClose ] ) ; // Prevent body scroll when open React . useEffect ( ( ) => { if ( isOpen ) { document . body . style . overflow = "hidden" ; } return ( ) => { document . body . style . overflow = "" ; } ; } , [ isOpen ] ) ; if ( ! isOpen ) return null ; return ( < div role = " dialog " aria-modal = " true " aria-labelledby = { titleId } aria-describedby = { descriptionId }

{ / Backdrop / } < div className = " fixed inset-0 bg-black/50 " aria-hidden = " true " onClick = { onClose } /> { / Focus trap container / } < FocusTrap

< div className = " fixed inset-0 flex items-center justify-center p-4 "

< div className = " bg-background rounded-lg shadow-lg max-w-md w-full p-6 "

< h2 id = { titleId } className = " text-lg font-semibold "

{ title } </ h2

< div id = { descriptionId }

{ children } </ div

< button onClick = { onClose } className = " absolute top-4 right-4 " aria-label = " Close dialog "

< X className = " h-4 w-4 " /> </ button

</ div

</ div

</ FocusTrap

</ div

) ; } Pattern 3: Accessible Form function AccessibleForm ( ) { const [ errors , setErrors ] = React . useState < Record < string , string

( { } ) ; return ( < form aria-describedby = " form-errors " noValidate

{ / Error summary for screen readers / } { Object . keys ( errors ) . length

0 && ( < div id = " form-errors " role = " alert " aria-live = " assertive " className = " bg-destructive/10 border border-destructive p-4 rounded-md mb-4 "

< h2 className = " font-semibold text-destructive "

Please fix the following errors: </ h2

< ul className = " list-disc list-inside mt-2 "

{ Object . entries ( errors ) . map ( ( [ field , message ] ) => ( < li key = { field }

< a href = { `

${ field } ` } className = " underline "

{ message } </ a

</ li

) ) } </ ul

</ div

) } { / Required field with error / } < div className = " space-y-2 "

< label htmlFor = " email " className = " block font-medium "

Email address < span aria-hidden = " true " className = " text-destructive ml-1 "

* </ span

< span className = " sr-only "

(required) </ span

</ label

< input id = " email " name = " email " type = " email " required aria-required = " true " aria-invalid = { ! ! errors . email } aria-describedby = { errors . email ? "email-error" : "email-hint" } className = { cn ( "w-full px-3 py-2 border rounded-md" , errors . email && "border-destructive" , ) } /> { errors . email ? ( < p id = " email-error " className = " text-sm text-destructive " role = " alert "

{ errors . email } </ p

) : ( < p id = " email-hint " className = " text-sm text-muted-foreground "

We'll never share your email. </ p

) } </ div

< button type = " submit " className = " mt-4 "

Submit </ button

</ form

) ; } Pattern 4: Skip Navigation Link function SkipLink ( ) { return ( < a href = "

main-content

"
className
=
{
cn
(
// Hidden by default, visible on focus
"sr-only focus:not-sr-only"
,
"focus:absolute focus:top-4 focus:left-4 focus:z-50"
,
"focus:bg-background focus:px-4 focus:py-2 focus:rounded-md"
,
"focus:ring-2 focus:ring-primary"
,
)
}
>
Skip to main content
</
a
>
)
;
}
// In layout
function
Layout
(
{
children
}
)
{
return
(
<
>
<
SkipLink
/>
<
header
>
...
</
header
>
<
nav
aria-label
=
"
Main navigation
"
>
...
</
nav
>
<
main
id
=
"
main-content
"
tabIndex
=
{
-
1
}
>
{
children
}
</
main
>
<
footer
>
...
</
footer
>
</
>
)
;
}
Pattern 5: Live Region for Announcements
function
useAnnounce
(
)
{
const
[
message
,
setMessage
]
=
React
.
useState
(
""
)
;
const
announce
=
React
.
useCallback
(
(
text
:
string
,
priority
:
"polite"
|
"assertive"
=
"polite"
)
=>
{
setMessage
(
""
)
;
// Clear first to ensure re-announcement
setTimeout
(
(
)
=>
setMessage
(
text
)
,
100
)
;
}
,
[
]
,
)
;
const
Announcer
=
(
)
=>
(
<
div
role
=
"
status
"
aria-live
=
"
polite
"
aria-atomic
=
"
true
"
className
=
"
sr-only
"
>
{
message
}
</
div
>
)
;
return
{
announce
,
Announcer
}
;
}
// Usage
function
SearchResults
(
{
results
,
isLoading
}
)
{
const
{
announce
,
Announcer
}
=
useAnnounce
(
)
;
React
.
useEffect
(
(
)
=>
{
if
(
!
isLoading
&&
results
)
{
announce
(
`
${
results
.
length
}
results found
`
)
;
}
}
,
[
results
,
isLoading
,
announce
]
)
;
return
(
<
>
<
Announcer
/>
<
ul
>
{
/ results /
}
</
ul
>
</
>
)
;
}
Color Contrast Requirements
// Contrast ratio utilities
function
getContrastRatio
(
foreground
:
string
,
background
:
string
)
:
number
{
const
fgLuminance
=
getLuminance
(
foreground
)
;
const
bgLuminance
=
getLuminance
(
background
)
;
const
lighter
=
Math
.
max
(
fgLuminance
,
bgLuminance
)
;
const
darker
=
Math
.
min
(
fgLuminance
,
bgLuminance
)
;
return
(
lighter
+
0.05
)
/
(
darker
+
0.05
)
;
}
// WCAG requirements
const
CONTRAST_REQUIREMENTS
=
{
// Normal text (<18pt or <14pt bold)
normalText
:
{
AA
:
4.5
,
AAA
:
7
,
}
,
// Large text (>=18pt or >=14pt bold)
largeText
:
{
AA
:
3
,
AAA
:
4.5
,
}
,
// UI components and graphics
uiComponents
:
{
AA
:
3
,
}
,
}
;
Best Practices
Use Semantic HTML
Prefer native elements over ARIA when possible
Test with Real Users
Include people with disabilities in user testing
Keyboard First
Design interactions to work without a mouse
Don't Disable Focus Styles
Style them, don't remove them
Provide Text Alternatives
All non-text content needs descriptions
Support Zoom
Content should work at 200% zoom
Announce Changes
Use live regions for dynamic content
Respect Preferences
Honor prefers-reduced-motion and prefers-contrast
Common Issues
Missing alt text
Images without descriptions
Poor color contrast
Text hard to read against background
Keyboard traps
Focus stuck in component
Missing labels
Form inputs without associated labels
Auto-playing media
Content that plays without user initiation
Inaccessible custom controls
Recreating native functionality poorly
Missing skip links
No way to bypass repetitive content
Focus order issues
Tab order doesn't match visual order
Testing Tools
Automated
axe DevTools, WAVE, Lighthouse
Manual
VoiceOver (macOS/iOS), NVDA/JAWS (Windows), TalkBack (Android)
Simulators
NoCoffee (vision), Silktide (various disabilities)
返回排行榜