@zoyth/simple-site-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +572 -0
- package/bin/create-simple-site.js +390 -0
- package/bin/simple-site.js +664 -0
- package/dist/client.js +135 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +107 -0
- package/dist/client.mjs.map +1 -0
- package/dist/components/index.d.mts +3936 -0
- package/dist/components/index.d.ts +3936 -0
- package/dist/components/index.js +38265 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +38173 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.d.mts +298 -0
- package/dist/config/index.d.ts +298 -0
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +1 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/index.d.mts +2184 -0
- package/dist/index.d.ts +2184 -0
- package/dist/index.js +1713 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1605 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/i18n/index.js +665 -0
- package/dist/lib/i18n/index.js.map +1 -0
- package/dist/lib/i18n/index.mjs +621 -0
- package/dist/lib/i18n/index.mjs.map +1 -0
- package/docs/DOCUMENTATION-STRUCTURE.md +1156 -0
- package/docs/EXPORTS.md +125 -0
- package/docs/PERFORMANCE.md +757 -0
- package/docs/POLICY-PAGES.md +867 -0
- package/docs/ROADMAP.md +334 -0
- package/docs/SEO.md +455 -0
- package/docs/SITEMAP.md +708 -0
- package/docs/STRUCTURED-DATA.md +671 -0
- package/docs/accessibility/common-patterns.md +529 -0
- package/docs/accessibility/keyboard-navigation.md +263 -0
- package/docs/accessibility/overview.md +122 -0
- package/docs/accessibility/screen-readers.md +311 -0
- package/docs/accessibility/wcag-compliance.md +159 -0
- package/docs/api/README.md +164 -0
- package/docs/api/components/Accessibility.md +356 -0
- package/docs/api/components/Button.md +240 -0
- package/docs/api/components/HeroSection.md +306 -0
- package/docs/architecture/decisions.md +449 -0
- package/docs/components/AnalyticsTracker.md +58 -0
- package/docs/components/AnimatedCounter.md +48 -0
- package/docs/components/AnimatedSection.md +56 -0
- package/docs/components/BlogCard.md +42 -0
- package/docs/components/Checkbox.md +56 -0
- package/docs/components/CodeBlock.md +52 -0
- package/docs/components/ComparisonTable.md +40 -0
- package/docs/components/ComponentDemo.md +38 -0
- package/docs/components/CountdownTimer.md +51 -0
- package/docs/components/ExitIntentModal.md +56 -0
- package/docs/components/FAQAccordion.md +66 -0
- package/docs/components/FeaturesGrid.md +55 -0
- package/docs/components/FileUpload.md +54 -0
- package/docs/components/I18nMetaTags.md +55 -0
- package/docs/components/Icon.md +53 -0
- package/docs/components/LazySection.md +46 -0
- package/docs/components/LiveProof.md +53 -0
- package/docs/components/LoadingSpinner.md +46 -0
- package/docs/components/MultiStepForm.md +48 -0
- package/docs/components/PolicyLayout.md +55 -0
- package/docs/components/PricingTable.md +49 -0
- package/docs/components/Radio.md +59 -0
- package/docs/components/SEOMetaTags.md +58 -0
- package/docs/components/ScriptInjector.md +50 -0
- package/docs/components/Select.md +72 -0
- package/docs/components/Skeleton.md +47 -0
- package/docs/components/StatsSection.md +48 -0
- package/docs/components/StickyBar.md +62 -0
- package/docs/components/StructuredData.md +99 -0
- package/docs/components/StyleGuide.md +46 -0
- package/docs/components/TableOfContents.md +47 -0
- package/docs/components/TestimonialCarousel.md +42 -0
- package/docs/components/Timeline.md +51 -0
- package/docs/components/Toast.md +59 -0
- package/docs/components/TrackedLink.md +62 -0
- package/docs/components/TrustBadges.md +44 -0
- package/docs/components/conversion/MobileCTA.md +363 -0
- package/docs/components/forms/ContactForm.md +75 -0
- package/docs/components/forms/FormField.md +74 -0
- package/docs/components/layout/Footer.md +601 -0
- package/docs/components/layout/Header.md +549 -0
- package/docs/components/layout/LanguageSelector.md +54 -0
- package/docs/components/layout/LanguageSwitcher.md +24 -0
- package/docs/components/overview.md +447 -0
- package/docs/components/sections/AboutSection.md +48 -0
- package/docs/components/sections/CTASection.md +596 -0
- package/docs/components/sections/CaseStudySection.md +47 -0
- package/docs/components/sections/ContactSection.md +599 -0
- package/docs/components/sections/FeatureSection.md +44 -0
- package/docs/components/sections/HeroSection.md +404 -0
- package/docs/components/sections/LogosSection.md +47 -0
- package/docs/components/sections/PersonalTaxesSection.md +23 -0
- package/docs/components/sections/RecruitingSection.md +23 -0
- package/docs/components/sections/SecurePortalSection.md +23 -0
- package/docs/components/sections/ServicePageLayout.md +52 -0
- package/docs/components/sections/ServicesSection.md +49 -0
- package/docs/components/sections/TestimonialSection.md +44 -0
- package/docs/components/sections/WhyChooseUsSection.md +54 -0
- package/docs/components/ui/Breadcrumb.md +70 -0
- package/docs/components/ui/Button.md +514 -0
- package/docs/components/ui/Card.md +501 -0
- package/docs/components/ui/Input.md +54 -0
- package/docs/components/ui/MobileLinks.md +43 -0
- package/docs/components/ui/Modal.md +60 -0
- package/docs/components/ui/Tabs.md +62 -0
- package/docs/components/ui/Textarea.md +52 -0
- package/docs/core-concepts/configuration-driven.md +552 -0
- package/docs/core-concepts/overview.md +351 -0
- package/docs/features/accessibility/README.md +73 -0
- package/docs/features/accessibility/aria-support.md +177 -0
- package/docs/features/accessibility/color-contrast.md +155 -0
- package/docs/features/accessibility/focus-management.md +187 -0
- package/docs/features/accessibility/testing.md +196 -0
- package/docs/features/analytics/README.md +51 -0
- package/docs/features/analytics/ab-testing.md +171 -0
- package/docs/features/analytics/conversion-tracking.md +207 -0
- package/docs/features/analytics/custom-events.md +219 -0
- package/docs/features/analytics/privacy.md +198 -0
- package/docs/features/analytics/setup.md +114 -0
- package/docs/features/analytics/tracking-events.md +224 -0
- package/docs/features/i18n/README.md +51 -0
- package/docs/features/i18n/best-practices.md +273 -0
- package/docs/features/i18n/configuration.md +84 -0
- package/docs/features/i18n/formatting.md +133 -0
- package/docs/features/i18n/locale-detection.md +122 -0
- package/docs/features/i18n/routing.md +99 -0
- package/docs/features/i18n/rtl-support.md +191 -0
- package/docs/features/i18n/translations.md +129 -0
- package/docs/features/internationalization.md +595 -0
- package/docs/features/performance/README.md +77 -0
- package/docs/features/performance/bundle-size.md +134 -0
- package/docs/features/performance/caching.md +131 -0
- package/docs/features/performance/code-splitting.md +121 -0
- package/docs/features/performance/image-optimization.md +110 -0
- package/docs/features/performance/lazy-loading.md +92 -0
- package/docs/features/performance/monitoring.md +148 -0
- package/docs/features/seo/README.md +51 -0
- package/docs/features/seo/best-practices.md +184 -0
- package/docs/features/seo/canonical-urls.md +182 -0
- package/docs/features/seo/meta-tags.md +126 -0
- package/docs/features/seo/open-graph.md +166 -0
- package/docs/features/seo/robots-txt.md +146 -0
- package/docs/features/seo/sitemaps.md +162 -0
- package/docs/features/seo/structured-data.md +166 -0
- package/docs/getting-started/installation.md +292 -0
- package/docs/getting-started/introduction.md +195 -0
- package/docs/getting-started/quick-start.md +460 -0
- package/docs/guides/analytics-setup.md +616 -0
- package/docs/i18n/CONFIGURATION.md +353 -0
- package/docs/i18n/EXAMPLES.md +402 -0
- package/docs/i18n/MIGRATION.md +260 -0
- package/docs/i18n/SEO.md +392 -0
- package/docs/i18n/STATIC-GENERATION-FIX.md +71 -0
- package/docs/migration/changelog.md +136 -0
- package/docs/migration/overview.md +233 -0
- package/docs/recipes/adding-animations.md +475 -0
- package/docs/recipes/forms-with-validation.md +393 -0
- package/package.json +152 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Keyboard Navigation Guide
|
|
2
|
+
|
|
3
|
+
All Simple Site Framework components support full keyboard navigation. This guide covers keyboard patterns and testing.
|
|
4
|
+
|
|
5
|
+
## Standard Keyboard Controls
|
|
6
|
+
|
|
7
|
+
### Tab Navigation
|
|
8
|
+
- **Tab**: Move focus forward through interactive elements
|
|
9
|
+
- **Shift + Tab**: Move focus backward
|
|
10
|
+
- **Enter/Space**: Activate buttons, links, and controls
|
|
11
|
+
- **Escape**: Close modals, menus, and dropdowns
|
|
12
|
+
|
|
13
|
+
### Component-Specific
|
|
14
|
+
- **Arrow Keys**: Navigate within lists, menus, tabs, and carousels
|
|
15
|
+
- **Home/End**: Jump to first/last item in lists
|
|
16
|
+
- **Page Up/Down**: Scroll or navigate in long lists
|
|
17
|
+
|
|
18
|
+
## Component Keyboard Patterns
|
|
19
|
+
|
|
20
|
+
### Button
|
|
21
|
+
```tsx
|
|
22
|
+
<Button onClick={handleClick}>
|
|
23
|
+
Submit
|
|
24
|
+
</Button>
|
|
25
|
+
```
|
|
26
|
+
- **Tab**: Focus the button
|
|
27
|
+
- **Enter/Space**: Activate the button
|
|
28
|
+
|
|
29
|
+
### Modal
|
|
30
|
+
```tsx
|
|
31
|
+
<Modal isOpen={isOpen} onClose={handleClose}>
|
|
32
|
+
{/* Modal content */}
|
|
33
|
+
</Modal>
|
|
34
|
+
```
|
|
35
|
+
- **Tab**: Cycle through focusable elements inside modal
|
|
36
|
+
- **Shift + Tab**: Cycle backward
|
|
37
|
+
- **Escape**: Close modal
|
|
38
|
+
- Focus is **trapped** inside modal while open
|
|
39
|
+
- Focus **returns** to trigger when closed
|
|
40
|
+
|
|
41
|
+
### Tabs
|
|
42
|
+
```tsx
|
|
43
|
+
<Tabs tabs={tabs} value={activeTab} onValueChange={setActiveTab} />
|
|
44
|
+
```
|
|
45
|
+
- **Tab**: Focus the tab list
|
|
46
|
+
- **Arrow Left/Right**: Navigate between tabs
|
|
47
|
+
- **Home**: Jump to first tab
|
|
48
|
+
- **End**: Jump to last tab
|
|
49
|
+
- **Enter/Space**: Activate selected tab
|
|
50
|
+
|
|
51
|
+
### Select/Dropdown
|
|
52
|
+
```tsx
|
|
53
|
+
<Select options={options} value={value} onChange={setValue} />
|
|
54
|
+
```
|
|
55
|
+
- **Tab**: Focus the select
|
|
56
|
+
- **Enter/Space/Arrow Down**: Open dropdown
|
|
57
|
+
- **Arrow Up/Down**: Navigate options
|
|
58
|
+
- **Home/End**: Jump to first/last option
|
|
59
|
+
- **Enter/Space**: Select option
|
|
60
|
+
- **Escape**: Close without selecting
|
|
61
|
+
- **Type ahead**: Jump to option starting with typed character
|
|
62
|
+
|
|
63
|
+
### Accordion
|
|
64
|
+
```tsx
|
|
65
|
+
<FAQAccordion items={faqItems} />
|
|
66
|
+
```
|
|
67
|
+
- **Tab**: Focus accordion header
|
|
68
|
+
- **Enter/Space**: Toggle section
|
|
69
|
+
- **Arrow Down**: Move to next header
|
|
70
|
+
- **Arrow Up**: Move to previous header
|
|
71
|
+
- **Home**: Jump to first header
|
|
72
|
+
- **End**: Jump to last header
|
|
73
|
+
|
|
74
|
+
### Carousel/Slider
|
|
75
|
+
```tsx
|
|
76
|
+
<TestimonialCarousel testimonials={items} />
|
|
77
|
+
```
|
|
78
|
+
- **Tab**: Focus carousel controls
|
|
79
|
+
- **Arrow Left/Right**: Navigate slides
|
|
80
|
+
- **Space/Enter**: Pause/play autoplay
|
|
81
|
+
|
|
82
|
+
### Multi-Step Form
|
|
83
|
+
```tsx
|
|
84
|
+
<MultiStepForm>
|
|
85
|
+
{/* Form steps */}
|
|
86
|
+
</MultiStepForm>
|
|
87
|
+
```
|
|
88
|
+
- **Tab**: Navigate through form fields
|
|
89
|
+
- **Enter**: Submit step or form
|
|
90
|
+
- **Arrow Keys**: Navigate between radio buttons/checkboxes in groups
|
|
91
|
+
|
|
92
|
+
## Focus Management
|
|
93
|
+
|
|
94
|
+
### Skip Links
|
|
95
|
+
Allow keyboard users to skip repetitive navigation:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { SkipLink } from '@zoyth/simple-site-framework'
|
|
99
|
+
|
|
100
|
+
<SkipLink href="#main">
|
|
101
|
+
Skip to main content
|
|
102
|
+
</SkipLink>
|
|
103
|
+
<SkipLink href="#nav">
|
|
104
|
+
Skip to navigation
|
|
105
|
+
</SkipLink>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Focus Trap (Modals/Dialogs)
|
|
109
|
+
Keep focus inside modal until closed:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { useFocusTrap } from '@zoyth/simple-site-framework'
|
|
113
|
+
|
|
114
|
+
function Modal({ isOpen, children }) {
|
|
115
|
+
const trapRef = useFocusTrap(isOpen)
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div ref={trapRef} role="dialog">
|
|
119
|
+
{children}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Focus Return
|
|
126
|
+
Return focus to trigger element after modal closes:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { useFocusReturn } from '@zoyth/simple-site-framework'
|
|
130
|
+
|
|
131
|
+
function Modal() {
|
|
132
|
+
const returnRef = useFocusReturn()
|
|
133
|
+
|
|
134
|
+
return <div ref={returnRef}>{/* content */}</div>
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Testing Keyboard Navigation
|
|
139
|
+
|
|
140
|
+
### Manual Testing Checklist
|
|
141
|
+
1. **Unplug your mouse** (or don't touch it)
|
|
142
|
+
2. **Tab through entire page**
|
|
143
|
+
- Can you reach all interactive elements?
|
|
144
|
+
- Is tab order logical?
|
|
145
|
+
- Is focus visible on all elements?
|
|
146
|
+
3. **Open and close all modals/menus**
|
|
147
|
+
- Does Escape close them?
|
|
148
|
+
- Does focus stay trapped inside?
|
|
149
|
+
- Does focus return when closed?
|
|
150
|
+
4. **Submit all forms**
|
|
151
|
+
- Can you fill out and submit with only keyboard?
|
|
152
|
+
- Are errors announced?
|
|
153
|
+
5. **Navigate all components**
|
|
154
|
+
- Do tabs work with arrows?
|
|
155
|
+
- Do dropdowns work as expected?
|
|
156
|
+
- Do carousels respond to arrows?
|
|
157
|
+
|
|
158
|
+
### Automated Testing
|
|
159
|
+
```tsx
|
|
160
|
+
import { testKeyboardNav } from '@zoyth/simple-site-framework/testing'
|
|
161
|
+
|
|
162
|
+
describe('Button', () => {
|
|
163
|
+
it('should activate on Enter and Space', () => {
|
|
164
|
+
const onClick = jest.fn()
|
|
165
|
+
const { getByRole } = render(<Button onClick={onClick}>Click</Button>)
|
|
166
|
+
|
|
167
|
+
testKeyboardNav(getByRole('button'), {
|
|
168
|
+
enter: onClick,
|
|
169
|
+
space: onClick
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Common Keyboard Traps (and How to Avoid)
|
|
176
|
+
|
|
177
|
+
### ❌ Keyboard Trap
|
|
178
|
+
```tsx
|
|
179
|
+
// BAD: Focus gets stuck in modal
|
|
180
|
+
<div onClick={() => setOpen(false)}>
|
|
181
|
+
<input /> {/* Focus can't reach close button */}
|
|
182
|
+
</div>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### ✅ Proper Modal
|
|
186
|
+
```tsx
|
|
187
|
+
// GOOD: Use framework Modal with focus trap
|
|
188
|
+
<Modal isOpen={isOpen} onClose={() => setOpen(false)}>
|
|
189
|
+
<input />
|
|
190
|
+
<Button onClick={() => setOpen(false)}>Close</Button>
|
|
191
|
+
</Modal>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### ❌ Missing Tab Index
|
|
195
|
+
```tsx
|
|
196
|
+
// BAD: Custom div button not keyboard accessible
|
|
197
|
+
<div onClick={handleClick}>Click me</div>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### ✅ Semantic Button
|
|
201
|
+
```tsx
|
|
202
|
+
// GOOD: Use semantic button or framework Button
|
|
203
|
+
<Button onClick={handleClick}>Click me</Button>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### ❌ No Escape to Close
|
|
207
|
+
```tsx
|
|
208
|
+
// BAD: No way to close with keyboard
|
|
209
|
+
<div className="modal">
|
|
210
|
+
<button onClick={handleClose}>×</button>
|
|
211
|
+
</div>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### ✅ Escape Handler
|
|
215
|
+
```tsx
|
|
216
|
+
// GOOD: Escape key closes modal
|
|
217
|
+
<Modal onClose={handleClose} isOpen={isOpen}>
|
|
218
|
+
{/* content */}
|
|
219
|
+
</Modal>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Focus Indicator Best Practices
|
|
223
|
+
|
|
224
|
+
### Visible Focus
|
|
225
|
+
All framework components have visible focus indicators:
|
|
226
|
+
```css
|
|
227
|
+
/* Default focus ring */
|
|
228
|
+
.button:focus-visible {
|
|
229
|
+
outline: 2px solid var(--color-primary);
|
|
230
|
+
outline-offset: 2px;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Custom Focus Styles
|
|
235
|
+
Override in your theme:
|
|
236
|
+
```tsx
|
|
237
|
+
const theme = {
|
|
238
|
+
colors: {
|
|
239
|
+
focusRing: '#FF7800',
|
|
240
|
+
},
|
|
241
|
+
// ...
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Never Remove Focus
|
|
246
|
+
```css
|
|
247
|
+
/* ❌ NEVER DO THIS */
|
|
248
|
+
*:focus {
|
|
249
|
+
outline: none;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ✅ Style it, but keep it visible */
|
|
253
|
+
*:focus-visible {
|
|
254
|
+
outline: 2px solid blue;
|
|
255
|
+
outline-offset: 2px;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Resources
|
|
260
|
+
|
|
261
|
+
- [WebAIM Keyboard Testing](https://webaim.org/articles/keyboard/)
|
|
262
|
+
- [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
|
|
263
|
+
- [Framework Component Guides](./components/)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Accessibility Overview
|
|
2
|
+
|
|
3
|
+
The Simple Site Framework is built with accessibility as a core principle, ensuring that all components meet WCAG 2.1 Level AA standards by default.
|
|
4
|
+
|
|
5
|
+
## Our Commitment
|
|
6
|
+
|
|
7
|
+
- **Semantic HTML**: All components use proper HTML5 semantic elements
|
|
8
|
+
- **Keyboard Navigation**: Full keyboard support for all interactive elements
|
|
9
|
+
- **Screen Reader Support**: ARIA attributes and announcements where needed
|
|
10
|
+
- **Color Contrast**: AA-compliant color ratios (4.5:1 for text, 3:1 for UI)
|
|
11
|
+
- **Focus Management**: Clear focus indicators and logical focus order
|
|
12
|
+
- **Responsive**: Touch targets meet minimum 44x44px on mobile
|
|
13
|
+
|
|
14
|
+
## WCAG 2.1 AA Compliance
|
|
15
|
+
|
|
16
|
+
All framework components are designed to meet:
|
|
17
|
+
- **Perceivable**: Content visible to all users
|
|
18
|
+
- **Operable**: Interface usable via keyboard and other input methods
|
|
19
|
+
- **Understandable**: Clear labels, instructions, and error messages
|
|
20
|
+
- **Robust**: Compatible with assistive technologies
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Use Semantic Components
|
|
25
|
+
```tsx
|
|
26
|
+
// Good - semantic and accessible
|
|
27
|
+
<Button onClick={handleClick}>Submit</Button>
|
|
28
|
+
|
|
29
|
+
// Avoid - div buttons need extra work
|
|
30
|
+
<div onClick={handleClick}>Submit</div>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Add ARIA Labels
|
|
34
|
+
```tsx
|
|
35
|
+
<Button
|
|
36
|
+
icon={<Icons.Search />}
|
|
37
|
+
iconOnly
|
|
38
|
+
aria-label="Search"
|
|
39
|
+
/>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. Test with Keyboard
|
|
43
|
+
All interactive elements should work with:
|
|
44
|
+
- `Tab` - Navigate forward
|
|
45
|
+
- `Shift + Tab` - Navigate backward
|
|
46
|
+
- `Enter/Space` - Activate buttons
|
|
47
|
+
- `Escape` - Close modals/menus
|
|
48
|
+
- `Arrow keys` - Navigate within components
|
|
49
|
+
|
|
50
|
+
### 4. Check Color Contrast
|
|
51
|
+
Use our built-in utilities or browser DevTools to verify:
|
|
52
|
+
- Normal text: 4.5:1 minimum
|
|
53
|
+
- Large text (18pt+): 3:1 minimum
|
|
54
|
+
- UI components: 3:1 minimum
|
|
55
|
+
|
|
56
|
+
## Testing Tools
|
|
57
|
+
|
|
58
|
+
### Development
|
|
59
|
+
> **Note**: The `A11yChecker` component is planned for v0.2.0 and not yet available.
|
|
60
|
+
|
|
61
|
+
For now, use these external tools for accessibility testing:
|
|
62
|
+
|
|
63
|
+
### Automated Testing
|
|
64
|
+
- **axe DevTools**: Browser extension for automated accessibility testing
|
|
65
|
+
- **WAVE**: Browser extension for manual testing
|
|
66
|
+
- **Lighthouse**: Chrome DevTools accessibility audit
|
|
67
|
+
- **axe-core**: Can be integrated into your test suite
|
|
68
|
+
|
|
69
|
+
### Screen Reader Testing
|
|
70
|
+
- **Windows**: NVDA (free) or JAWS
|
|
71
|
+
- **macOS**: VoiceOver (built-in)
|
|
72
|
+
- **Mobile**: TalkBack (Android), VoiceOver (iOS)
|
|
73
|
+
|
|
74
|
+
## Common Patterns
|
|
75
|
+
|
|
76
|
+
### Skip Links
|
|
77
|
+
```tsx
|
|
78
|
+
import { SkipLink } from '@zoyth/simple-site-framework'
|
|
79
|
+
|
|
80
|
+
<SkipLink href="#main">Skip to main content</SkipLink>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Screen Reader Announcements
|
|
84
|
+
```tsx
|
|
85
|
+
import { useA11y } from '@zoyth/simple-site-framework'
|
|
86
|
+
|
|
87
|
+
const { announce } = useA11y()
|
|
88
|
+
|
|
89
|
+
const handleSubmit = async () => {
|
|
90
|
+
await submitForm()
|
|
91
|
+
announce('Form submitted successfully')
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Focus Management
|
|
96
|
+
```tsx
|
|
97
|
+
import { useFocusTrap } from '@zoyth/simple-site-framework'
|
|
98
|
+
|
|
99
|
+
function Modal({ isOpen }) {
|
|
100
|
+
const trapRef = useFocusTrap(isOpen)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div ref={trapRef} role="dialog" aria-modal="true">
|
|
104
|
+
{/* Modal content */}
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Resources
|
|
111
|
+
|
|
112
|
+
- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/)
|
|
113
|
+
- [A11y Project Checklist](https://www.a11yproject.com/checklist/)
|
|
114
|
+
- [WebAIM Resources](https://webaim.org/resources/)
|
|
115
|
+
- [Component A11y Guides](./components/)
|
|
116
|
+
|
|
117
|
+
## Need Help?
|
|
118
|
+
|
|
119
|
+
- Review component-specific guides in [./components/](./components/)
|
|
120
|
+
- Check the [accessibility checklist](./wcag-compliance.md)
|
|
121
|
+
- Test with [keyboard navigation guide](./keyboard-navigation.md)
|
|
122
|
+
- Verify with [screen reader testing guide](./screen-readers.md)
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Screen Reader Testing Guide
|
|
2
|
+
|
|
3
|
+
Testing with screen readers is essential to ensure your site is accessible to users with visual impairments.
|
|
4
|
+
|
|
5
|
+
## Screen Reader Software
|
|
6
|
+
|
|
7
|
+
### Windows
|
|
8
|
+
**NVDA (Free, Open Source)**
|
|
9
|
+
- Download: https://www.nvaccess.org/
|
|
10
|
+
- Most popular free option
|
|
11
|
+
- Good compatibility with modern web standards
|
|
12
|
+
|
|
13
|
+
**JAWS (Commercial)**
|
|
14
|
+
- Most widely used by professionals
|
|
15
|
+
- Expensive ($1000+)
|
|
16
|
+
- Very thorough testing tool
|
|
17
|
+
|
|
18
|
+
### macOS
|
|
19
|
+
**VoiceOver (Built-in)**
|
|
20
|
+
- Cmd + F5 to toggle on/off
|
|
21
|
+
- Free and pre-installed
|
|
22
|
+
- Good for basic testing
|
|
23
|
+
|
|
24
|
+
### Mobile
|
|
25
|
+
**iOS VoiceOver**
|
|
26
|
+
- Settings → Accessibility → VoiceOver
|
|
27
|
+
- Triple-click home button shortcut
|
|
28
|
+
|
|
29
|
+
**Android TalkBack**
|
|
30
|
+
- Settings → Accessibility → TalkBack
|
|
31
|
+
- Volume keys shortcut
|
|
32
|
+
|
|
33
|
+
## Quick Start with NVDA (Windows)
|
|
34
|
+
|
|
35
|
+
### Basic Controls
|
|
36
|
+
- **NVDA + Space**: Toggle browse/focus mode
|
|
37
|
+
- **↓**: Read next item
|
|
38
|
+
- **↑**: Read previous item
|
|
39
|
+
- **Tab**: Navigate to next focusable element
|
|
40
|
+
- **NVDA + T**: Read window title
|
|
41
|
+
- **NVDA + B**: Read entire page
|
|
42
|
+
- **Insert**: NVDA modifier key
|
|
43
|
+
|
|
44
|
+
### Testing Workflow
|
|
45
|
+
1. Start NVDA
|
|
46
|
+
2. Open your site
|
|
47
|
+
3. Press **NVDA + B** to read entire page
|
|
48
|
+
4. Use **↓** to navigate item by item
|
|
49
|
+
5. Use **Tab** to jump between interactive elements
|
|
50
|
+
6. Listen for:
|
|
51
|
+
- Clear labels and descriptions
|
|
52
|
+
- Proper heading hierarchy
|
|
53
|
+
- Form field labels
|
|
54
|
+
- Button text
|
|
55
|
+
- Image alt text
|
|
56
|
+
- Error messages
|
|
57
|
+
|
|
58
|
+
## Quick Start with VoiceOver (macOS)
|
|
59
|
+
|
|
60
|
+
### Basic Controls
|
|
61
|
+
- **Cmd + F5**: Toggle VoiceOver on/off
|
|
62
|
+
- **VO + →**: Next item (VO = Ctrl + Option)
|
|
63
|
+
- **VO + ←**: Previous item
|
|
64
|
+
- **VO + Cmd + H**: Next heading
|
|
65
|
+
- **VO + Cmd + Shift + H**: Previous heading
|
|
66
|
+
- **VO + U**: Open rotor (navigation menu)
|
|
67
|
+
- **Tab**: Next focusable element
|
|
68
|
+
|
|
69
|
+
### Testing Workflow
|
|
70
|
+
1. Press **Cmd + F5** to start
|
|
71
|
+
2. Open your site
|
|
72
|
+
3. Press **VO + A** to read entire page
|
|
73
|
+
4. Use **VO + →** to navigate
|
|
74
|
+
5. Press **VO + U** for rotor navigation
|
|
75
|
+
6. Test interactions with **VO + Space**
|
|
76
|
+
|
|
77
|
+
## What to Listen For
|
|
78
|
+
|
|
79
|
+
### ✅ Good Screen Reader Experience
|
|
80
|
+
- **Descriptive labels**: "Submit contact form" not just "Submit"
|
|
81
|
+
- **Clear headings**: "Contact Us - heading level 2"
|
|
82
|
+
- **Form labels**: "Email address, edit text, required"
|
|
83
|
+
- **Error messages**: "Email address, invalid format, edit text"
|
|
84
|
+
- **State changes**: "Loading complete" announcements
|
|
85
|
+
- **Alt text**: "Company logo" not "image-logo-final-v2.png"
|
|
86
|
+
|
|
87
|
+
### ❌ Poor Screen Reader Experience
|
|
88
|
+
- "Button" with no label
|
|
89
|
+
- "Image" with no alt text
|
|
90
|
+
- "Required" not announced
|
|
91
|
+
- "Edit text" with no label
|
|
92
|
+
- No headings or all h1s
|
|
93
|
+
- Silent state changes
|
|
94
|
+
- "Click here" links
|
|
95
|
+
|
|
96
|
+
## Component Testing Examples
|
|
97
|
+
|
|
98
|
+
### Button
|
|
99
|
+
```tsx
|
|
100
|
+
<Button onClick={handleSubmit}>Submit Form</Button>
|
|
101
|
+
```
|
|
102
|
+
**Reads**: "Submit Form, button"
|
|
103
|
+
|
|
104
|
+
### Icon Button
|
|
105
|
+
```tsx
|
|
106
|
+
<Button icon={<Icons.Search />} iconOnly aria-label="Search">
|
|
107
|
+
```
|
|
108
|
+
**Reads**: "Search, button"
|
|
109
|
+
|
|
110
|
+
### Form Field
|
|
111
|
+
```tsx
|
|
112
|
+
<FormField
|
|
113
|
+
name="email"
|
|
114
|
+
label="Email Address"
|
|
115
|
+
required
|
|
116
|
+
error={errors.email}
|
|
117
|
+
>
|
|
118
|
+
<input type="email" {...register('email')} />
|
|
119
|
+
</FormField>
|
|
120
|
+
```
|
|
121
|
+
**Reads**: "Email Address, edit text, required"
|
|
122
|
+
**If error**: "Email Address, invalid format, edit text, required"
|
|
123
|
+
|
|
124
|
+
### Modal
|
|
125
|
+
```tsx
|
|
126
|
+
<Modal isOpen={true} title="Confirm Action">
|
|
127
|
+
<p>Are you sure?</p>
|
|
128
|
+
</Modal>
|
|
129
|
+
```
|
|
130
|
+
**Reads**: "Confirm Action, dialog"
|
|
131
|
+
**Announces**: When modal opens, focus moves and title is read
|
|
132
|
+
|
|
133
|
+
### Live Region (Toast)
|
|
134
|
+
```tsx
|
|
135
|
+
toast.success('Form submitted successfully')
|
|
136
|
+
```
|
|
137
|
+
**Announces**: "Form submitted successfully" (polite)
|
|
138
|
+
|
|
139
|
+
### Loading State
|
|
140
|
+
```tsx
|
|
141
|
+
<Button loading={true}>Submit</Button>
|
|
142
|
+
```
|
|
143
|
+
**Reads**: "Submit, button, busy"
|
|
144
|
+
|
|
145
|
+
## ARIA Attributes and What They Do
|
|
146
|
+
|
|
147
|
+
### aria-label
|
|
148
|
+
Provides accessible name when visible text isn't enough:
|
|
149
|
+
```tsx
|
|
150
|
+
<Button icon={<Icons.Close />} aria-label="Close dialog" />
|
|
151
|
+
```
|
|
152
|
+
**Reads**: "Close dialog, button"
|
|
153
|
+
|
|
154
|
+
### aria-labelledby
|
|
155
|
+
References another element for label:
|
|
156
|
+
```tsx
|
|
157
|
+
<div role="dialog" aria-labelledby="dialog-title">
|
|
158
|
+
<h2 id="dialog-title">Confirm Delete</h2>
|
|
159
|
+
</div>
|
|
160
|
+
```
|
|
161
|
+
**Reads**: "Confirm Delete, dialog"
|
|
162
|
+
|
|
163
|
+
### aria-describedby
|
|
164
|
+
Provides additional description:
|
|
165
|
+
```tsx
|
|
166
|
+
<input
|
|
167
|
+
id="password"
|
|
168
|
+
type="password"
|
|
169
|
+
aria-describedby="password-hint"
|
|
170
|
+
/>
|
|
171
|
+
<p id="password-hint">
|
|
172
|
+
Must be at least 8 characters
|
|
173
|
+
</p>
|
|
174
|
+
```
|
|
175
|
+
**Reads**: "Password, edit text. Must be at least 8 characters"
|
|
176
|
+
|
|
177
|
+
### aria-live
|
|
178
|
+
Announces dynamic content changes:
|
|
179
|
+
```tsx
|
|
180
|
+
<div aria-live="polite" aria-atomic="true">
|
|
181
|
+
{statusMessage}
|
|
182
|
+
</div>
|
|
183
|
+
```
|
|
184
|
+
**Announces**: When statusMessage changes
|
|
185
|
+
|
|
186
|
+
### aria-hidden
|
|
187
|
+
Hides decorative content:
|
|
188
|
+
```tsx
|
|
189
|
+
<span aria-hidden="true">★</span>
|
|
190
|
+
<span className="sr-only">5 out of 5 stars</span>
|
|
191
|
+
```
|
|
192
|
+
**Reads**: "5 out of 5 stars" (star icon ignored)
|
|
193
|
+
|
|
194
|
+
## Common Screen Reader Issues
|
|
195
|
+
|
|
196
|
+
### Issue: Button Reads as "Button" Only
|
|
197
|
+
**Problem**: No accessible text
|
|
198
|
+
```tsx
|
|
199
|
+
<button onClick={handleClick}>
|
|
200
|
+
<Icon name="trash" />
|
|
201
|
+
</button>
|
|
202
|
+
```
|
|
203
|
+
**Fix**: Add aria-label
|
|
204
|
+
```tsx
|
|
205
|
+
<Button
|
|
206
|
+
icon={<Icons.Trash />}
|
|
207
|
+
iconOnly
|
|
208
|
+
aria-label="Delete item"
|
|
209
|
+
/>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Issue: Form Field Not Associated with Label
|
|
213
|
+
**Problem**: Label not programmatically connected
|
|
214
|
+
```tsx
|
|
215
|
+
<label>Email</label>
|
|
216
|
+
<input type="email" />
|
|
217
|
+
```
|
|
218
|
+
**Fix**: Use for/id or nest input
|
|
219
|
+
```tsx
|
|
220
|
+
<FormField name="email" label="Email">
|
|
221
|
+
<input type="email" />
|
|
222
|
+
</FormField>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Issue: State Changes Not Announced
|
|
226
|
+
**Problem**: Loading/success state is visual only
|
|
227
|
+
```tsx
|
|
228
|
+
<button disabled={isLoading}>
|
|
229
|
+
{isLoading ? 'Loading...' : 'Submit'}
|
|
230
|
+
</button>
|
|
231
|
+
```
|
|
232
|
+
**Fix**: Use aria-busy or toast announcement
|
|
233
|
+
```tsx
|
|
234
|
+
<Button loading={isLoading}>Submit</Button>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Issue: Modal Focus Not Managed
|
|
238
|
+
**Problem**: Focus doesn't move to modal
|
|
239
|
+
```tsx
|
|
240
|
+
<div className={isOpen ? 'modal' : 'modal hidden'}>
|
|
241
|
+
{/* content */}
|
|
242
|
+
</div>
|
|
243
|
+
```
|
|
244
|
+
**Fix**: Use framework Modal
|
|
245
|
+
```tsx
|
|
246
|
+
<Modal isOpen={isOpen} onClose={handleClose}>
|
|
247
|
+
{/* content */}
|
|
248
|
+
</Modal>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Testing Checklist
|
|
252
|
+
|
|
253
|
+
- [ ] All buttons and links have accessible text
|
|
254
|
+
- [ ] All form fields have associated labels
|
|
255
|
+
- [ ] Required fields are announced as required
|
|
256
|
+
- [ ] Form errors are announced clearly
|
|
257
|
+
- [ ] Headings create logical document outline
|
|
258
|
+
- [ ] Images have descriptive alt text (or alt="" if decorative)
|
|
259
|
+
- [ ] Modals announce when opened
|
|
260
|
+
- [ ] Focus moves logically through page
|
|
261
|
+
- [ ] State changes (loading, success, error) are announced
|
|
262
|
+
- [ ] Skip links work
|
|
263
|
+
- [ ] Data tables have proper headers
|
|
264
|
+
- [ ] Live regions announce dynamic content
|
|
265
|
+
|
|
266
|
+
## Framework Utilities for Screen Readers
|
|
267
|
+
|
|
268
|
+
### useA11y Hook
|
|
269
|
+
```tsx
|
|
270
|
+
import { useA11y } from '@zoyth/simple-site-framework'
|
|
271
|
+
|
|
272
|
+
const { announce } = useA11y()
|
|
273
|
+
|
|
274
|
+
const handleSubmit = async () => {
|
|
275
|
+
await submitForm()
|
|
276
|
+
announce('Form submitted successfully', 'polite')
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### A11yAnnouncer Component
|
|
281
|
+
```tsx
|
|
282
|
+
import { A11yAnnouncer } from '@zoyth/simple-site-framework'
|
|
283
|
+
|
|
284
|
+
<A11yAnnouncer
|
|
285
|
+
message={statusMessage}
|
|
286
|
+
politeness="polite" // or "assertive"
|
|
287
|
+
/>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Screen Reader Only Text
|
|
291
|
+
```tsx
|
|
292
|
+
// Visible to screen readers, hidden visually
|
|
293
|
+
<span className="sr-only">Required field</span>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Resources
|
|
297
|
+
|
|
298
|
+
- [NVDA User Guide](https://www.nvaccess.org/files/nvda/documentation/userGuide.html)
|
|
299
|
+
- [VoiceOver Guide](https://www.apple.com/voiceover/info/guide/)
|
|
300
|
+
- [WebAIM Screen Reader Testing](https://webaim.org/articles/screenreader_testing/)
|
|
301
|
+
- [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
|
|
302
|
+
|
|
303
|
+
## Pro Tips
|
|
304
|
+
|
|
305
|
+
1. **Test early and often** - Don't wait until the end
|
|
306
|
+
2. **Listen to the full page** - Navigate start to finish
|
|
307
|
+
3. **Test forms completely** - Fill out, submit, handle errors
|
|
308
|
+
4. **Test all interactive elements** - Buttons, links, modals, etc.
|
|
309
|
+
5. **Test with real users** - Nothing beats actual screen reader users
|
|
310
|
+
6. **Keep it simple** - Don't over-use ARIA, semantic HTML is often enough
|
|
311
|
+
7. **Test on mobile** - VoiceOver/TalkBack usage is common
|