@smicolon/ai-kit 0.1.0 → 0.1.1
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/.claude-plugin/CLAUDE.md +7 -0
- package/.claude-plugin/marketplace.json +373 -0
- package/package.json +4 -3
- package/packs/architect/CHANGELOG.md +17 -0
- package/packs/architect/README.md +58 -0
- package/packs/architect/agents/system-architect.md +768 -0
- package/packs/architect/commands/diagram-create.md +300 -0
- package/packs/better-auth/.claude-plugin/plugin.json +14 -0
- package/packs/better-auth/.mcp.json +14 -0
- package/packs/better-auth/CHANGELOG.md +26 -0
- package/packs/better-auth/README.md +125 -0
- package/packs/better-auth/agents/auth-architect.md +278 -0
- package/packs/better-auth/commands/auth-provider-add.md +265 -0
- package/packs/better-auth/commands/auth-setup.md +298 -0
- package/packs/better-auth/skills/auth-security/SKILL.md +425 -0
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +455 -0
- package/packs/dev-loop/.claude-plugin/plugin.json +10 -0
- package/packs/dev-loop/CHANGELOG.md +69 -0
- package/packs/dev-loop/README.md +155 -0
- package/packs/dev-loop/commands/cancel-dev.md +21 -0
- package/packs/dev-loop/commands/dev-loop.md +72 -0
- package/packs/dev-loop/commands/dev-plan.md +351 -0
- package/packs/dev-loop/hooks/hooks.json +15 -0
- package/packs/dev-loop/hooks/stop-hook.sh +178 -0
- package/packs/dev-loop/scripts/setup-dev-loop.sh +194 -0
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +249 -0
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +874 -0
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +260 -0
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +275 -0
- package/packs/django/CHANGELOG.md +39 -0
- package/packs/django/README.md +92 -0
- package/packs/django/agents/django-architect.md +182 -0
- package/packs/django/agents/django-builder.md +250 -0
- package/packs/django/agents/django-feature-based.md +420 -0
- package/packs/django/agents/django-reviewer.md +253 -0
- package/packs/django/agents/django-tester.md +230 -0
- package/packs/django/commands/api-endpoint.md +285 -0
- package/packs/django/commands/model-create.md +178 -0
- package/packs/django/commands/test-generate.md +325 -0
- package/packs/django/rules/migrations.md +138 -0
- package/packs/django/rules/models.md +167 -0
- package/packs/django/rules/serializers.md +126 -0
- package/packs/django/rules/services.md +131 -0
- package/packs/django/rules/tests.md +140 -0
- package/packs/django/rules/views.md +102 -0
- package/packs/django/skills/import-convention-enforcer/SKILL.md +226 -0
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +343 -0
- package/packs/django/skills/migration-safety-checker/SKILL.md +375 -0
- package/packs/django/skills/model-entity-validator/SKILL.md +298 -0
- package/packs/django/skills/performance-optimizer/SKILL.md +447 -0
- package/packs/django/skills/red-phase-verifier/SKILL.md +180 -0
- package/packs/django/skills/security-first-validator/SKILL.md +435 -0
- package/packs/django/skills/test-coverage-advisor/SKILL.md +394 -0
- package/packs/django/skills/test-validity-checker/SKILL.md +194 -0
- package/packs/failure-log/.claude-plugin/plugin.json +14 -0
- package/packs/failure-log/CHANGELOG.md +20 -0
- package/packs/failure-log/README.md +168 -0
- package/packs/failure-log/commands/failure-add.md +106 -0
- package/packs/failure-log/commands/failure-list.md +89 -0
- package/packs/failure-log/hooks/hooks.json +16 -0
- package/packs/failure-log/hooks/scripts/inject-failures.sh +64 -0
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +164 -0
- package/packs/flutter/.claude-plugin/plugin.json +10 -0
- package/packs/flutter/CHANGELOG.md +19 -0
- package/packs/flutter/README.md +170 -0
- package/packs/flutter/agents/flutter-architect.md +166 -0
- package/packs/flutter/agents/flutter-builder.md +303 -0
- package/packs/flutter/agents/release-manager.md +355 -0
- package/packs/flutter/commands/fastlane-setup.md +188 -0
- package/packs/flutter/commands/flutter-build.md +90 -0
- package/packs/flutter/commands/flutter-deploy.md +133 -0
- package/packs/flutter/commands/flutter-test.md +117 -0
- package/packs/flutter/commands/signing-setup.md +209 -0
- package/packs/flutter/hooks/hooks.json +17 -0
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +193 -0
- package/packs/flutter/skills/flutter-architecture/SKILL.md +127 -0
- package/packs/flutter/skills/store-publishing/SKILL.md +163 -0
- package/packs/hono/.claude-plugin/plugin.json +19 -0
- package/packs/hono/CHANGELOG.md +19 -0
- package/packs/hono/README.md +143 -0
- package/packs/hono/agents/hono-architect.md +240 -0
- package/packs/hono/agents/hono-builder.md +285 -0
- package/packs/hono/agents/hono-reviewer.md +279 -0
- package/packs/hono/agents/hono-tester.md +346 -0
- package/packs/hono/commands/middleware-create.md +223 -0
- package/packs/hono/commands/project-init.md +306 -0
- package/packs/hono/commands/route-create.md +153 -0
- package/packs/hono/commands/rpc-client.md +263 -0
- package/packs/hono/hooks/hooks.json +4 -0
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +408 -0
- package/packs/hono/skills/hono-patterns/SKILL.md +309 -0
- package/packs/hono/skills/rpc-typesafe/SKILL.md +388 -0
- package/packs/hono/skills/zod-validation/SKILL.md +332 -0
- package/packs/nestjs/CHANGELOG.md +29 -0
- package/packs/nestjs/README.md +75 -0
- package/packs/nestjs/agents/nestjs-architect.md +402 -0
- package/packs/nestjs/agents/nestjs-builder.md +301 -0
- package/packs/nestjs/agents/nestjs-tester.md +437 -0
- package/packs/nestjs/commands/module-create.md +369 -0
- package/packs/nestjs/rules/controllers.md +92 -0
- package/packs/nestjs/rules/dto.md +124 -0
- package/packs/nestjs/rules/entities.md +102 -0
- package/packs/nestjs/rules/services.md +106 -0
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +389 -0
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +365 -0
- package/packs/nextjs/CHANGELOG.md +36 -0
- package/packs/nextjs/README.md +76 -0
- package/packs/nextjs/agents/frontend-tester.md +680 -0
- package/packs/nextjs/agents/frontend-visual.md +820 -0
- package/packs/nextjs/agents/nextjs-architect.md +331 -0
- package/packs/nextjs/agents/nextjs-modular.md +433 -0
- package/packs/nextjs/commands/component-create.md +398 -0
- package/packs/nextjs/rules/api-routes.md +129 -0
- package/packs/nextjs/rules/components.md +106 -0
- package/packs/nextjs/rules/hooks.md +132 -0
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +445 -0
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +399 -0
- package/packs/nextjs/skills/react-form-validator/SKILL.md +569 -0
- package/packs/nuxtjs/CHANGELOG.md +30 -0
- package/packs/nuxtjs/README.md +56 -0
- package/packs/nuxtjs/agents/frontend-tester.md +680 -0
- package/packs/nuxtjs/agents/frontend-visual.md +820 -0
- package/packs/nuxtjs/agents/nuxtjs-architect.md +537 -0
- package/packs/nuxtjs/commands/component-create.md +223 -0
- package/packs/nuxtjs/rules/components.md +101 -0
- package/packs/nuxtjs/rules/composables.md +118 -0
- package/packs/nuxtjs/rules/server-routes.md +127 -0
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +183 -0
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +196 -0
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +190 -0
- package/packs/onboard/CHANGELOG.md +22 -0
- package/packs/onboard/README.md +103 -0
- package/packs/onboard/agents/onboard-guide.md +118 -0
- package/packs/onboard/commands/onboard.md +313 -0
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +98 -0
- package/packs/tanstack-router/.claude-plugin/plugin.json +14 -0
- package/packs/tanstack-router/CHANGELOG.md +30 -0
- package/packs/tanstack-router/README.md +113 -0
- package/packs/tanstack-router/agents/tanstack-architect.md +173 -0
- package/packs/tanstack-router/agents/tanstack-builder.md +360 -0
- package/packs/tanstack-router/agents/tanstack-tester.md +454 -0
- package/packs/tanstack-router/commands/form-create.md +313 -0
- package/packs/tanstack-router/commands/query-create.md +263 -0
- package/packs/tanstack-router/commands/route-create.md +190 -0
- package/packs/tanstack-router/commands/table-create.md +413 -0
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +370 -0
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +346 -0
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +415 -0
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +425 -0
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +341 -0
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +359 -0
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +285 -0
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +351 -0
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +531 -0
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +428 -0
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +490 -0
- package/packs/worktree/.claude-plugin/plugin.json +19 -0
- package/packs/worktree/CHANGELOG.md +24 -0
- package/packs/worktree/README.md +110 -0
- package/packs/worktree/commands/wt.md +73 -0
- package/packs/worktree/scripts/wt.sh +396 -0
- package/packs/worktree/skills/worktree-manager/SKILL.md +68 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/hooks/**/*.ts"
|
|
4
|
+
- "**/hooks/**/*.tsx"
|
|
5
|
+
- "**/use*.ts"
|
|
6
|
+
- "**/use*.tsx"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Next.js Custom Hooks Standards
|
|
10
|
+
|
|
11
|
+
## Structure
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
15
|
+
|
|
16
|
+
interface UseUserOptions {
|
|
17
|
+
enabled?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface UseUserReturn {
|
|
21
|
+
user: User | null
|
|
22
|
+
isLoading: boolean
|
|
23
|
+
error: Error | null
|
|
24
|
+
refetch: () => Promise<void>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useUser(userId: string, options: UseUserOptions = {}): UseUserReturn {
|
|
28
|
+
const { enabled = true } = options
|
|
29
|
+
const [user, setUser] = useState<User | null>(null)
|
|
30
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
31
|
+
const [error, setError] = useState<Error | null>(null)
|
|
32
|
+
|
|
33
|
+
const fetchUser = useCallback(async () => {
|
|
34
|
+
if (!enabled) return
|
|
35
|
+
setIsLoading(true)
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(`/api/users/${userId}`)
|
|
38
|
+
if (!response.ok) throw new Error('Failed to fetch user')
|
|
39
|
+
const data = await response.json()
|
|
40
|
+
setUser(data)
|
|
41
|
+
setError(null)
|
|
42
|
+
} catch (e) {
|
|
43
|
+
setError(e instanceof Error ? e : new Error('Unknown error'))
|
|
44
|
+
} finally {
|
|
45
|
+
setIsLoading(false)
|
|
46
|
+
}
|
|
47
|
+
}, [userId, enabled])
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
fetchUser()
|
|
51
|
+
}, [fetchUser])
|
|
52
|
+
|
|
53
|
+
return { user, isLoading, error, refetch: fetchUser }
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
- TypeScript with proper types
|
|
60
|
+
- Return type interface defined
|
|
61
|
+
- Memoized callbacks with useCallback
|
|
62
|
+
- Proper dependency arrays
|
|
63
|
+
- Error state handling
|
|
64
|
+
- Loading state handling
|
|
65
|
+
|
|
66
|
+
## Naming Convention
|
|
67
|
+
|
|
68
|
+
- Prefix with `use`
|
|
69
|
+
- Descriptive name: `useUser`, `useAuth`, `useLocalStorage`
|
|
70
|
+
- Options parameter for configuration
|
|
71
|
+
- Return object with named properties
|
|
72
|
+
|
|
73
|
+
## TanStack Query Pattern
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
77
|
+
|
|
78
|
+
export function useUser(userId: string) {
|
|
79
|
+
return useQuery({
|
|
80
|
+
queryKey: ['user', userId],
|
|
81
|
+
queryFn: () => fetchUser(userId),
|
|
82
|
+
enabled: !!userId,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function useUpdateUser() {
|
|
87
|
+
const queryClient = useQueryClient()
|
|
88
|
+
|
|
89
|
+
return useMutation({
|
|
90
|
+
mutationFn: updateUser,
|
|
91
|
+
onSuccess: (data) => {
|
|
92
|
+
queryClient.invalidateQueries({ queryKey: ['user', data.id] })
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Common Hooks
|
|
99
|
+
|
|
100
|
+
### useLocalStorage
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
104
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
105
|
+
if (typeof window === 'undefined') return initialValue
|
|
106
|
+
try {
|
|
107
|
+
const item = window.localStorage.getItem(key)
|
|
108
|
+
return item ? JSON.parse(item) : initialValue
|
|
109
|
+
} catch {
|
|
110
|
+
return initialValue
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const setValue = useCallback((value: T | ((val: T) => T)) => {
|
|
115
|
+
setStoredValue(prev => {
|
|
116
|
+
const newValue = value instanceof Function ? value(prev) : value
|
|
117
|
+
window.localStorage.setItem(key, JSON.stringify(newValue))
|
|
118
|
+
return newValue
|
|
119
|
+
})
|
|
120
|
+
}, [key])
|
|
121
|
+
|
|
122
|
+
return [storedValue, setValue] as const
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Forbidden Patterns
|
|
127
|
+
|
|
128
|
+
- Missing dependency arrays
|
|
129
|
+
- Non-memoized callbacks
|
|
130
|
+
- Any type
|
|
131
|
+
- Missing error handling
|
|
132
|
+
- Side effects outside useEffect
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-accessibility-validator
|
|
3
|
+
description: Automatically validate React/Next.js components meet WCAG 2.1 AA standards. Use when creating components, forms, buttons, modals, navigation, or any interactive UI elements.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Accessibility Validator
|
|
7
|
+
|
|
8
|
+
Auto-enforces WCAG 2.1 AA accessibility standards for all React/Next.js components.
|
|
9
|
+
|
|
10
|
+
## Activation Triggers
|
|
11
|
+
|
|
12
|
+
This skill activates when:
|
|
13
|
+
- Creating React/Next.js components
|
|
14
|
+
- Building forms, buttons, modals, navigation
|
|
15
|
+
- Adding interactive elements
|
|
16
|
+
- Mentioning "component", "UI", "form", "button"
|
|
17
|
+
- Creating pages or layouts
|
|
18
|
+
- Working on user-facing features
|
|
19
|
+
|
|
20
|
+
## WCAG 2.1 AA Requirements (MANDATORY)
|
|
21
|
+
|
|
22
|
+
All components MUST meet:
|
|
23
|
+
- ✅ **Keyboard navigation** - All interactive elements focusable and usable via keyboard
|
|
24
|
+
- ✅ **Semantic HTML** - Proper HTML elements and ARIA attributes
|
|
25
|
+
- ✅ **Color contrast** - 4.5:1 for normal text, 3:1 for large text
|
|
26
|
+
- ✅ **Screen reader support** - All content accessible to assistive technologies
|
|
27
|
+
- ✅ **Focus management** - Visible focus indicators, logical tab order
|
|
28
|
+
- ✅ **Form accessibility** - Labels, error announcements, help text
|
|
29
|
+
|
|
30
|
+
## Auto-Validation Process
|
|
31
|
+
|
|
32
|
+
### Step 1: Detect Component Creation
|
|
33
|
+
|
|
34
|
+
When detecting component being written:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// Component without accessibility
|
|
38
|
+
function LoginButton({ onClick }) {
|
|
39
|
+
return <div onClick={onClick}>Login</div> // ❌ Multiple issues!
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Step 2: Run Accessibility Checklist
|
|
44
|
+
|
|
45
|
+
Verify:
|
|
46
|
+
1. ❌ Using div instead of button (semantic HTML)
|
|
47
|
+
2. ❌ No keyboard support (only onClick, no onKeyDown)
|
|
48
|
+
3. ❌ Not focusable (div not in tab order)
|
|
49
|
+
4. ❌ No ARIA attributes
|
|
50
|
+
5. ❌ No focus styles
|
|
51
|
+
|
|
52
|
+
### Step 3: Auto-Fix Violations
|
|
53
|
+
|
|
54
|
+
**Before (Inaccessible):**
|
|
55
|
+
```tsx
|
|
56
|
+
function LoginButton({ onClick }) {
|
|
57
|
+
return <div onClick={onClick}>Login</div>
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**After (Accessible):**
|
|
62
|
+
```tsx
|
|
63
|
+
function LoginButton({ onClick }) {
|
|
64
|
+
return (
|
|
65
|
+
<button
|
|
66
|
+
onClick={onClick}
|
|
67
|
+
type="button"
|
|
68
|
+
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
69
|
+
>
|
|
70
|
+
Login
|
|
71
|
+
</button>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Step 4: Explain Fixes
|
|
77
|
+
|
|
78
|
+
> **Accessibility Violations Fixed**
|
|
79
|
+
>
|
|
80
|
+
> **Changes:**
|
|
81
|
+
> 1. ✅ Changed `<div>` to `<button>` (semantic HTML)
|
|
82
|
+
> 2. ✅ Added `type="button"` (prevents form submission)
|
|
83
|
+
> 3. ✅ Button is now keyboard accessible (Enter/Space keys work)
|
|
84
|
+
> 4. ✅ Automatically focusable (in tab order)
|
|
85
|
+
> 5. ✅ Added visible focus ring (focus:ring-2)
|
|
86
|
+
>
|
|
87
|
+
> **Why:**
|
|
88
|
+
> - Buttons are natively accessible to keyboard and screen readers
|
|
89
|
+
> - Focus ring shows keyboard users where they are
|
|
90
|
+
> - Proper semantics help assistive technologies
|
|
91
|
+
|
|
92
|
+
## Common Accessibility Patterns
|
|
93
|
+
|
|
94
|
+
### Pattern 1: Buttons vs Links
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// ❌ WRONG - div as button
|
|
98
|
+
<div onClick={handleClick}>Click me</div>
|
|
99
|
+
|
|
100
|
+
// ❌ WRONG - link as button
|
|
101
|
+
<a href="#" onClick={handleClick}>Click me</a>
|
|
102
|
+
|
|
103
|
+
// ✅ CORRECT - button for actions
|
|
104
|
+
<button onClick={handleClick} type="button">
|
|
105
|
+
Click me
|
|
106
|
+
</button>
|
|
107
|
+
|
|
108
|
+
// ✅ CORRECT - link for navigation
|
|
109
|
+
<Link href="/page">Go to page</Link>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Pattern 2: Form Labels
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
// ❌ WRONG - no label
|
|
116
|
+
<input type="email" placeholder="Email" />
|
|
117
|
+
|
|
118
|
+
// ❌ WRONG - placeholder as label (insufficient)
|
|
119
|
+
<input type="email" placeholder="Enter your email" />
|
|
120
|
+
|
|
121
|
+
// ✅ CORRECT - explicit label
|
|
122
|
+
<label htmlFor="email" className="block text-sm font-medium">
|
|
123
|
+
Email Address
|
|
124
|
+
</label>
|
|
125
|
+
<input
|
|
126
|
+
id="email"
|
|
127
|
+
type="email"
|
|
128
|
+
className="mt-1 block w-full rounded-md border-gray-300"
|
|
129
|
+
aria-required="true"
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
// ✅ CORRECT - with error handling
|
|
133
|
+
<label htmlFor="email" className="block text-sm font-medium">
|
|
134
|
+
Email Address
|
|
135
|
+
</label>
|
|
136
|
+
<input
|
|
137
|
+
id="email"
|
|
138
|
+
type="email"
|
|
139
|
+
aria-invalid={!!errors.email}
|
|
140
|
+
aria-describedby={errors.email ? "email-error" : undefined}
|
|
141
|
+
/>
|
|
142
|
+
{errors.email && (
|
|
143
|
+
<p id="email-error" className="mt-1 text-sm text-red-600" role="alert">
|
|
144
|
+
{errors.email.message}
|
|
145
|
+
</p>
|
|
146
|
+
)}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Pattern 3: Modal Dialogs
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
// ✅ CORRECT - Accessible modal
|
|
153
|
+
import { Dialog } from '@headlessui/react'
|
|
154
|
+
|
|
155
|
+
function Modal({ isOpen, onClose, title, children }) {
|
|
156
|
+
return (
|
|
157
|
+
<Dialog
|
|
158
|
+
open={isOpen}
|
|
159
|
+
onClose={onClose}
|
|
160
|
+
className="relative z-50"
|
|
161
|
+
>
|
|
162
|
+
{/* Backdrop */}
|
|
163
|
+
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
|
164
|
+
|
|
165
|
+
{/* Modal */}
|
|
166
|
+
<div className="fixed inset-0 flex items-center justify-center p-4">
|
|
167
|
+
<Dialog.Panel className="bg-white rounded-lg p-6 max-w-md">
|
|
168
|
+
<Dialog.Title className="text-lg font-semibold">
|
|
169
|
+
{title}
|
|
170
|
+
</Dialog.Title>
|
|
171
|
+
|
|
172
|
+
<div className="mt-4">
|
|
173
|
+
{children}
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<button
|
|
177
|
+
onClick={onClose}
|
|
178
|
+
className="mt-4 px-4 py-2 bg-gray-200 rounded"
|
|
179
|
+
aria-label="Close dialog"
|
|
180
|
+
>
|
|
181
|
+
Close
|
|
182
|
+
</button>
|
|
183
|
+
</Dialog.Panel>
|
|
184
|
+
</div>
|
|
185
|
+
</Dialog>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Pattern 4: Icon Buttons
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
// ❌ WRONG - no accessible name
|
|
194
|
+
<button>
|
|
195
|
+
<XIcon />
|
|
196
|
+
</button>
|
|
197
|
+
|
|
198
|
+
// ✅ CORRECT - with aria-label
|
|
199
|
+
<button aria-label="Close" type="button">
|
|
200
|
+
<XIcon className="h-5 w-5" aria-hidden="true" />
|
|
201
|
+
</button>
|
|
202
|
+
|
|
203
|
+
// ✅ CORRECT - with visually hidden text
|
|
204
|
+
<button type="button" className="relative">
|
|
205
|
+
<span className="sr-only">Close</span>
|
|
206
|
+
<XIcon className="h-5 w-5" aria-hidden="true" />
|
|
207
|
+
</button>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Pattern 5: Loading States
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
// ❌ WRONG - no screen reader announcement
|
|
214
|
+
{isLoading && <Spinner />}
|
|
215
|
+
|
|
216
|
+
// ✅ CORRECT - with live region
|
|
217
|
+
{isLoading && (
|
|
218
|
+
<div role="status" aria-live="polite">
|
|
219
|
+
<Spinner />
|
|
220
|
+
<span className="sr-only">Loading...</span>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Pattern 6: Images
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
// ❌ WRONG - no alt text
|
|
229
|
+
<img src="/logo.png" />
|
|
230
|
+
|
|
231
|
+
// ✅ CORRECT - with alt text
|
|
232
|
+
<img src="/logo.png" alt="Company Logo" />
|
|
233
|
+
|
|
234
|
+
// ✅ CORRECT - decorative image
|
|
235
|
+
<img src="/decoration.png" alt="" aria-hidden="true" />
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Keyboard Navigation Checklist
|
|
239
|
+
|
|
240
|
+
For every component, verify:
|
|
241
|
+
|
|
242
|
+
- ✅ All interactive elements are focusable (button, a, input, etc.)
|
|
243
|
+
- ✅ Tab order is logical (follows visual flow)
|
|
244
|
+
- ✅ Focus is visible (outline, ring, or custom indicator)
|
|
245
|
+
- ✅ No keyboard traps (can escape from all UI)
|
|
246
|
+
- ✅ Enter/Space work on buttons
|
|
247
|
+
- ✅ Escape closes modals/dropdowns
|
|
248
|
+
- ✅ Arrow keys work in lists/menus
|
|
249
|
+
|
|
250
|
+
## Color Contrast Requirements
|
|
251
|
+
|
|
252
|
+
Check all text meets minimum contrast:
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
// ❌ WRONG - insufficient contrast (2.5:1)
|
|
256
|
+
<p className="text-gray-400 bg-white">Low contrast text</p>
|
|
257
|
+
|
|
258
|
+
// ✅ CORRECT - good contrast (7:1)
|
|
259
|
+
<p className="text-gray-900 bg-white">High contrast text</p>
|
|
260
|
+
|
|
261
|
+
// ✅ CORRECT - large text can be 3:1
|
|
262
|
+
<h1 className="text-2xl text-gray-600 bg-white">Large heading</h1>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Minimum Ratios:**
|
|
266
|
+
- Normal text (< 18pt): 4.5:1
|
|
267
|
+
- Large text (≥ 18pt or bold ≥ 14pt): 3:1
|
|
268
|
+
- UI components: 3:1
|
|
269
|
+
|
|
270
|
+
## Screen Reader Support
|
|
271
|
+
|
|
272
|
+
### Landmark Regions
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
// ✅ CORRECT - proper landmarks
|
|
276
|
+
<header role="banner">
|
|
277
|
+
<nav role="navigation" aria-label="Main navigation">
|
|
278
|
+
{/* nav items */}
|
|
279
|
+
</nav>
|
|
280
|
+
</header>
|
|
281
|
+
|
|
282
|
+
<main role="main">
|
|
283
|
+
{/* main content */}
|
|
284
|
+
</main>
|
|
285
|
+
|
|
286
|
+
<aside role="complementary" aria-label="Related content">
|
|
287
|
+
{/* sidebar */}
|
|
288
|
+
</aside>
|
|
289
|
+
|
|
290
|
+
<footer role="contentinfo">
|
|
291
|
+
{/* footer */}
|
|
292
|
+
</footer>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### ARIA Labels
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
// ✅ CORRECT - descriptive labels
|
|
299
|
+
<button aria-label="Add item to cart">
|
|
300
|
+
<PlusIcon aria-hidden="true" />
|
|
301
|
+
</button>
|
|
302
|
+
|
|
303
|
+
<nav aria-label="Breadcrumb">
|
|
304
|
+
<ol>
|
|
305
|
+
<li><a href="/">Home</a></li>
|
|
306
|
+
<li aria-current="page">Products</li>
|
|
307
|
+
</ol>
|
|
308
|
+
</nav>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Live Regions
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
// ✅ CORRECT - announce changes
|
|
315
|
+
<div role="alert" aria-live="assertive">
|
|
316
|
+
Error: Please fill in all required fields
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<div role="status" aria-live="polite">
|
|
320
|
+
5 items in cart
|
|
321
|
+
</div>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Focus Management
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
'use client'
|
|
328
|
+
import { useEffect, useRef } from 'react'
|
|
329
|
+
|
|
330
|
+
function Modal({ isOpen, title }) {
|
|
331
|
+
const titleRef = useRef<HTMLHeadingElement>(null)
|
|
332
|
+
|
|
333
|
+
// Focus title when modal opens
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (isOpen && titleRef.current) {
|
|
336
|
+
titleRef.current.focus()
|
|
337
|
+
}
|
|
338
|
+
}, [isOpen])
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<div role="dialog" aria-modal="true">
|
|
342
|
+
<h2 ref={titleRef} tabIndex={-1} className="outline-none">
|
|
343
|
+
{title}
|
|
344
|
+
</h2>
|
|
345
|
+
{/* content */}
|
|
346
|
+
</div>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Testing Recommendations
|
|
352
|
+
|
|
353
|
+
Suggest automated tests:
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
import { render } from '@testing-library/react'
|
|
357
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
358
|
+
|
|
359
|
+
expect.extend(toHaveNoViolations)
|
|
360
|
+
|
|
361
|
+
test('LoginButton has no accessibility violations', async () => {
|
|
362
|
+
const { container } = render(<LoginButton onClick={() => {}} />)
|
|
363
|
+
const results = await axe(container)
|
|
364
|
+
expect(results).toHaveNoViolations()
|
|
365
|
+
})
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Common Violations Checked
|
|
369
|
+
|
|
370
|
+
### Missing Labels
|
|
371
|
+
- Form inputs without labels
|
|
372
|
+
- Icon buttons without aria-label
|
|
373
|
+
- Images without alt text
|
|
374
|
+
|
|
375
|
+
### Poor Semantics
|
|
376
|
+
- Divs instead of buttons
|
|
377
|
+
- Links instead of buttons
|
|
378
|
+
- Missing heading hierarchy
|
|
379
|
+
|
|
380
|
+
### Keyboard Issues
|
|
381
|
+
- Elements not in tab order
|
|
382
|
+
- Missing focus indicators
|
|
383
|
+
- Keyboard traps
|
|
384
|
+
|
|
385
|
+
### Screen Reader Issues
|
|
386
|
+
- Missing ARIA labels
|
|
387
|
+
- Incorrect ARIA roles
|
|
388
|
+
- No live region announcements
|
|
389
|
+
|
|
390
|
+
### Color Issues
|
|
391
|
+
- Insufficient contrast
|
|
392
|
+
- Color-only information
|
|
393
|
+
- Missing text alternatives
|
|
394
|
+
|
|
395
|
+
## Integration with Tailwind
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
// Utility class for screen reader only text
|
|
399
|
+
<span className="sr-only">Accessible description</span>
|
|
400
|
+
|
|
401
|
+
// Focus ring utilities
|
|
402
|
+
<button className="focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
403
|
+
Click me
|
|
404
|
+
</button>
|
|
405
|
+
|
|
406
|
+
// High contrast text
|
|
407
|
+
<p className="text-gray-900 dark:text-gray-100">
|
|
408
|
+
Good contrast in both modes
|
|
409
|
+
</p>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Success Criteria
|
|
413
|
+
|
|
414
|
+
✅ All interactive elements keyboard accessible
|
|
415
|
+
✅ All images have alt text
|
|
416
|
+
✅ All forms have labels
|
|
417
|
+
✅ Color contrast ≥ 4.5:1 (normal text)
|
|
418
|
+
✅ Proper semantic HTML used
|
|
419
|
+
✅ ARIA attributes where needed
|
|
420
|
+
✅ Focus indicators visible
|
|
421
|
+
✅ Screen reader tested
|
|
422
|
+
✅ Automated axe tests pass
|
|
423
|
+
|
|
424
|
+
## Behavior
|
|
425
|
+
|
|
426
|
+
**Proactive enforcement:**
|
|
427
|
+
- Check accessibility without being asked
|
|
428
|
+
- Fix violations immediately
|
|
429
|
+
- Add ARIA attributes automatically
|
|
430
|
+
- Explain WCAG criteria for each fix
|
|
431
|
+
- Suggest automated tests
|
|
432
|
+
|
|
433
|
+
**Never:**
|
|
434
|
+
- Require explicit "check accessibility" request
|
|
435
|
+
- Wait for accessibility audit
|
|
436
|
+
- Just warn without fixing
|
|
437
|
+
|
|
438
|
+
**Block completion if:**
|
|
439
|
+
- Buttons are divs
|
|
440
|
+
- Forms lack labels
|
|
441
|
+
- Color contrast fails
|
|
442
|
+
- Icon buttons lack accessible names
|
|
443
|
+
- Images missing alt text
|
|
444
|
+
|
|
445
|
+
This ensures every component is accessible from day one.
|