agentic-team-templates 0.3.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/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Web Backend Development Guide
|
|
2
|
+
|
|
3
|
+
Comprehensive guidelines for building robust backend APIs and services.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This guide applies to:
|
|
10
|
+
- RESTful APIs
|
|
11
|
+
- GraphQL APIs
|
|
12
|
+
- Microservices
|
|
13
|
+
- Backend-for-frontend (BFF) services
|
|
14
|
+
|
|
15
|
+
### Key Principles
|
|
16
|
+
|
|
17
|
+
1. **Security First** - Every input is hostile until validated
|
|
18
|
+
2. **Reliability** - Handle errors gracefully, fail fast
|
|
19
|
+
3. **Observability** - Log meaningfully, track metrics
|
|
20
|
+
4. **Scalability** - Design for horizontal scaling
|
|
21
|
+
|
|
22
|
+
### Project Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
src/
|
|
26
|
+
├── routes/ # Route handlers/controllers
|
|
27
|
+
├── services/ # Business logic
|
|
28
|
+
├── repositories/ # Data access layer
|
|
29
|
+
├── middleware/ # Request/response middleware
|
|
30
|
+
├── lib/ # Shared utilities
|
|
31
|
+
├── types/ # TypeScript definitions
|
|
32
|
+
├── validation/ # Input validation schemas
|
|
33
|
+
└── config/ # Configuration management
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## API Design
|
|
39
|
+
|
|
40
|
+
### RESTful Conventions
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
GET /users # List users
|
|
44
|
+
POST /users # Create user
|
|
45
|
+
GET /users/:id # Get user
|
|
46
|
+
PUT /users/:id # Update user
|
|
47
|
+
DELETE /users/:id # Delete user
|
|
48
|
+
GET /users/:id/posts # Get user's posts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### HTTP Status Codes
|
|
52
|
+
|
|
53
|
+
**Success**: 200 OK, 201 Created, 204 No Content
|
|
54
|
+
**Client Errors**: 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 422 Validation Error, 429 Rate Limited
|
|
55
|
+
**Server Errors**: 500 Internal Error, 502 Bad Gateway, 503 Service Unavailable
|
|
56
|
+
|
|
57
|
+
### Response Format
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
// Success
|
|
61
|
+
{ "data": { "id": "123", "name": "John" } }
|
|
62
|
+
|
|
63
|
+
// Error
|
|
64
|
+
{
|
|
65
|
+
"error": {
|
|
66
|
+
"code": "VALIDATION_ERROR",
|
|
67
|
+
"message": "Validation failed",
|
|
68
|
+
"details": [{ "field": "email", "message": "Invalid format" }]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Collection
|
|
73
|
+
{
|
|
74
|
+
"data": [...],
|
|
75
|
+
"pagination": { "page": 1, "limit": 20, "total": 150 }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Query Parameters
|
|
80
|
+
|
|
81
|
+
- **Filtering**: `?role=admin&status=active`
|
|
82
|
+
- **Sorting**: `?sort=-createdAt` (prefix `-` for descending)
|
|
83
|
+
- **Pagination**: `?page=2&limit=20` or `?cursor=xyz&limit=20`
|
|
84
|
+
- **Fields**: `?fields=id,name,email`
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Database Patterns
|
|
89
|
+
|
|
90
|
+
### Data Access Layer
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// repository/userRepository.ts
|
|
94
|
+
export const userRepository = {
|
|
95
|
+
async findById(id: string): Promise<User | null> {
|
|
96
|
+
return db.user.findUnique({ where: { id } });
|
|
97
|
+
},
|
|
98
|
+
async create(data: CreateUserInput): Promise<User> {
|
|
99
|
+
return db.user.create({ data });
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Transactions
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
await db.$transaction(async (tx) => {
|
|
108
|
+
const order = await tx.order.create({ data: orderData });
|
|
109
|
+
await tx.inventory.update({ ... });
|
|
110
|
+
await tx.payment.create({ ... });
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Avoid N+1 Queries
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// Bad: N+1
|
|
118
|
+
const posts = await db.post.findMany();
|
|
119
|
+
for (const post of posts) {
|
|
120
|
+
post.author = await db.user.findUnique({ where: { id: post.authorId } });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Good: Include
|
|
124
|
+
const posts = await db.post.findMany({ include: { author: true } });
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Use Indexes
|
|
128
|
+
|
|
129
|
+
```sql
|
|
130
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
131
|
+
CREATE INDEX idx_posts_user_created ON posts(user_id, created_at);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Authentication & Authorization
|
|
137
|
+
|
|
138
|
+
### JWT Authentication
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const generateToken = (user: User): string => {
|
|
142
|
+
return jwt.sign(
|
|
143
|
+
{ sub: user.id, email: user.email, role: user.role },
|
|
144
|
+
process.env.JWT_SECRET,
|
|
145
|
+
{ expiresIn: '15m' }
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Authorization Middleware
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const requirePermission = (permission: string) => {
|
|
154
|
+
return (req, res, next) => {
|
|
155
|
+
const userPermissions = permissions[req.user.role] || [];
|
|
156
|
+
if (!userPermissions.includes(permission)) {
|
|
157
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
158
|
+
}
|
|
159
|
+
next();
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Password Security
|
|
165
|
+
|
|
166
|
+
- Hash with bcrypt (12+ rounds)
|
|
167
|
+
- Enforce minimum requirements
|
|
168
|
+
- Rate limit login attempts
|
|
169
|
+
|
|
170
|
+
### Security Best Practices
|
|
171
|
+
|
|
172
|
+
- Short-lived access tokens + refresh tokens
|
|
173
|
+
- HTTP-only, Secure, SameSite cookies
|
|
174
|
+
- CSRF tokens for session-based auth
|
|
175
|
+
- Never log credentials
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Error Handling
|
|
180
|
+
|
|
181
|
+
### Custom Error Classes
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
class AppError extends Error {
|
|
185
|
+
constructor(
|
|
186
|
+
public statusCode: number,
|
|
187
|
+
public code: string,
|
|
188
|
+
message: string,
|
|
189
|
+
public details?: unknown
|
|
190
|
+
) {
|
|
191
|
+
super(message);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
class ValidationError extends AppError {
|
|
196
|
+
constructor(message: string, details?: ValidationDetail[]) {
|
|
197
|
+
super(422, 'VALIDATION_ERROR', message, details);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
class NotFoundError extends AppError {
|
|
202
|
+
constructor(resource: string, id: string) {
|
|
203
|
+
super(404, 'NOT_FOUND', `${resource} with id ${id} not found`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Global Error Handler
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
212
|
+
logger.error({ error: err.message, stack: err.stack, requestId: req.id });
|
|
213
|
+
|
|
214
|
+
if (err instanceof AppError) {
|
|
215
|
+
return res.status(err.statusCode).json({
|
|
216
|
+
error: { code: err.code, message: err.message, details: err.details },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
res.status(500).json({
|
|
221
|
+
error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' },
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Result Types
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
|
230
|
+
|
|
231
|
+
const findUser = async (id: string): Promise<Result<User, NotFoundError>> => {
|
|
232
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
233
|
+
return user ? { ok: true, value: user } : { ok: false, error: new NotFoundError('User', id) };
|
|
234
|
+
};
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Security
|
|
240
|
+
|
|
241
|
+
### Input Validation
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { z } from 'zod';
|
|
245
|
+
|
|
246
|
+
const CreateUserSchema = z.object({
|
|
247
|
+
email: z.string().email().max(255),
|
|
248
|
+
name: z.string().min(1).max(100),
|
|
249
|
+
role: z.enum(['user', 'admin']).default('user'),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const result = CreateUserSchema.safeParse(req.body);
|
|
253
|
+
if (!result.success) {
|
|
254
|
+
return res.status(422).json({ error: { details: result.error.errors } });
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Injection Prevention
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
// SQL: Always use parameterized queries
|
|
262
|
+
const users = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
|
|
263
|
+
|
|
264
|
+
// Command: Use execFile with array arguments
|
|
265
|
+
execFile('convert', [inputPath, '-resize', '800x600', outputPath]);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Rate Limiting
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const authLimiter = rateLimit({
|
|
272
|
+
windowMs: 15 * 60 * 1000,
|
|
273
|
+
max: 5,
|
|
274
|
+
message: { error: 'Too many attempts' },
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
app.post('/login', authLimiter, loginHandler);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Security Headers
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
app.use(helmet({
|
|
284
|
+
contentSecurityPolicy: { ... },
|
|
285
|
+
hsts: { maxAge: 31536000, includeSubDomains: true },
|
|
286
|
+
}));
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Security Checklist
|
|
290
|
+
|
|
291
|
+
- [ ] All inputs validated
|
|
292
|
+
- [ ] Parameterized queries
|
|
293
|
+
- [ ] Rate limiting in place
|
|
294
|
+
- [ ] Auth required on protected routes
|
|
295
|
+
- [ ] Security headers configured
|
|
296
|
+
- [ ] Secrets not in code or logs
|
|
297
|
+
- [ ] HTTPS enforced
|
|
298
|
+
- [ ] Dependencies audited
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Testing
|
|
303
|
+
|
|
304
|
+
### Unit Tests
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
describe('calculateDiscount', () => {
|
|
308
|
+
it('applies percentage discount', () => {
|
|
309
|
+
expect(calculateDiscount(100, { type: 'percentage', value: 10 })).toBe(90);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Integration Tests
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
describe('userRepository', () => {
|
|
318
|
+
beforeEach(async () => await db.user.deleteMany());
|
|
319
|
+
|
|
320
|
+
it('creates a user', async () => {
|
|
321
|
+
const user = await userRepository.create({ email: 'test@example.com', name: 'Test' });
|
|
322
|
+
expect(user.id).toBeDefined();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### API Tests
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
describe('POST /users', () => {
|
|
331
|
+
it('creates a new user', async () => {
|
|
332
|
+
const response = await request(app)
|
|
333
|
+
.post('/users')
|
|
334
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
335
|
+
.send({ email: 'new@test.com', name: 'New User' });
|
|
336
|
+
|
|
337
|
+
expect(response.status).toBe(201);
|
|
338
|
+
expect(response.body.data.email).toBe('new@test.com');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('validates email format', async () => {
|
|
342
|
+
const response = await request(app)
|
|
343
|
+
.post('/users')
|
|
344
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
345
|
+
.send({ email: 'invalid', name: 'Test' });
|
|
346
|
+
|
|
347
|
+
expect(response.status).toBe(422);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Definition of Done
|
|
355
|
+
|
|
356
|
+
A backend feature is complete when:
|
|
357
|
+
|
|
358
|
+
- [ ] Endpoint works as specified
|
|
359
|
+
- [ ] Input validation implemented
|
|
360
|
+
- [ ] Error handling covers edge cases
|
|
361
|
+
- [ ] Authentication/authorization enforced
|
|
362
|
+
- [ ] Unit and integration tests passing
|
|
363
|
+
- [ ] API documentation updated
|
|
364
|
+
- [ ] No security vulnerabilities
|
|
365
|
+
- [ ] Logging in place for debugging
|
|
366
|
+
- [ ] Code reviewed and approved
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Accessibility (a11y)
|
|
2
|
+
|
|
3
|
+
Guidelines for building accessible web applications that work for everyone.
|
|
4
|
+
|
|
5
|
+
## Core Principles
|
|
6
|
+
|
|
7
|
+
### 1. Perceivable
|
|
8
|
+
Information must be presentable in ways users can perceive.
|
|
9
|
+
|
|
10
|
+
### 2. Operable
|
|
11
|
+
UI components must be operable by all users.
|
|
12
|
+
|
|
13
|
+
### 3. Understandable
|
|
14
|
+
Information and UI operation must be understandable.
|
|
15
|
+
|
|
16
|
+
### 4. Robust
|
|
17
|
+
Content must be robust enough for assistive technologies.
|
|
18
|
+
|
|
19
|
+
## Semantic HTML
|
|
20
|
+
|
|
21
|
+
Use the right element for the job.
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<!-- Good: Semantic -->
|
|
25
|
+
<nav>
|
|
26
|
+
<ul>
|
|
27
|
+
<li><a href="/home">Home</a></li>
|
|
28
|
+
<li><a href="/about">About</a></li>
|
|
29
|
+
</ul>
|
|
30
|
+
</nav>
|
|
31
|
+
|
|
32
|
+
<main>
|
|
33
|
+
<article>
|
|
34
|
+
<h1>Article Title</h1>
|
|
35
|
+
<p>Content...</p>
|
|
36
|
+
</article>
|
|
37
|
+
</main>
|
|
38
|
+
|
|
39
|
+
<button onClick={handleClick}>Submit</button>
|
|
40
|
+
|
|
41
|
+
<!-- Bad: Div soup -->
|
|
42
|
+
<div class="nav">
|
|
43
|
+
<div class="nav-item" onclick="...">Home</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="content">
|
|
47
|
+
<div class="title">Article Title</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="button" onclick="...">Submit</div>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Keyboard Navigation
|
|
54
|
+
|
|
55
|
+
### All Interactive Elements Must Be Keyboard Accessible
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// Good: Native button is keyboard accessible
|
|
59
|
+
<button onClick={handleClick}>Click me</button>
|
|
60
|
+
|
|
61
|
+
// If you must use a div (avoid if possible):
|
|
62
|
+
<div
|
|
63
|
+
role="button"
|
|
64
|
+
tabIndex={0}
|
|
65
|
+
onClick={handleClick}
|
|
66
|
+
onKeyDown={(e) => {
|
|
67
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
68
|
+
handleClick();
|
|
69
|
+
}
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
Click me
|
|
73
|
+
</div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Focus Management
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Visible focus indicators
|
|
80
|
+
button:focus {
|
|
81
|
+
outline: 2px solid var(--color-focus);
|
|
82
|
+
outline-offset: 2px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Focus trap for modals
|
|
86
|
+
const Modal = ({ isOpen, onClose, children }) => {
|
|
87
|
+
const firstFocusableRef = useRef();
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (isOpen) {
|
|
91
|
+
firstFocusableRef.current?.focus();
|
|
92
|
+
}
|
|
93
|
+
}, [isOpen]);
|
|
94
|
+
|
|
95
|
+
// ... trap focus within modal
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Skip Links
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<a href="#main-content" class="sr-only focus:not-sr-only">
|
|
103
|
+
Skip to main content
|
|
104
|
+
</a>
|
|
105
|
+
|
|
106
|
+
<main id="main-content">
|
|
107
|
+
...
|
|
108
|
+
</main>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## ARIA
|
|
112
|
+
|
|
113
|
+
### Use ARIA Only When Necessary
|
|
114
|
+
|
|
115
|
+
Native HTML is always preferred. ARIA supplements, not replaces.
|
|
116
|
+
|
|
117
|
+
```html
|
|
118
|
+
<!-- Good: Native HTML -->
|
|
119
|
+
<button>Submit</button>
|
|
120
|
+
<nav>...</nav>
|
|
121
|
+
<main>...</main>
|
|
122
|
+
|
|
123
|
+
<!-- ARIA when custom components are unavoidable -->
|
|
124
|
+
<div role="tablist">
|
|
125
|
+
<button role="tab" aria-selected="true" aria-controls="panel-1">Tab 1</button>
|
|
126
|
+
<button role="tab" aria-selected="false" aria-controls="panel-2">Tab 2</button>
|
|
127
|
+
</div>
|
|
128
|
+
<div role="tabpanel" id="panel-1">Content 1</div>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Common ARIA Patterns
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
// Loading state
|
|
135
|
+
<button aria-busy={isLoading} disabled={isLoading}>
|
|
136
|
+
{isLoading ? 'Loading...' : 'Submit'}
|
|
137
|
+
</button>
|
|
138
|
+
|
|
139
|
+
// Expanded/collapsed
|
|
140
|
+
<button aria-expanded={isOpen} aria-controls="menu">
|
|
141
|
+
Menu
|
|
142
|
+
</button>
|
|
143
|
+
<ul id="menu" hidden={!isOpen}>...</ul>
|
|
144
|
+
|
|
145
|
+
// Live regions for dynamic content
|
|
146
|
+
<div aria-live="polite" aria-atomic="true">
|
|
147
|
+
{statusMessage}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
// Form errors
|
|
151
|
+
<input
|
|
152
|
+
aria-invalid={hasError}
|
|
153
|
+
aria-describedby={hasError ? 'email-error' : undefined}
|
|
154
|
+
/>
|
|
155
|
+
{hasError && <span id="email-error">Please enter a valid email</span>}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Forms
|
|
159
|
+
|
|
160
|
+
### Labels
|
|
161
|
+
|
|
162
|
+
Every form input needs a label.
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
// Good: Explicit label
|
|
166
|
+
<label htmlFor="email">Email</label>
|
|
167
|
+
<input id="email" type="email" />
|
|
168
|
+
|
|
169
|
+
// Good: Implicit label
|
|
170
|
+
<label>
|
|
171
|
+
Email
|
|
172
|
+
<input type="email" />
|
|
173
|
+
</label>
|
|
174
|
+
|
|
175
|
+
// Bad: No label
|
|
176
|
+
<input type="email" placeholder="Email" />
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Error Messages
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<div>
|
|
183
|
+
<label htmlFor="password">Password</label>
|
|
184
|
+
<input
|
|
185
|
+
id="password"
|
|
186
|
+
type="password"
|
|
187
|
+
aria-invalid={!!error}
|
|
188
|
+
aria-describedby={error ? 'password-error' : 'password-hint'}
|
|
189
|
+
/>
|
|
190
|
+
<span id="password-hint">Must be at least 8 characters</span>
|
|
191
|
+
{error && (
|
|
192
|
+
<span id="password-error" role="alert">
|
|
193
|
+
{error}
|
|
194
|
+
</span>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Images
|
|
200
|
+
|
|
201
|
+
### Alt Text
|
|
202
|
+
|
|
203
|
+
```html
|
|
204
|
+
<!-- Informative image: Describe the content -->
|
|
205
|
+
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2" />
|
|
206
|
+
|
|
207
|
+
<!-- Decorative image: Empty alt -->
|
|
208
|
+
<img src="decorative-border.png" alt="" />
|
|
209
|
+
|
|
210
|
+
<!-- Complex image: Longer description -->
|
|
211
|
+
<figure>
|
|
212
|
+
<img src="diagram.png" alt="System architecture diagram" />
|
|
213
|
+
<figcaption>
|
|
214
|
+
The system consists of three layers: presentation, business logic, and data.
|
|
215
|
+
</figcaption>
|
|
216
|
+
</figure>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Color and Contrast
|
|
220
|
+
|
|
221
|
+
### Minimum Contrast Ratios
|
|
222
|
+
|
|
223
|
+
- Normal text: 4.5:1
|
|
224
|
+
- Large text (18px+ or 14px+ bold): 3:1
|
|
225
|
+
- UI components and graphics: 3:1
|
|
226
|
+
|
|
227
|
+
### Don't Rely on Color Alone
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
// Bad: Color only
|
|
231
|
+
<span style={{ color: 'red' }}>Error</span>
|
|
232
|
+
|
|
233
|
+
// Good: Color + icon + text
|
|
234
|
+
<span className="error">
|
|
235
|
+
<ErrorIcon aria-hidden="true" />
|
|
236
|
+
Error: Please fix the following issues
|
|
237
|
+
</span>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Testing
|
|
241
|
+
|
|
242
|
+
### Automated Testing
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
246
|
+
|
|
247
|
+
expect.extend(toHaveNoViolations);
|
|
248
|
+
|
|
249
|
+
test('component has no accessibility violations', async () => {
|
|
250
|
+
const { container } = render(<MyComponent />);
|
|
251
|
+
const results = await axe(container);
|
|
252
|
+
expect(results).toHaveNoViolations();
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Manual Testing Checklist
|
|
257
|
+
|
|
258
|
+
- [ ] Navigate entire page with keyboard only
|
|
259
|
+
- [ ] Test with screen reader (NVDA, VoiceOver, JAWS)
|
|
260
|
+
- [ ] Check color contrast ratios
|
|
261
|
+
- [ ] Verify focus indicators are visible
|
|
262
|
+
- [ ] Test at 200% zoom
|
|
263
|
+
- [ ] Check with reduced motion preference
|
|
264
|
+
|
|
265
|
+
## Common Anti-Patterns
|
|
266
|
+
|
|
267
|
+
### Removing Focus Outlines
|
|
268
|
+
|
|
269
|
+
```css
|
|
270
|
+
/* Never do this */
|
|
271
|
+
*:focus { outline: none; }
|
|
272
|
+
|
|
273
|
+
/* Do this instead */
|
|
274
|
+
*:focus { outline: 2px solid var(--focus-color); }
|
|
275
|
+
*:focus:not(:focus-visible) { outline: none; }
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Click-Only Handlers on Non-Interactive Elements
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
// Bad
|
|
282
|
+
<div onClick={handleClick}>Click me</div>
|
|
283
|
+
|
|
284
|
+
// Good
|
|
285
|
+
<button onClick={handleClick}>Click me</button>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Missing Alternative Text
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
// Bad
|
|
292
|
+
<img src="logo.png" />
|
|
293
|
+
|
|
294
|
+
// Good
|
|
295
|
+
<img src="logo.png" alt="Company Name" />
|
|
296
|
+
```
|