@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.
Files changed (166) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +572 -0
  3. package/bin/create-simple-site.js +390 -0
  4. package/bin/simple-site.js +664 -0
  5. package/dist/client.js +135 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/client.mjs +107 -0
  8. package/dist/client.mjs.map +1 -0
  9. package/dist/components/index.d.mts +3936 -0
  10. package/dist/components/index.d.ts +3936 -0
  11. package/dist/components/index.js +38265 -0
  12. package/dist/components/index.js.map +1 -0
  13. package/dist/components/index.mjs +38173 -0
  14. package/dist/components/index.mjs.map +1 -0
  15. package/dist/config/index.d.mts +298 -0
  16. package/dist/config/index.d.ts +298 -0
  17. package/dist/config/index.js +19 -0
  18. package/dist/config/index.js.map +1 -0
  19. package/dist/config/index.mjs +1 -0
  20. package/dist/config/index.mjs.map +1 -0
  21. package/dist/index.d.mts +2184 -0
  22. package/dist/index.d.ts +2184 -0
  23. package/dist/index.js +1713 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/index.mjs +1605 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/lib/i18n/index.js +665 -0
  28. package/dist/lib/i18n/index.js.map +1 -0
  29. package/dist/lib/i18n/index.mjs +621 -0
  30. package/dist/lib/i18n/index.mjs.map +1 -0
  31. package/docs/DOCUMENTATION-STRUCTURE.md +1156 -0
  32. package/docs/EXPORTS.md +125 -0
  33. package/docs/PERFORMANCE.md +757 -0
  34. package/docs/POLICY-PAGES.md +867 -0
  35. package/docs/ROADMAP.md +334 -0
  36. package/docs/SEO.md +455 -0
  37. package/docs/SITEMAP.md +708 -0
  38. package/docs/STRUCTURED-DATA.md +671 -0
  39. package/docs/accessibility/common-patterns.md +529 -0
  40. package/docs/accessibility/keyboard-navigation.md +263 -0
  41. package/docs/accessibility/overview.md +122 -0
  42. package/docs/accessibility/screen-readers.md +311 -0
  43. package/docs/accessibility/wcag-compliance.md +159 -0
  44. package/docs/api/README.md +164 -0
  45. package/docs/api/components/Accessibility.md +356 -0
  46. package/docs/api/components/Button.md +240 -0
  47. package/docs/api/components/HeroSection.md +306 -0
  48. package/docs/architecture/decisions.md +449 -0
  49. package/docs/components/AnalyticsTracker.md +58 -0
  50. package/docs/components/AnimatedCounter.md +48 -0
  51. package/docs/components/AnimatedSection.md +56 -0
  52. package/docs/components/BlogCard.md +42 -0
  53. package/docs/components/Checkbox.md +56 -0
  54. package/docs/components/CodeBlock.md +52 -0
  55. package/docs/components/ComparisonTable.md +40 -0
  56. package/docs/components/ComponentDemo.md +38 -0
  57. package/docs/components/CountdownTimer.md +51 -0
  58. package/docs/components/ExitIntentModal.md +56 -0
  59. package/docs/components/FAQAccordion.md +66 -0
  60. package/docs/components/FeaturesGrid.md +55 -0
  61. package/docs/components/FileUpload.md +54 -0
  62. package/docs/components/I18nMetaTags.md +55 -0
  63. package/docs/components/Icon.md +53 -0
  64. package/docs/components/LazySection.md +46 -0
  65. package/docs/components/LiveProof.md +53 -0
  66. package/docs/components/LoadingSpinner.md +46 -0
  67. package/docs/components/MultiStepForm.md +48 -0
  68. package/docs/components/PolicyLayout.md +55 -0
  69. package/docs/components/PricingTable.md +49 -0
  70. package/docs/components/Radio.md +59 -0
  71. package/docs/components/SEOMetaTags.md +58 -0
  72. package/docs/components/ScriptInjector.md +50 -0
  73. package/docs/components/Select.md +72 -0
  74. package/docs/components/Skeleton.md +47 -0
  75. package/docs/components/StatsSection.md +48 -0
  76. package/docs/components/StickyBar.md +62 -0
  77. package/docs/components/StructuredData.md +99 -0
  78. package/docs/components/StyleGuide.md +46 -0
  79. package/docs/components/TableOfContents.md +47 -0
  80. package/docs/components/TestimonialCarousel.md +42 -0
  81. package/docs/components/Timeline.md +51 -0
  82. package/docs/components/Toast.md +59 -0
  83. package/docs/components/TrackedLink.md +62 -0
  84. package/docs/components/TrustBadges.md +44 -0
  85. package/docs/components/conversion/MobileCTA.md +363 -0
  86. package/docs/components/forms/ContactForm.md +75 -0
  87. package/docs/components/forms/FormField.md +74 -0
  88. package/docs/components/layout/Footer.md +601 -0
  89. package/docs/components/layout/Header.md +549 -0
  90. package/docs/components/layout/LanguageSelector.md +54 -0
  91. package/docs/components/layout/LanguageSwitcher.md +24 -0
  92. package/docs/components/overview.md +447 -0
  93. package/docs/components/sections/AboutSection.md +48 -0
  94. package/docs/components/sections/CTASection.md +596 -0
  95. package/docs/components/sections/CaseStudySection.md +47 -0
  96. package/docs/components/sections/ContactSection.md +599 -0
  97. package/docs/components/sections/FeatureSection.md +44 -0
  98. package/docs/components/sections/HeroSection.md +404 -0
  99. package/docs/components/sections/LogosSection.md +47 -0
  100. package/docs/components/sections/PersonalTaxesSection.md +23 -0
  101. package/docs/components/sections/RecruitingSection.md +23 -0
  102. package/docs/components/sections/SecurePortalSection.md +23 -0
  103. package/docs/components/sections/ServicePageLayout.md +52 -0
  104. package/docs/components/sections/ServicesSection.md +49 -0
  105. package/docs/components/sections/TestimonialSection.md +44 -0
  106. package/docs/components/sections/WhyChooseUsSection.md +54 -0
  107. package/docs/components/ui/Breadcrumb.md +70 -0
  108. package/docs/components/ui/Button.md +514 -0
  109. package/docs/components/ui/Card.md +501 -0
  110. package/docs/components/ui/Input.md +54 -0
  111. package/docs/components/ui/MobileLinks.md +43 -0
  112. package/docs/components/ui/Modal.md +60 -0
  113. package/docs/components/ui/Tabs.md +62 -0
  114. package/docs/components/ui/Textarea.md +52 -0
  115. package/docs/core-concepts/configuration-driven.md +552 -0
  116. package/docs/core-concepts/overview.md +351 -0
  117. package/docs/features/accessibility/README.md +73 -0
  118. package/docs/features/accessibility/aria-support.md +177 -0
  119. package/docs/features/accessibility/color-contrast.md +155 -0
  120. package/docs/features/accessibility/focus-management.md +187 -0
  121. package/docs/features/accessibility/testing.md +196 -0
  122. package/docs/features/analytics/README.md +51 -0
  123. package/docs/features/analytics/ab-testing.md +171 -0
  124. package/docs/features/analytics/conversion-tracking.md +207 -0
  125. package/docs/features/analytics/custom-events.md +219 -0
  126. package/docs/features/analytics/privacy.md +198 -0
  127. package/docs/features/analytics/setup.md +114 -0
  128. package/docs/features/analytics/tracking-events.md +224 -0
  129. package/docs/features/i18n/README.md +51 -0
  130. package/docs/features/i18n/best-practices.md +273 -0
  131. package/docs/features/i18n/configuration.md +84 -0
  132. package/docs/features/i18n/formatting.md +133 -0
  133. package/docs/features/i18n/locale-detection.md +122 -0
  134. package/docs/features/i18n/routing.md +99 -0
  135. package/docs/features/i18n/rtl-support.md +191 -0
  136. package/docs/features/i18n/translations.md +129 -0
  137. package/docs/features/internationalization.md +595 -0
  138. package/docs/features/performance/README.md +77 -0
  139. package/docs/features/performance/bundle-size.md +134 -0
  140. package/docs/features/performance/caching.md +131 -0
  141. package/docs/features/performance/code-splitting.md +121 -0
  142. package/docs/features/performance/image-optimization.md +110 -0
  143. package/docs/features/performance/lazy-loading.md +92 -0
  144. package/docs/features/performance/monitoring.md +148 -0
  145. package/docs/features/seo/README.md +51 -0
  146. package/docs/features/seo/best-practices.md +184 -0
  147. package/docs/features/seo/canonical-urls.md +182 -0
  148. package/docs/features/seo/meta-tags.md +126 -0
  149. package/docs/features/seo/open-graph.md +166 -0
  150. package/docs/features/seo/robots-txt.md +146 -0
  151. package/docs/features/seo/sitemaps.md +162 -0
  152. package/docs/features/seo/structured-data.md +166 -0
  153. package/docs/getting-started/installation.md +292 -0
  154. package/docs/getting-started/introduction.md +195 -0
  155. package/docs/getting-started/quick-start.md +460 -0
  156. package/docs/guides/analytics-setup.md +616 -0
  157. package/docs/i18n/CONFIGURATION.md +353 -0
  158. package/docs/i18n/EXAMPLES.md +402 -0
  159. package/docs/i18n/MIGRATION.md +260 -0
  160. package/docs/i18n/SEO.md +392 -0
  161. package/docs/i18n/STATIC-GENERATION-FIX.md +71 -0
  162. package/docs/migration/changelog.md +136 -0
  163. package/docs/migration/overview.md +233 -0
  164. package/docs/recipes/adding-animations.md +475 -0
  165. package/docs/recipes/forms-with-validation.md +393 -0
  166. package/package.json +152 -0
@@ -0,0 +1,529 @@
1
+ # Common Accessibility Patterns
2
+
3
+ Reusable accessibility patterns and code examples for common scenarios.
4
+
5
+ ## Skip Links
6
+
7
+ Allow keyboard users to skip repetitive navigation.
8
+
9
+ ```tsx
10
+ import { SkipLink } from '@zoyth/simple-site-framework'
11
+
12
+ export default function Layout({ children }) {
13
+ return (
14
+ <>
15
+ <SkipLink href="#main">Skip to main content</SkipLink>
16
+ <SkipLink href="#nav">Skip to navigation</SkipLink>
17
+
18
+ <header>
19
+ <nav id="nav">{/* Navigation */}</nav>
20
+ </header>
21
+
22
+ <main id="main">{children}</main>
23
+ </>
24
+ )
25
+ }
26
+ ```
27
+
28
+ **Benefits:**
29
+ - Keyboard users can bypass repeated content
30
+ - Required for WCAG 2.4.1 (Level A)
31
+ - Hidden until focused
32
+
33
+ ## Screen Reader Announcements
34
+
35
+ Notify users of dynamic content changes.
36
+
37
+ ### Using useA11y Hook
38
+ ```tsx
39
+ import { useA11y } from '@zoyth/simple-site-framework'
40
+
41
+ function FormExample() {
42
+ const { announce } = useA11y()
43
+
44
+ const handleSubmit = async (data) => {
45
+ try {
46
+ await submitForm(data)
47
+ announce('Form submitted successfully')
48
+ } catch (error) {
49
+ announce('Error submitting form. Please try again.', 'assertive')
50
+ }
51
+ }
52
+
53
+ return <form onSubmit={handleSubmit}>{/* fields */}</form>
54
+ }
55
+ ```
56
+
57
+ ### Using A11yAnnouncer Component
58
+ ```tsx
59
+ import { A11yAnnouncer } from '@zoyth/simple-site-framework'
60
+
61
+ function LoadingExample() {
62
+ const [status, setStatus] = useState('')
63
+
64
+ useEffect(() => {
65
+ loadData().then(() => setStatus('Loading complete'))
66
+ }, [])
67
+
68
+ return (
69
+ <>
70
+ <div>{/* content */}</div>
71
+ <A11yAnnouncer message={status} politeness="polite" />
72
+ </>
73
+ )
74
+ }
75
+ ```
76
+
77
+ ## Focus Management
78
+
79
+ ### Focus Trap (Modals)
80
+ Keep focus inside modal while open:
81
+
82
+ ```tsx
83
+ import { useFocusTrap } from '@zoyth/simple-site-framework'
84
+
85
+ function CustomModal({ isOpen, onClose }) {
86
+ const trapRef = useFocusTrap(isOpen)
87
+
88
+ if (!isOpen) return null
89
+
90
+ return (
91
+ <div
92
+ ref={trapRef}
93
+ role="dialog"
94
+ aria-modal="true"
95
+ aria-labelledby="modal-title"
96
+ >
97
+ <h2 id="modal-title">Confirm Action</h2>
98
+ <p>Are you sure?</p>
99
+ <Button onClick={onClose}>Cancel</Button>
100
+ <Button onClick={handleConfirm}>Confirm</Button>
101
+ </div>
102
+ )
103
+ }
104
+ ```
105
+
106
+ ### Focus Return
107
+ Return focus after closing modal:
108
+
109
+ ```tsx
110
+ import { useFocusReturn } from '@zoyth/simple-site-framework'
111
+
112
+ function Modal({ children }) {
113
+ const returnRef = useFocusReturn()
114
+
115
+ return (
116
+ <div ref={returnRef} role="dialog">
117
+ {children}
118
+ </div>
119
+ )
120
+ }
121
+ ```
122
+
123
+ ### Manual Focus Management
124
+ ```tsx
125
+ import { useRef, useEffect } from 'react'
126
+
127
+ function SearchModal({ isOpen }) {
128
+ const inputRef = useRef(null)
129
+
130
+ useEffect(() => {
131
+ if (isOpen && inputRef.current) {
132
+ inputRef.current.focus()
133
+ }
134
+ }, [isOpen])
135
+
136
+ return (
137
+ <Modal isOpen={isOpen}>
138
+ <input ref={inputRef} type="search" />
139
+ </Modal>
140
+ )
141
+ }
142
+ ```
143
+
144
+ ## Form Accessibility
145
+
146
+ ### Basic Form
147
+ ```tsx
148
+ import { FormField } from '@zoyth/simple-site-framework'
149
+
150
+ function ContactForm() {
151
+ return (
152
+ <form>
153
+ <FormField
154
+ name="name"
155
+ label="Full Name"
156
+ required
157
+ error={errors.name}
158
+ >
159
+ <input type="text" {...register('name')} />
160
+ </FormField>
161
+
162
+ <FormField
163
+ name="email"
164
+ label="Email Address"
165
+ required
166
+ hint="We'll never share your email"
167
+ error={errors.email}
168
+ >
169
+ <input type="email" {...register('email')} />
170
+ </FormField>
171
+
172
+ <Button type="submit">Submit</Button>
173
+ </form>
174
+ )
175
+ }
176
+ ```
177
+
178
+ ### Fieldset for Related Inputs
179
+ ```tsx
180
+ <fieldset>
181
+ <legend>Shipping Address</legend>
182
+
183
+ <FormField name="street" label="Street">
184
+ <input type="text" {...register('street')} />
185
+ </FormField>
186
+
187
+ <FormField name="city" label="City">
188
+ <input type="text" {...register('city')} />
189
+ </FormField>
190
+ </fieldset>
191
+ ```
192
+
193
+ ### Radio Group
194
+ ```tsx
195
+ import { Radio } from '@zoyth/simple-site-framework'
196
+
197
+ <Radio
198
+ name="plan"
199
+ label="Choose a plan"
200
+ options={[
201
+ { value: 'basic', label: 'Basic' },
202
+ { value: 'pro', label: 'Professional' },
203
+ { value: 'enterprise', label: 'Enterprise' }
204
+ ]}
205
+ value={selectedPlan}
206
+ onChange={setSelectedPlan}
207
+ />
208
+ ```
209
+
210
+ ## Loading States
211
+
212
+ ### Loading Button
213
+ ```tsx
214
+ <Button
215
+ onClick={handleSubmit}
216
+ loading={isSubmitting}
217
+ loadingText="Submitting..."
218
+ >
219
+ Submit Form
220
+ </Button>
221
+ ```
222
+ **Screen reader**: "Submitting..., button, busy"
223
+
224
+ ### Loading Content
225
+ ```tsx
226
+ function DataTable() {
227
+ if (isLoading) {
228
+ return (
229
+ <div role="status" aria-live="polite">
230
+ <LoadingSpinner />
231
+ <span className="sr-only">Loading data...</span>
232
+ </div>
233
+ )
234
+ }
235
+
236
+ return <table>{/* data */}</table>
237
+ }
238
+ ```
239
+
240
+ ## Icon-Only Buttons
241
+
242
+ Always provide accessible labels:
243
+
244
+ ```tsx
245
+ // ❌ Bad - no label
246
+ <button onClick={handleClose}>
247
+ <Icons.X />
248
+ </button>
249
+
250
+ // ✅ Good - has aria-label
251
+ <Button
252
+ icon={<Icons.X />}
253
+ iconOnly
254
+ aria-label="Close dialog"
255
+ onClick={handleClose}
256
+ />
257
+
258
+ // ✅ Also good - visual label hidden, but readable
259
+ <button onClick={handleClose}>
260
+ <Icons.X aria-hidden="true" />
261
+ <span className="sr-only">Close dialog</span>
262
+ </button>
263
+ ```
264
+
265
+ ## Data Tables
266
+
267
+ ### Basic Table
268
+ ```tsx
269
+ <table>
270
+ <caption>Sales by Quarter</caption>
271
+ <thead>
272
+ <tr>
273
+ <th scope="col">Quarter</th>
274
+ <th scope="col">Sales</th>
275
+ <th scope="col">Growth</th>
276
+ </tr>
277
+ </thead>
278
+ <tbody>
279
+ <tr>
280
+ <th scope="row">Q1 2024</th>
281
+ <td>$125,000</td>
282
+ <td>+12%</td>
283
+ </tr>
284
+ </tbody>
285
+ </table>
286
+ ```
287
+
288
+ ### Sortable Headers
289
+ ```tsx
290
+ <th scope="col">
291
+ <button
292
+ onClick={() => sortBy('name')}
293
+ aria-sort={sortColumn === 'name' ? sortDirection : 'none'}
294
+ >
295
+ Name
296
+ {sortColumn === 'name' && (
297
+ <span aria-label={sortDirection === 'asc' ? 'sorted ascending' : 'sorted descending'}>
298
+ {sortDirection === 'asc' ? '↑' : '↓'}
299
+ </span>
300
+ )}
301
+ </button>
302
+ </th>
303
+ ```
304
+
305
+ ## Accordions & Disclosure
306
+
307
+ ```tsx
308
+ import { FAQAccordion } from '@zoyth/simple-site-framework'
309
+
310
+ <FAQAccordion
311
+ items={[
312
+ {
313
+ question: 'What is your return policy?',
314
+ answer: 'We offer 30-day returns...'
315
+ },
316
+ {
317
+ question: 'Do you ship internationally?',
318
+ answer: 'Yes, we ship worldwide...'
319
+ }
320
+ ]}
321
+ locale="en"
322
+ />
323
+ ```
324
+
325
+ **Screen reader navigation:**
326
+ - Tab through questions
327
+ - Enter/Space to expand/collapse
328
+ - Arrow keys to navigate
329
+ - Content announces when expanded
330
+
331
+ ## Breadcrumbs
332
+
333
+ ```tsx
334
+ <nav aria-label="Breadcrumb">
335
+ <ol>
336
+ <li>
337
+ <a href="/">Home</a>
338
+ </li>
339
+ <li>
340
+ <a href="/products">Products</a>
341
+ </li>
342
+ <li aria-current="page">
343
+ Product Details
344
+ </li>
345
+ </ol>
346
+ </nav>
347
+ ```
348
+
349
+ ## Pagination
350
+
351
+ ```tsx
352
+ <nav aria-label="Pagination">
353
+ <Button
354
+ onClick={prevPage}
355
+ disabled={currentPage === 1}
356
+ aria-label="Previous page"
357
+ >
358
+ Previous
359
+ </Button>
360
+
361
+ <span aria-current="page">
362
+ Page {currentPage} of {totalPages}
363
+ </span>
364
+
365
+ <Button
366
+ onClick={nextPage}
367
+ disabled={currentPage === totalPages}
368
+ aria-label="Next page"
369
+ >
370
+ Next
371
+ </Button>
372
+ </nav>
373
+ ```
374
+
375
+ ## Tabs
376
+
377
+ ```tsx
378
+ import { Tabs } from '@zoyth/simple-site-framework'
379
+
380
+ <Tabs
381
+ tabs={[
382
+ { value: 'tab1', label: 'Overview', content: <Overview /> },
383
+ { value: 'tab2', label: 'Details', content: <Details /> },
384
+ { value: 'tab3', label: 'Reviews', content: <Reviews /> }
385
+ ]}
386
+ value={activeTab}
387
+ onValueChange={setActiveTab}
388
+ />
389
+ ```
390
+
391
+ **Screen reader navigation:**
392
+ - Tab to focus tab list
393
+ - Arrow keys to navigate tabs
394
+ - Enter to activate selected tab
395
+
396
+ ## Tooltips
397
+
398
+ ```tsx
399
+ import { Button } from '@zoyth/simple-site-framework'
400
+
401
+ <Button
402
+ disabled={!isValid}
403
+ disabledTooltip="Please fill all required fields"
404
+ >
405
+ Submit
406
+ </Button>
407
+ ```
408
+
409
+ **For non-disabled tooltips:**
410
+ ```tsx
411
+ <button
412
+ aria-describedby="tooltip-1"
413
+ onMouseEnter={() => setShowTooltip(true)}
414
+ onMouseLeave={() => setShowTooltip(false)}
415
+ onFocus={() => setShowTooltip(true)}
416
+ onBlur={() => setShowTooltip(false)}
417
+ >
418
+ Help
419
+ </button>
420
+ {showTooltip && (
421
+ <div id="tooltip-1" role="tooltip">
422
+ Additional information
423
+ </div>
424
+ )}
425
+ ```
426
+
427
+ ## Responsive Images
428
+
429
+ ```tsx
430
+ <img
431
+ src="/images/product.jpg"
432
+ alt="Blue ceramic vase with geometric pattern"
433
+ loading="lazy"
434
+ />
435
+
436
+ // For decorative images
437
+ <img
438
+ src="/images/decorative-pattern.svg"
439
+ alt=""
440
+ aria-hidden="true"
441
+ />
442
+ ```
443
+
444
+ ## Landmarks
445
+
446
+ Use semantic HTML5 elements:
447
+
448
+ ```tsx
449
+ <>
450
+ <header>{/* Site header */}</header>
451
+
452
+ <nav aria-label="Main navigation">
453
+ {/* Primary navigation */}
454
+ </nav>
455
+
456
+ <main>
457
+ <section aria-labelledby="products-heading">
458
+ <h2 id="products-heading">Our Products</h2>
459
+ {/* Products */}
460
+ </section>
461
+
462
+ <aside aria-label="Related articles">
463
+ {/* Sidebar content */}
464
+ </aside>
465
+ </main>
466
+
467
+ <footer>{/* Site footer */}</footer>
468
+ </>
469
+ ```
470
+
471
+ ## Color Contrast
472
+
473
+ Ensure proper contrast ratios:
474
+
475
+ ```tsx
476
+ // ❌ Bad - low contrast
477
+ <p style={{ color: '#999', background: '#fff' }}>
478
+ Light gray on white (3:1)
479
+ </p>
480
+
481
+ // ✅ Good - meets AA
482
+ <p style={{ color: '#666', background: '#fff' }}>
483
+ Dark gray on white (5.7:1)
484
+ </p>
485
+
486
+ // ✅ Better - use theme colors
487
+ <p className="text-gray-700 bg-white">
488
+ Framework colors meet AA standards
489
+ </p>
490
+ ```
491
+
492
+ ## Error Messages
493
+
494
+ ### Inline Errors
495
+ ```tsx
496
+ <FormField
497
+ name="email"
498
+ label="Email"
499
+ error={{ message: 'Please enter a valid email address' }}
500
+ >
501
+ <input
502
+ type="email"
503
+ aria-invalid={!!errors.email}
504
+ aria-describedby="email-error"
505
+ />
506
+ </FormField>
507
+ ```
508
+
509
+ ### Summary of Errors
510
+ ```tsx
511
+ {Object.keys(errors).length > 0 && (
512
+ <div role="alert" aria-live="assertive">
513
+ <h2>Please fix the following errors:</h2>
514
+ <ul>
515
+ {Object.entries(errors).map(([field, error]) => (
516
+ <li key={field}>
517
+ <a href={`#${field}`}>{error.message}</a>
518
+ </li>
519
+ ))}
520
+ </ul>
521
+ </div>
522
+ )}
523
+ ```
524
+
525
+ ## Resources
526
+
527
+ - [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/)
528
+ - [Inclusive Components](https://inclusive-components.design/)
529
+ - [A11y Project Checklist](https://www.a11yproject.com/checklist/)