509
CONTRIBUTING.md
509
CONTRIBUTING.md
@@ -1,421 +1,236 @@
|
||||
# Contributing to Claude Skills Library
|
||||
# Contributing to Claude Code Skills
|
||||
|
||||
Thank you for your interest in contributing to the Claude Skills Library! This repository aims to democratize professional expertise through reusable, production-ready skill packages for Claude AI.
|
||||
Thank you for your interest in contributing! This repository is the largest open-source Claude Code skills & agent plugins library (6,800+ stars, 205 production-ready skills).
|
||||
|
||||
## ⚠️ Important: Always Target `dev`
|
||||
|
||||
**All PRs must target the `dev` branch.** PRs targeting `main` will be automatically closed.
|
||||
|
||||
```bash
|
||||
# Fork the repo, then:
|
||||
git checkout -b feat/my-new-skill origin/dev
|
||||
# ... make your changes ...
|
||||
# Open PR → base: dev (NOT main)
|
||||
```
|
||||
|
||||
The `main` branch is reserved for releases and is maintained by the project owner.
|
||||
|
||||
## 🎯 Ways to Contribute
|
||||
|
||||
### 1. Create New Skills
|
||||
|
||||
Add domain expertise in your field:
|
||||
- Marketing, sales, customer success
|
||||
- Engineering specializations
|
||||
- Business functions (finance, HR, operations)
|
||||
- Industry-specific skills (FinTech, EdTech, etc.)
|
||||
|
||||
### 2. Enhance Existing Skills
|
||||
|
||||
Improve current skills with:
|
||||
- Better frameworks and templates
|
||||
- Additional Python automation tools
|
||||
- Updated best practices
|
||||
- More reference materials
|
||||
- Real-world examples and case studies
|
||||
|
||||
### 3. Improve Documentation
|
||||
|
||||
Help others use skills effectively:
|
||||
- Clearer how-to guides
|
||||
- Additional usage examples
|
||||
- Better README files
|
||||
- Translations to other languages
|
||||
|
||||
### 4. Fix Bugs
|
||||
|
||||
Report or fix issues in:
|
||||
- Python scripts
|
||||
- Documentation errors
|
||||
- Broken links
|
||||
- Outdated information
|
||||
**Before you start:** Read [CONVENTIONS.md](CONVENTIONS.md) — it contains the mandatory technical rules that every contribution must follow. PRs that violate conventions will be closed.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Getting Started
|
||||
## Target Branch: `dev`
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.7+ (for running/testing scripts)
|
||||
- Git and GitHub account
|
||||
- Claude AI or Claude Code account (for testing skills)
|
||||
- Familiarity with the skill domain you're contributing to
|
||||
|
||||
### Fork and Clone
|
||||
**All PRs must target the `dev` branch.** PRs targeting `main` will be closed automatically.
|
||||
|
||||
```bash
|
||||
# Fork the repository on GitHub first
|
||||
git clone https://github.com/YOUR_USERNAME/claude-skills.git
|
||||
cd claude-skills
|
||||
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/alirezarezvani/claude-skills.git
|
||||
git fetch upstream dev
|
||||
git checkout -b feature/my-skill upstream/dev
|
||||
```
|
||||
|
||||
### Create a Branch
|
||||
---
|
||||
|
||||
## What We Accept
|
||||
|
||||
### New Skills
|
||||
|
||||
Add domain expertise that doesn't already exist in the repo:
|
||||
- Engineering tools and workflows
|
||||
- Marketing, sales, customer success patterns
|
||||
- Product management frameworks
|
||||
- Regulatory and compliance (ISO, SOC, GDPR, FDA)
|
||||
- Business functions (finance, HR, operations)
|
||||
|
||||
**Before building:** Check existing skills to avoid overlap. Open an issue to discuss if unsure.
|
||||
|
||||
### Improvements to Existing Skills
|
||||
|
||||
- Better workflows and actionable patterns
|
||||
- Additional Python automation scripts
|
||||
- New reference material
|
||||
- More code examples and cross-references
|
||||
- Bug fixes in scripts or documentation
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Python scripts that fail `--help`
|
||||
- Broken cross-references between skills
|
||||
- Incorrect information in reference docs
|
||||
|
||||
---
|
||||
|
||||
## What We Do NOT Accept
|
||||
|
||||
| Type | Reason |
|
||||
|------|--------|
|
||||
| Links to external repos/tools in README | No 3rd party promotion |
|
||||
| Skills requiring paid API keys | Must work without external services |
|
||||
| Scripts with pip dependencies | stdlib-only Python |
|
||||
| PRs that change the skill count (205) | Curated number |
|
||||
| PRs modifying `.codex/`, `.gemini/`, `marketplace.json` | Auto-generated files |
|
||||
| Bloated diffs with fork merge history | Rebase on `dev` first |
|
||||
| Generic advice without actionable frameworks | Must be executable by an AI agent |
|
||||
|
||||
---
|
||||
|
||||
## Skill Creation Guide
|
||||
|
||||
### 1. Create the directory
|
||||
|
||||
```bash
|
||||
# Create feature branch
|
||||
git checkout -b feature/my-new-skill
|
||||
|
||||
# Or for improvements
|
||||
git checkout -b improvement/enhance-content-creator
|
||||
# Example: new engineering skill
|
||||
mkdir -p engineering/my-new-skill/scripts
|
||||
mkdir -p engineering/my-new-skill/references
|
||||
```
|
||||
|
||||
---
|
||||
### 2. Write SKILL.md
|
||||
|
||||
## 📝 Skill Creation Guidelines
|
||||
**Frontmatter — only `name` and `description`:**
|
||||
|
||||
### Following Anthropic's Official Spec
|
||||
|
||||
All skills must follow [Anthropic's Agent Skills Specification](https://docs.anthropic.com/en/docs/agents-and-tools/agent-skills/overview).
|
||||
|
||||
### Required Structure
|
||||
|
||||
```
|
||||
your-skill-name/
|
||||
├── SKILL.md (required)
|
||||
│ ├── YAML frontmatter (name, description, license, metadata)
|
||||
│ └── Markdown content (instructions, examples, workflows)
|
||||
├── scripts/ (optional but recommended)
|
||||
│ ├── tool1.py
|
||||
│ ├── tool2.py
|
||||
│ └── tool3.py
|
||||
├── references/ (optional but recommended)
|
||||
│ ├── framework.md
|
||||
│ ├── best-practices.md
|
||||
│ └── examples.md
|
||||
└── assets/ (optional)
|
||||
└── templates/
|
||||
```
|
||||
|
||||
### SKILL.md Requirements
|
||||
|
||||
**YAML Frontmatter (required):**
|
||||
```yaml
|
||||
---
|
||||
name: your-skill-name
|
||||
description: What it does and when to use it. Include specific triggers and keywords.
|
||||
license: MIT
|
||||
metadata:
|
||||
version: 1.0.0
|
||||
author: Your Name
|
||||
category: domain-category
|
||||
updated: 2025-10-28
|
||||
name: "my-new-skill"
|
||||
description: "Use when the user asks to [specific trigger]. Covers [key capabilities]."
|
||||
---
|
||||
```
|
||||
|
||||
**Markdown Content (required):**
|
||||
- Clear heading and overview
|
||||
- Keywords section for discovery
|
||||
- Quick start guide
|
||||
- Core workflows
|
||||
- Script documentation (if applicable)
|
||||
- Reference guide (if applicable)
|
||||
- Best practices
|
||||
- Examples
|
||||
> **Important:** Do NOT add `license`, `metadata`, `triggers`, `version`, `author`, or any other fields. See [CONVENTIONS.md](CONVENTIONS.md) for the full specification.
|
||||
|
||||
**Target Length:** 100-200 lines for SKILL.md
|
||||
- Keep core instructions lean
|
||||
- Move detailed content to references/
|
||||
- Follow progressive disclosure principle
|
||||
**Content must be:**
|
||||
- Under 500 lines (move detailed content to `references/`)
|
||||
- Opinionated (recommend specific approaches)
|
||||
- Actionable (agent can execute, not just advise)
|
||||
- Include anti-patterns and cross-references sections
|
||||
|
||||
### Python Script Standards
|
||||
### 3. Write Python scripts (optional but valuable)
|
||||
|
||||
**Quality Requirements:**
|
||||
- Production-ready code (not placeholders)
|
||||
- Standard library preferred (minimal dependencies)
|
||||
- CLI-first design with --help support
|
||||
- JSON output option for automation
|
||||
- Clear docstrings and comments
|
||||
- Error handling and validation
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tool Name - Brief description
|
||||
"""Tool Name — brief description."""
|
||||
|
||||
Usage:
|
||||
python tool.py input.txt [--output json]
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
def main():
|
||||
# Implementation
|
||||
pass
|
||||
parser = argparse.ArgumentParser(description="Tool description")
|
||||
parser.add_argument("input", help="Input file or value")
|
||||
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
parser.add_argument("--verbose", action="store_true", help="Verbose output")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Your logic here
|
||||
result = {"status": "ok"}
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
print(f"Result: {result['status']}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
### Documentation Standards
|
||||
**Script rules:** stdlib-only, argparse, `--help`, `--json`, proper exit codes (0/1/2). See [CONVENTIONS.md](CONVENTIONS.md) for full requirements.
|
||||
|
||||
- Clear, actionable guidance
|
||||
- Real-world examples
|
||||
- Specific metrics and benchmarks
|
||||
- No generic advice
|
||||
- Professional tone
|
||||
- Proper formatting
|
||||
### 4. Add reference docs (optional)
|
||||
|
||||
---
|
||||
Place detailed material in `references/`:
|
||||
```
|
||||
my-new-skill/references/
|
||||
├── patterns.md # Detailed patterns and examples
|
||||
├── best-practices.md # Best practices guide
|
||||
└── decision-matrix.md # Comparison tables
|
||||
```
|
||||
|
||||
## 🔄 Contribution Process
|
||||
Reference them from SKILL.md:
|
||||
```markdown
|
||||
> See [references/patterns.md](references/patterns.md) for detailed patterns.
|
||||
```
|
||||
|
||||
### Step 1: Discuss First (Recommended)
|
||||
|
||||
For major contributions:
|
||||
1. Open an issue describing your idea
|
||||
2. Discuss approach with maintainers
|
||||
3. Get feedback before investing time
|
||||
4. Avoid duplicate efforts
|
||||
|
||||
### Step 2: Develop Your Contribution
|
||||
|
||||
Follow the guidelines above for:
|
||||
- New skills
|
||||
- Python tools
|
||||
- Documentation
|
||||
- Bug fixes
|
||||
|
||||
### Step 3: Test Thoroughly
|
||||
|
||||
**For New Skills:**
|
||||
- [ ] YAML frontmatter valid (no syntax errors)
|
||||
- [ ] Description triggers Claude correctly
|
||||
- [ ] All Python scripts work with --help
|
||||
- [ ] All reference links work
|
||||
- [ ] Skill activates when expected
|
||||
- [ ] Tested with Claude AI or Claude Code
|
||||
|
||||
**For Python Tools:**
|
||||
- [ ] Runs without errors
|
||||
- [ ] Handles edge cases
|
||||
- [ ] Provides helpful error messages
|
||||
- [ ] JSON output works (if applicable)
|
||||
- [ ] Dependencies documented
|
||||
|
||||
### Step 4: Submit Pull Request
|
||||
### 5. Validate before submitting
|
||||
|
||||
```bash
|
||||
# Commit your changes
|
||||
git add .
|
||||
git commit -m "feat(domain): add new-skill with [capabilities]"
|
||||
# Structure validation
|
||||
python3 engineering/skill-tester/scripts/skill_validator.py <your-skill-path>
|
||||
|
||||
# Push to your fork
|
||||
git push origin feature/my-new-skill
|
||||
# Script testing
|
||||
python3 engineering/skill-tester/scripts/script_tester.py <your-skill-path> --verbose
|
||||
|
||||
# Create pull request on GitHub
|
||||
# Security audit
|
||||
python3 engineering/skill-security-auditor/scripts/skill_security_auditor.py <your-skill-path> --strict
|
||||
```
|
||||
|
||||
**PR Title Format:**
|
||||
- `feat(domain): add new skill for [purpose]`
|
||||
- `fix(skill-name): correct issue with [component]`
|
||||
- `docs(domain): improve documentation for [topic]`
|
||||
- `refactor(skill-name): optimize [component]`
|
||||
---
|
||||
|
||||
**PR Description Must Include:**
|
||||
- What: What does this add/change/fix?
|
||||
- Why: Why is this valuable?
|
||||
- Testing: How was it tested?
|
||||
- Documentation: What docs were updated?
|
||||
## PR Checklist
|
||||
|
||||
Before submitting your PR, verify:
|
||||
|
||||
- [ ] **Targets `dev` branch** (not `main`)
|
||||
- [ ] **SKILL.md frontmatter** has only `name` + `description`
|
||||
- [ ] **SKILL.md under 500 lines** (detailed content in `references/`)
|
||||
- [ ] **All scripts pass** `python3 script.py --help`
|
||||
- [ ] **Scripts use stdlib only** (no pip dependencies)
|
||||
- [ ] **Scripts support `--json` output**
|
||||
- [ ] **Anti-patterns section** included in SKILL.md
|
||||
- [ ] **Cross-references** to related skills included
|
||||
- [ ] **No modifications** to `.codex/`, `.gemini/`, `marketplace.json`, or index files
|
||||
- [ ] **No 3rd party links** added to README
|
||||
- [ ] **Clean diff** — rebased on `dev`, no merge commit history
|
||||
- [ ] **Security audit passes** with zero CRITICAL/HIGH findings
|
||||
|
||||
---
|
||||
|
||||
## ✅ Quality Standards
|
||||
## Commit Messages
|
||||
|
||||
### Skill Quality Checklist
|
||||
[Conventional Commits](https://www.conventionalcommits.org/) format:
|
||||
|
||||
All new skills must meet these standards:
|
||||
|
||||
**Documentation:**
|
||||
- [ ] Clear SKILL.md with all required sections
|
||||
- [ ] Enhanced description with triggers and keywords
|
||||
- [ ] Keywords section for discovery
|
||||
- [ ] Quick start guide with 2-3 examples
|
||||
- [ ] Professional metadata (license, version, author)
|
||||
- [ ] Domain-specific README updated (if applicable)
|
||||
|
||||
**Python Tools (if included):**
|
||||
- [ ] Production-ready code (not placeholders)
|
||||
- [ ] CLI with --help support
|
||||
- [ ] Proper error handling
|
||||
- [ ] Clear docstrings
|
||||
- [ ] Dependencies minimal and documented
|
||||
|
||||
**References (if included):**
|
||||
- [ ] Actionable frameworks and templates
|
||||
- [ ] Specific guidance (not generic advice)
|
||||
- [ ] Real-world benchmarks and examples
|
||||
- [ ] Properly linked from SKILL.md
|
||||
|
||||
**Testing:**
|
||||
- [ ] Skill activates correctly with Claude
|
||||
- [ ] All scripts execute without errors
|
||||
- [ ] All links work
|
||||
- [ ] No broken references
|
||||
|
||||
**ROI:**
|
||||
- [ ] Demonstrates measurable value
|
||||
- [ ] Time savings quantified
|
||||
- [ ] Quality improvements specified
|
||||
- [ ] Clear use cases documented
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Style Guide
|
||||
|
||||
### Python Code
|
||||
|
||||
**Follow PEP 8:**
|
||||
- 4 spaces for indentation
|
||||
- Max line length: 100 characters
|
||||
- Clear variable names
|
||||
- Docstrings for functions
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
def analyze_content(text: str, keywords: list) -> dict:
|
||||
"""
|
||||
Analyze text content for keyword density and readability.
|
||||
|
||||
Args:
|
||||
text: Content to analyze
|
||||
keywords: List of keywords to check
|
||||
|
||||
Returns:
|
||||
dict: Analysis results with scores and recommendations
|
||||
"""
|
||||
pass
|
||||
```
|
||||
feat(engineering): add browser-automation skill
|
||||
fix(self-improving-agent): use absolute path for hooks
|
||||
improve(tdd-guide): add per-language examples
|
||||
docs: update CONTRIBUTING.md
|
||||
```
|
||||
|
||||
### Markdown Documentation
|
||||
---
|
||||
|
||||
- Use headers consistently (H1 for title, H2 for sections)
|
||||
- Include code blocks with language specification
|
||||
- Use tables for comparisons
|
||||
- Add emojis sparingly for visual hierarchy
|
||||
- Keep paragraphs concise
|
||||
## PR Description Template
|
||||
|
||||
### Commit Messages
|
||||
```markdown
|
||||
## Summary
|
||||
- What: [What does this add/change/fix?]
|
||||
- Why: [Why is this valuable?]
|
||||
|
||||
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
- `feat(domain): add new capability`
|
||||
- `fix(skill): correct bug in script`
|
||||
- `docs(readme): update installation guide`
|
||||
- `refactor(skill): optimize SKILL.md length`
|
||||
- `test(tool): add test coverage`
|
||||
## Checklist
|
||||
- [x] Targets dev branch
|
||||
- [x] SKILL.md frontmatter: name + description only
|
||||
- [x] Under 500 lines
|
||||
- [x] Scripts pass --help
|
||||
- [x] Security audit: 0 critical/high findings
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏆 Recognition
|
||||
## After Your PR is Merged
|
||||
|
||||
### Contributors
|
||||
Maintainers will handle:
|
||||
1. Running sync scripts (Codex, Gemini, integrations)
|
||||
2. Generating docs pages
|
||||
3. Updating mkdocs.yml navigation
|
||||
4. Updating domain plugin.json counts
|
||||
5. Updating marketplace.json
|
||||
6. Merging dev → main for deployment
|
||||
|
||||
All contributors will be:
|
||||
- Listed in CHANGELOG.md for their contributions
|
||||
- Mentioned in release notes
|
||||
- Credited in PR merge messages
|
||||
- Acknowledged in the community
|
||||
|
||||
### Significant Contributions
|
||||
|
||||
Major contributions may result in:
|
||||
- Co-author credit in commits
|
||||
- Feature attribution in documentation
|
||||
- Highlighted in README
|
||||
- Social media recognition
|
||||
You do NOT need to do any of these steps in your PR.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Domain-Specific Guidelines
|
||||
## Recognition
|
||||
|
||||
### Marketing Skills
|
||||
|
||||
- Include real benchmarks (CAC, conversion rates, etc.)
|
||||
- Platform-specific guidance (LinkedIn, Google, etc.)
|
||||
- B2B or B2C focus clearly stated
|
||||
- International market considerations
|
||||
|
||||
### Engineering Skills
|
||||
|
||||
- Include tech stack in metadata
|
||||
- Provide architecture patterns
|
||||
- Add code quality standards
|
||||
- Performance benchmarks
|
||||
|
||||
### Product Skills
|
||||
|
||||
- Include frameworks (RICE, OKR, etc.)
|
||||
- Real-world metrics and KPIs
|
||||
- Template-heavy with examples
|
||||
- Integration points with tools
|
||||
|
||||
### Regulatory/Quality Skills
|
||||
|
||||
- Cite specific standards (ISO, FDA, EU MDR)
|
||||
- Compliance frameworks clear
|
||||
- Industry-specific (HealthTech, MedTech)
|
||||
- Regulatory jurisdiction specified
|
||||
Contributors are credited via:
|
||||
- `Co-Authored-By:` in commit messages
|
||||
- PR merge messages and changelogs
|
||||
- Attribution in SKILL.md when skills are improved from community submissions
|
||||
|
||||
---
|
||||
|
||||
## 🚫 What NOT to Contribute
|
||||
## Questions?
|
||||
|
||||
**We will not accept:**
|
||||
- Generic advice without actionable frameworks
|
||||
- Placeholder scripts (must be production-ready)
|
||||
- Skills without clear use cases
|
||||
- Duplicate capabilities of existing skills
|
||||
- Proprietary or confidential information
|
||||
- Content that violates licenses
|
||||
- Skills promoting unethical practices
|
||||
- **General:** Open a [discussion](https://github.com/alirezarezvani/claude-skills/discussions)
|
||||
- **Bugs:** Use the [issue tracker](https://github.com/alirezarezvani/claude-skills/issues)
|
||||
- **Contact:** [alirezarezvani.com](https://alirezarezvani.com)
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Code of Conduct
|
||||
|
||||
By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
Expected behavior:
|
||||
- Be respectful and inclusive
|
||||
- Provide constructive feedback
|
||||
- Focus on what's best for the community
|
||||
- Show empathy and kindness
|
||||
|
||||
---
|
||||
|
||||
## 📞 Questions?
|
||||
|
||||
- **General Questions:** Open a [discussion](https://github.com/alirezarezvani/claude-skills/discussions)
|
||||
- **Bug Reports:** Use [bug report template](https://github.com/alirezarezvani/claude-skills/issues/new?template=bug_report.md)
|
||||
- **Feature Ideas:** Use [feature request template](https://github.com/alirezarezvani/claude-skills/issues/new?template=feature_request.md)
|
||||
- **Contact:** [alirezarezvani.com](https://alirezarezvani.com) or [medium.com/@alirezarezvani](https://medium.com/@alirezarezvani)
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Thank You!
|
||||
|
||||
Your contributions help make world-class expertise accessible to everyone through Claude AI. Every skill added, bug fixed, or documentation improved makes a difference.
|
||||
|
||||
**Happy contributing!** 🚀
|
||||
**Full technical conventions:** [CONVENTIONS.md](CONVENTIONS.md)
|
||||
|
||||
289
CONVENTIONS.md
Normal file
289
CONVENTIONS.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Repository Conventions
|
||||
|
||||
**Mandatory conventions for the claude-code-skills repository.** Every contributor — human or AI coding agent — must follow these rules. PRs that violate them will be closed.
|
||||
|
||||
For contribution workflow and how to submit PRs, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Skill Structure
|
||||
|
||||
Every skill is a directory under one of the 9 domain folders:
|
||||
|
||||
```
|
||||
<domain>/<skill-name>/
|
||||
├── SKILL.md # Required — main skill file
|
||||
├── .claude-plugin/
|
||||
│ └── plugin.json # Optional — for standalone installs
|
||||
├── scripts/ # Optional — Python CLI tools
|
||||
│ └── *.py
|
||||
├── references/ # Optional — detailed reference docs
|
||||
│ └── *.md
|
||||
└── assets/ # Optional — templates, examples
|
||||
```
|
||||
|
||||
### Domains
|
||||
|
||||
| Directory | Category | Current Count |
|
||||
|-----------|----------|---------------|
|
||||
| `engineering/` | POWERFUL-tier advanced engineering | 35 |
|
||||
| `engineering-team/` | Core engineering roles | 30 |
|
||||
| `marketing-skill/` | Marketing & growth | 43 |
|
||||
| `c-level-advisor/` | Executive advisory | 28 |
|
||||
| `product-team/` | Product management | 14 |
|
||||
| `ra-qm-team/` | Regulatory & quality | 13 |
|
||||
| `project-management/` | PM tools | 6 |
|
||||
| `business-growth/` | Sales & business dev | 4 |
|
||||
| `finance/` | Financial analysis | 2 |
|
||||
|
||||
Place your skill in the domain that best fits. If unsure, open an issue to discuss.
|
||||
|
||||
---
|
||||
|
||||
## 2. SKILL.md Format
|
||||
|
||||
### Frontmatter (YAML)
|
||||
|
||||
**Only two fields are allowed:**
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: "skill-name"
|
||||
description: "One-line description of when to use this skill. Be specific about trigger conditions."
|
||||
---
|
||||
```
|
||||
|
||||
**Do NOT include:** `license`, `metadata`, `triggers`, `version`, `author`, `category`, `updated`, or any other fields. PRs with extra frontmatter fields will be rejected.
|
||||
|
||||
### Content Requirements
|
||||
|
||||
| Requirement | Rule |
|
||||
|-------------|------|
|
||||
| **Line limit** | Under 500 lines. Move detailed content to `references/` files. |
|
||||
| **Opinionated** | Recommend specific approaches. Don't just list options. |
|
||||
| **Actionable** | The agent must be able to execute, not just advise. |
|
||||
| **Anti-patterns** | Include a section on what NOT to do. |
|
||||
| **Cross-references** | Link to related skills in the repo. |
|
||||
| **Code examples** | Include concrete examples where helpful. |
|
||||
|
||||
### Required Sections
|
||||
|
||||
At minimum, every SKILL.md should include:
|
||||
|
||||
1. **Title** (H1) — skill name
|
||||
2. **Overview** — what it does, when to use it
|
||||
3. **Core content** — workflows, patterns, instructions
|
||||
4. **Anti-Patterns** — common mistakes to avoid
|
||||
5. **Cross-References** — related skills in this repo
|
||||
|
||||
Reference detailed material from `references/` files:
|
||||
```markdown
|
||||
> See [references/detailed-guide.md](references/detailed-guide.md) for full patterns.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. plugin.json Format
|
||||
|
||||
If your skill includes a `.claude-plugin/plugin.json`, use this **exact schema**:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "skill-name",
|
||||
"description": "One-line description matching SKILL.md",
|
||||
"version": "2.1.2",
|
||||
"author": {
|
||||
"name": "Alireza Rezvani",
|
||||
"url": "https://alirezarezvani.com"
|
||||
},
|
||||
"homepage": "https://github.com/alirezarezvani/claude-skills/tree/main/<domain>/<skill>",
|
||||
"repository": "https://github.com/alirezarezvani/claude-skills",
|
||||
"license": "MIT",
|
||||
"skills": "./"
|
||||
}
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- `author` **must be an object**, never a string. String format causes install errors.
|
||||
- `version` must match the current repo version (`2.1.2`).
|
||||
- No extra fields (`commands`, `hooks`, `triggers`, `tags`, `category`).
|
||||
- Not every skill needs a plugin.json — skills roll up into their domain's parent plugin automatically.
|
||||
|
||||
---
|
||||
|
||||
## 4. Python Scripts
|
||||
|
||||
All scripts in `scripts/` must follow these rules:
|
||||
|
||||
| Rule | Requirement |
|
||||
|------|-------------|
|
||||
| **Standard library only** | No pip dependencies. Use `argparse`, `json`, `os`, `re`, `sys`, etc. |
|
||||
| **CLI-first** | Must support `python3 script.py --help` |
|
||||
| **JSON output** | Must support `--json` flag for machine-readable output |
|
||||
| **Exit codes** | `0` = success, `1` = warnings, `2` = critical errors |
|
||||
| **No LLM calls** | Scripts must be deterministic — no API calls to AI services |
|
||||
| **No hardcoded secrets** | Use environment variables for credentials |
|
||||
| **Main guard** | Include `if __name__ == "__main__":` |
|
||||
|
||||
**Example skeleton:**
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Tool Name — brief description."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Tool description")
|
||||
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
args = parser.parse_args()
|
||||
|
||||
result = {"status": "ok"}
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(result, indent=2))
|
||||
else:
|
||||
print("Result: ok")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Sub-Skills
|
||||
|
||||
Some skills contain sub-skills in a nested `skills/` directory:
|
||||
|
||||
```
|
||||
engineering-team/playwright-pro/
|
||||
├── SKILL.md ← Parent skill
|
||||
└── skills/
|
||||
├── generate/SKILL.md ← Sub-skill
|
||||
├── fix/SKILL.md ← Sub-skill
|
||||
└── migrate/SKILL.md ← Sub-skill
|
||||
```
|
||||
|
||||
**Sub-skill rules:**
|
||||
- Sub-skills are documented within their parent's docs page, NOT as standalone pages.
|
||||
- Sub-skills do NOT get their own Codex/Gemini symlinks.
|
||||
- Sub-skills do NOT count toward the official skill total.
|
||||
- Do not create new sub-skill structures unless extending an existing compound skill.
|
||||
|
||||
---
|
||||
|
||||
## 6. What NOT to Contribute
|
||||
|
||||
The following will be **immediately closed**:
|
||||
|
||||
| Type | Why |
|
||||
|------|-----|
|
||||
| Links to external repos/tools in README | We don't link 3rd party projects |
|
||||
| Skills that require paid API keys | Must work without external dependencies |
|
||||
| Skills that call LLMs in scripts | Scripts must be deterministic |
|
||||
| PRs that change the official skill count (205) | This number is curated |
|
||||
| PRs targeting `main` instead of `dev` | All PRs must target `dev` |
|
||||
| PRs with bloated diffs (merge history from forks) | Rebase on `dev` HEAD first |
|
||||
| PRs that modify marketplace.json counts | We handle count updates |
|
||||
| PRs that modify codex/gemini index files | These are auto-generated |
|
||||
|
||||
---
|
||||
|
||||
## 7. Cross-Platform Sync
|
||||
|
||||
Platform copies are handled by automated scripts. **Do not create or modify these manually:**
|
||||
|
||||
| Platform | Directory | Script |
|
||||
|----------|-----------|--------|
|
||||
| Codex CLI | `.codex/skills/` | `python3 scripts/sync-codex-skills.py` |
|
||||
| Gemini CLI | `.gemini/skills/` | `python3 scripts/sync-gemini-skills.py` |
|
||||
| Cursor/Aider/etc. | `integrations/` (gitignored) | `scripts/convert.sh --tool all` |
|
||||
|
||||
After your skill is merged, maintainers run these scripts to sync all platforms.
|
||||
|
||||
---
|
||||
|
||||
## 8. Docs Site
|
||||
|
||||
Docs pages are auto-generated by `python3 scripts/generate-docs.py`. After your skill is merged, maintainers will:
|
||||
|
||||
1. Run the docs generator
|
||||
2. Add your skill to `mkdocs.yml` nav (alphabetical within domain)
|
||||
3. Deploy via GitHub Pages
|
||||
|
||||
You do NOT need to create docs pages in your PR.
|
||||
|
||||
---
|
||||
|
||||
## 9. Git Conventions
|
||||
|
||||
### Branch Naming
|
||||
|
||||
```
|
||||
feature/<skill-name> → New skill
|
||||
fix/<description> → Bug fix
|
||||
improve/<skill-name> → Enhancement
|
||||
docs/<description> → Documentation
|
||||
```
|
||||
|
||||
### Commit Messages
|
||||
|
||||
[Conventional Commits](https://www.conventionalcommits.org/) format:
|
||||
|
||||
```
|
||||
feat(engineering): add browser-automation skill
|
||||
fix(self-improving-agent): use absolute path for hooks
|
||||
improve(tdd-guide): add per-language examples
|
||||
docs: update CONTRIBUTING.md
|
||||
chore: sync codex/gemini indexes
|
||||
```
|
||||
|
||||
### PR Requirements
|
||||
|
||||
- **Target branch:** `dev` (never `main`)
|
||||
- **One concern per PR** — don't bundle unrelated changes
|
||||
- **Clean diff** — rebase on `dev` HEAD, no merge commit history
|
||||
- **Description** — explain what, why, and how you tested
|
||||
|
||||
---
|
||||
|
||||
## 10. Quality Validation
|
||||
|
||||
Before submitting, verify your skill passes these checks:
|
||||
|
||||
```bash
|
||||
# Structure validation
|
||||
python3 engineering/skill-tester/scripts/skill_validator.py <your-skill-path> --json
|
||||
|
||||
# Quality scoring
|
||||
python3 engineering/skill-tester/scripts/quality_scorer.py <your-skill-path> --json
|
||||
|
||||
# Script testing (if you have scripts)
|
||||
python3 engineering/skill-tester/scripts/script_tester.py <your-skill-path> --json
|
||||
|
||||
# Security audit
|
||||
python3 engineering/skill-security-auditor/scripts/skill_security_auditor.py <your-skill-path> --strict
|
||||
```
|
||||
|
||||
**Minimum requirements:**
|
||||
- Structure score ≥ 75/100
|
||||
- All scripts pass `--help`
|
||||
- Zero CRITICAL or HIGH security findings
|
||||
- SKILL.md under 500 lines
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | Rule |
|
||||
|------|------|
|
||||
| Frontmatter fields | `name` + `description` only |
|
||||
| SKILL.md max lines | 500 |
|
||||
| Python dependencies | stdlib only |
|
||||
| PR target branch | `dev` |
|
||||
| plugin.json author | Object `{"name": "...", "url": "..."}`, never string |
|
||||
| External links in README | Not accepted |
|
||||
| Skill count | 205 (do not change) |
|
||||
| Commit format | Conventional commits |
|
||||
| Script output | Must support `--json` |
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
# Audit Report Template
|
||||
|
||||
The scanner generates a stakeholder-ready report when run with the `--report` flag:
|
||||
|
||||
```bash
|
||||
python scripts/a11y_scanner.py /path/to/project --report --output audit-report.md
|
||||
```
|
||||
|
||||
## Generated Report Structure
|
||||
|
||||
```markdown
|
||||
# Accessibility Audit Report
|
||||
**Project:** Acme Dashboard
|
||||
**Date:** 2026-03-18
|
||||
**Standard:** WCAG 2.2 Level AA
|
||||
**Tool:** a11y-audit v2.1.2
|
||||
|
||||
## Executive Summary
|
||||
- Files Scanned: 127
|
||||
- Total Violations: 14
|
||||
- Critical: 3 | Major: 7 | Minor: 4
|
||||
- Estimated Remediation: 8-12 hours
|
||||
- Compliance Score: 72% (Target: 100%)
|
||||
|
||||
## Violations by Category
|
||||
| Category | Count | Severity Breakdown |
|
||||
|----------|-------|--------------------|
|
||||
| Missing Alt Text | 3 | 2 Critical, 1 Minor |
|
||||
| Keyboard Access | 4 | 2 Critical, 2 Major |
|
||||
| Color Contrast | 3 | 3 Major |
|
||||
| Form Labels | 2 | 2 Major |
|
||||
| ARIA Usage | 2 | 2 Minor |
|
||||
|
||||
## Detailed Findings
|
||||
[Per-violation details with file, line, WCAG criterion, and fix]
|
||||
|
||||
## Remediation Priority
|
||||
1. Fix all Critical issues (blocks release)
|
||||
2. Fix Major issues in current sprint
|
||||
3. Schedule Minor issues for next sprint
|
||||
|
||||
## Recommendations
|
||||
- Add a11y linting to CI pipeline (eslint-plugin-jsx-a11y)
|
||||
- Include keyboard testing in QA checklist
|
||||
- Schedule quarterly manual audit with assistive technology
|
||||
```
|
||||
127
engineering-team/a11y-audit/references/ci-cd-integration.md
Normal file
127
engineering-team/a11y-audit/references/ci-cd-integration.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# CI/CD Integration for Accessibility Auditing
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
```yaml
|
||||
# .github/workflows/a11y-audit.yml
|
||||
name: Accessibility Audit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**/*.tsx'
|
||||
- 'src/**/*.vue'
|
||||
- 'src/**/*.html'
|
||||
- 'src/**/*.svelte'
|
||||
|
||||
jobs:
|
||||
a11y-audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Run A11y Scanner
|
||||
run: |
|
||||
python scripts/a11y_scanner.py ./src --json > a11y-results.json
|
||||
|
||||
- name: Check for Critical Issues
|
||||
run: |
|
||||
python -c "
|
||||
import json, sys
|
||||
with open('a11y-results.json') as f:
|
||||
data = json.load(f)
|
||||
critical = [v for v in data.get('violations', []) if v['severity'] == 'critical']
|
||||
if critical:
|
||||
print(f'FAILED: {len(critical)} critical a11y violations found')
|
||||
for v in critical:
|
||||
print(f\" [{v['wcag']}] {v['file']}:{v['line']} - {v['message']}\")
|
||||
sys.exit(1)
|
||||
print('PASSED: No critical a11y violations')
|
||||
"
|
||||
|
||||
- name: Upload Audit Report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: a11y-audit-report
|
||||
path: a11y-results.json
|
||||
|
||||
- name: Comment on PR
|
||||
if: failure()
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: a11y-audit
|
||||
message: |
|
||||
## Accessibility Audit Failed
|
||||
Critical WCAG 2.2 violations were found. See the uploaded artifact for details.
|
||||
Run `python scripts/a11y_scanner.py ./src` locally to view and fix issues.
|
||||
```
|
||||
|
||||
## GitLab CI
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml
|
||||
a11y-audit:
|
||||
stage: test
|
||||
image: python:3.11-slim
|
||||
script:
|
||||
- python scripts/a11y_scanner.py ./src --json > a11y-results.json
|
||||
- python -c "
|
||||
import json, sys;
|
||||
data = json.load(open('a11y-results.json'));
|
||||
critical = [v for v in data.get('violations', []) if v['severity'] == 'critical'];
|
||||
sys.exit(1) if critical else print('A11y audit passed')
|
||||
"
|
||||
artifacts:
|
||||
paths:
|
||||
- a11y-results.json
|
||||
when: always
|
||||
rules:
|
||||
- changes:
|
||||
- "src/**/*.{tsx,vue,html,svelte}"
|
||||
```
|
||||
|
||||
## Azure DevOps
|
||||
|
||||
```yaml
|
||||
# azure-pipelines.yml
|
||||
- task: PythonScript@0
|
||||
displayName: 'Run A11y Audit'
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: 'scripts/a11y_scanner.py'
|
||||
arguments: './src --json --output $(Build.ArtifactStagingDirectory)/a11y-results.json'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
condition: always()
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)/a11y-results.json'
|
||||
ArtifactName: 'a11y-audit-report'
|
||||
```
|
||||
|
||||
## Pre-Commit Hook
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# .git/hooks/pre-commit
|
||||
|
||||
# Run a11y scan on staged files only
|
||||
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(tsx|vue|html|svelte|jsx)$')
|
||||
|
||||
if [ -n "$STAGED_FILES" ]; then
|
||||
echo "Running accessibility audit on staged files..."
|
||||
for file in $STAGED_FILES; do
|
||||
python scripts/a11y_scanner.py "$file" --severity critical --quiet
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "A11y audit FAILED for $file. Fix critical issues before committing."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "A11y audit passed."
|
||||
fi
|
||||
```
|
||||
@@ -0,0 +1,83 @@
|
||||
# Color Contrast Guide
|
||||
|
||||
## Contrast Checker Usage
|
||||
|
||||
The `contrast_checker.py` script validates color pairs against WCAG 2.2 contrast requirements.
|
||||
|
||||
```bash
|
||||
# Check a single color pair
|
||||
python scripts/contrast_checker.py --fg "#777777" --bg "#ffffff"
|
||||
|
||||
# Output:
|
||||
# Foreground: #777777 | Background: #ffffff
|
||||
# Contrast Ratio: 4.48:1
|
||||
# AA Normal Text (4.5:1): FAIL
|
||||
# AA Large Text (3.0:1): PASS
|
||||
# AAA Normal Text (7.0:1): FAIL
|
||||
# Suggested alternative: #767676 (4.54:1 - passes AA)
|
||||
|
||||
# Scan a CSS file for all color pairs
|
||||
python scripts/contrast_checker.py --file src/styles/globals.css
|
||||
|
||||
# Scan Tailwind classes in components
|
||||
python scripts/contrast_checker.py --tailwind src/components/
|
||||
```
|
||||
|
||||
## Common Contrast Fixes
|
||||
|
||||
| Original Color | Contrast on White | Fix | New Contrast |
|
||||
|----------------|------------------|-----|--------------|
|
||||
| `#aaaaaa` | 2.32:1 | `#767676` | 4.54:1 (AA) |
|
||||
| `#999999` | 2.85:1 | `#767676` | 4.54:1 (AA) |
|
||||
| `#888888` | 3.54:1 | `#767676` | 4.54:1 (AA) |
|
||||
| `#777777` | 4.48:1 | `#757575` | 4.60:1 (AA) |
|
||||
| `#66bb6a` | 3.06:1 | `#2e7d32` | 5.87:1 (AA) |
|
||||
| `#42a5f5` | 2.81:1 | `#1565c0` | 6.08:1 (AA) |
|
||||
| `#ef5350` | 3.13:1 | `#c62828` | 5.57:1 (AA) |
|
||||
|
||||
## Tailwind CSS Accessible Palette Mapping
|
||||
|
||||
| Inaccessible Class | Contrast on White | Accessible Alternative | Contrast |
|
||||
|---------------------|------------------|----------------------|----------|
|
||||
| `text-gray-400` | 2.68:1 | `text-gray-600` | 5.74:1 |
|
||||
| `text-blue-400` | 2.81:1 | `text-blue-700` | 5.96:1 |
|
||||
| `text-green-400` | 2.12:1 | `text-green-700` | 5.18:1 |
|
||||
| `text-red-400` | 3.04:1 | `text-red-700` | 6.05:1 |
|
||||
| `text-yellow-500` | 1.47:1 | `text-yellow-800` | 7.34:1 |
|
||||
|
||||
## Screen Reader Utility Class
|
||||
|
||||
Every project should include this utility class for visually hiding content while keeping it accessible to screen readers:
|
||||
|
||||
```css
|
||||
/* Visually hidden but accessible to screen readers */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* Allow the element to be focusable when navigated to via keyboard */
|
||||
.sr-only-focusable:focus,
|
||||
.sr-only-focusable:active {
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: inherit;
|
||||
margin: inherit;
|
||||
overflow: visible;
|
||||
clip: auto;
|
||||
white-space: inherit;
|
||||
}
|
||||
```
|
||||
|
||||
Tailwind CSS includes this as `sr-only` by default. For other frameworks:
|
||||
- **Angular**: Add to `styles.scss`
|
||||
- **Vue**: Add to `assets/global.css`
|
||||
- **Svelte**: Add to `app.css`
|
||||
312
engineering-team/a11y-audit/references/examples-by-framework.md
Normal file
312
engineering-team/a11y-audit/references/examples-by-framework.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# Accessibility Audit Examples by Framework
|
||||
|
||||
## Example 1: Vue SFC Form Audit
|
||||
|
||||
```vue
|
||||
<!-- BEFORE: src/components/LoginForm.vue -->
|
||||
<template>
|
||||
<form @submit="handleLogin">
|
||||
<input type="text" placeholder="Email" v-model="email" />
|
||||
<input type="password" placeholder="Password" v-model="password" />
|
||||
<div v-if="error" style="color: red">{{ error }}</div>
|
||||
<div @click="handleLogin">Sign In</div>
|
||||
</form>
|
||||
</template>
|
||||
```
|
||||
|
||||
**Violations detected:**
|
||||
|
||||
| # | WCAG | Severity | Issue |
|
||||
|---|------|----------|-------|
|
||||
| 1 | 1.3.1 | Critical | Inputs missing associated `<label>` elements |
|
||||
| 2 | 3.3.2 | Major | Placeholder text used as only label (disappears on input) |
|
||||
| 3 | 2.1.1 | Critical | `<div @click>` not keyboard accessible |
|
||||
| 4 | 4.1.3 | Major | Error message not announced to screen readers |
|
||||
| 5 | 3.3.1 | Major | Error not programmatically associated with input |
|
||||
|
||||
```vue
|
||||
<!-- AFTER: src/components/LoginForm.vue -->
|
||||
<template>
|
||||
<form @submit.prevent="handleLogin" aria-label="Sign in to your account">
|
||||
<div class="field">
|
||||
<label for="login-email">Email</label>
|
||||
<input
|
||||
id="login-email"
|
||||
type="email"
|
||||
v-model="email"
|
||||
autocomplete="email"
|
||||
required
|
||||
:aria-describedby="emailError ? 'email-error' : undefined"
|
||||
:aria-invalid="!!emailError"
|
||||
/>
|
||||
<span v-if="emailError" id="email-error" role="alert">
|
||||
{{ emailError }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="login-password">Password</label>
|
||||
<input
|
||||
id="login-password"
|
||||
type="password"
|
||||
v-model="password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
:aria-describedby="passwordError ? 'password-error' : undefined"
|
||||
:aria-invalid="!!passwordError"
|
||||
/>
|
||||
<span v-if="passwordError" id="password-error" role="alert">
|
||||
{{ passwordError }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="error" role="alert" aria-live="assertive" class="form-error">
|
||||
{{ error }}
|
||||
</div>
|
||||
<button type="submit">Sign In</button>
|
||||
</form>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Example 2: Angular Template Audit
|
||||
|
||||
```html
|
||||
<!-- BEFORE: src/app/dashboard/dashboard.component.html -->
|
||||
<div class="tabs">
|
||||
<div *ngFor="let tab of tabs"
|
||||
(click)="selectTab(tab)"
|
||||
[class.active]="tab.active">
|
||||
{{ tab.label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div *ngIf="selectedTab">{{ selectedTab.content }}</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Violations detected:**
|
||||
|
||||
| # | WCAG | Severity | Issue |
|
||||
|---|------|----------|-------|
|
||||
| 1 | 4.1.2 | Critical | Tab widget missing ARIA roles (`tablist`, `tab`, `tabpanel`) |
|
||||
| 2 | 2.1.1 | Critical | Tabs not keyboard navigable (arrow keys, Home, End) |
|
||||
| 3 | 2.4.11 | Major | No visible focus indicator on active tab |
|
||||
|
||||
```html
|
||||
<!-- AFTER: src/app/dashboard/dashboard.component.html -->
|
||||
<div class="tabs" role="tablist" aria-label="Dashboard sections">
|
||||
<button
|
||||
*ngFor="let tab of tabs; let i = index"
|
||||
role="tab"
|
||||
[id]="'tab-' + tab.id"
|
||||
[attr.aria-selected]="tab.active"
|
||||
[attr.aria-controls]="'panel-' + tab.id"
|
||||
[attr.tabindex]="tab.active ? 0 : -1"
|
||||
(click)="selectTab(tab)"
|
||||
(keydown)="handleTabKeydown($event, i)"
|
||||
class="tab-button"
|
||||
[class.active]="tab.active">
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="selectedTab"
|
||||
role="tabpanel"
|
||||
[id]="'panel-' + selectedTab.id"
|
||||
[attr.aria-labelledby]="'tab-' + selectedTab.id"
|
||||
tabindex="0"
|
||||
class="tab-content">
|
||||
{{ selectedTab.content }}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Supporting TypeScript for keyboard navigation:**
|
||||
|
||||
```typescript
|
||||
// dashboard.component.ts
|
||||
handleTabKeydown(event: KeyboardEvent, index: number): void {
|
||||
const tabCount = this.tabs.length;
|
||||
let newIndex = index;
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowRight':
|
||||
newIndex = (index + 1) % tabCount;
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
newIndex = (index - 1 + tabCount) % tabCount;
|
||||
break;
|
||||
case 'Home':
|
||||
newIndex = 0;
|
||||
break;
|
||||
case 'End':
|
||||
newIndex = tabCount - 1;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
// Move focus to the new tab button
|
||||
const tabElement = document.getElementById(`tab-${this.tabs[newIndex].id}`);
|
||||
tabElement?.focus();
|
||||
}
|
||||
```
|
||||
|
||||
## Example 3: Next.js Page-Level Audit
|
||||
|
||||
```tsx
|
||||
// BEFORE: src/app/page.tsx
|
||||
export default function Home() {
|
||||
return (
|
||||
<main>
|
||||
<div className="text-4xl font-bold">Welcome to Acme</div>
|
||||
<div className="mt-4">
|
||||
Build better products with our platform.
|
||||
</div>
|
||||
<div className="mt-8 bg-blue-600 text-white px-6 py-3 rounded cursor-pointer"
|
||||
onClick={() => router.push('/signup')}>
|
||||
Get Started
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Violations detected:**
|
||||
|
||||
| # | WCAG | Severity | Issue |
|
||||
|---|------|----------|-------|
|
||||
| 1 | 1.3.1 | Major | Heading uses `<div>` instead of `<h1>` -- no semantic structure |
|
||||
| 2 | 2.4.2 | Major | Page missing `<title>` (Next.js metadata) |
|
||||
| 3 | 2.1.1 | Critical | CTA uses `<div onClick>` -- not keyboard accessible |
|
||||
| 4 | 3.1.1 | Minor | `<html>` missing `lang` attribute (check `layout.tsx`) |
|
||||
|
||||
```tsx
|
||||
// AFTER: src/app/page.tsx
|
||||
import type { Metadata } from 'next';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Acme - Build Better Products',
|
||||
description: 'Build better products with the Acme platform.',
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main>
|
||||
<h1 className="text-4xl font-bold">Welcome to Acme</h1>
|
||||
<p className="mt-4">
|
||||
Build better products with our platform.
|
||||
</p>
|
||||
<Link
|
||||
href="/signup"
|
||||
className="mt-8 inline-block bg-blue-600 text-white px-6 py-3 rounded
|
||||
hover:bg-blue-700 focus-visible:outline focus-visible:outline-2
|
||||
focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||
>
|
||||
Get Started
|
||||
</Link>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// Also fix: src/app/layout.tsx
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Example 4: Svelte Component Audit
|
||||
|
||||
```svelte
|
||||
<!-- BEFORE: src/lib/components/Accordion.svelte -->
|
||||
<script>
|
||||
let openIndex = -1;
|
||||
function toggle(i) { openIndex = openIndex === i ? -1 : i; }
|
||||
</script>
|
||||
|
||||
{#each items as item, i}
|
||||
<div class="header" on:click={() => toggle(i)}>
|
||||
{item.title}
|
||||
</div>
|
||||
{#if openIndex === i}
|
||||
<div class="content">{item.body}</div>
|
||||
{/if}
|
||||
{/each}
|
||||
```
|
||||
|
||||
**Violations detected:**
|
||||
|
||||
| # | WCAG | Severity | Issue |
|
||||
|---|------|----------|-------|
|
||||
| 1 | 4.1.2 | Critical | Accordion missing ARIA roles and properties |
|
||||
| 2 | 2.1.1 | Critical | Headers not keyboard accessible |
|
||||
| 3 | 2.5.8 | Minor | Click targets may be smaller than 24x24px (NEW in WCAG 2.2) |
|
||||
|
||||
```svelte
|
||||
<!-- AFTER: src/lib/components/Accordion.svelte -->
|
||||
<script>
|
||||
export let items = [];
|
||||
let openIndex = -1;
|
||||
|
||||
function toggle(i) {
|
||||
openIndex = openIndex === i ? -1 : i;
|
||||
}
|
||||
|
||||
function handleKeydown(event, i) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
toggle(i);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="accordion">
|
||||
{#each items as item, i}
|
||||
<h3>
|
||||
<button
|
||||
class="accordion-header"
|
||||
aria-expanded={openIndex === i}
|
||||
aria-controls="panel-{i}"
|
||||
id="header-{i}"
|
||||
on:click={() => toggle(i)}
|
||||
on:keydown={(e) => handleKeydown(e, i)}
|
||||
>
|
||||
{item.title}
|
||||
<span class="icon" aria-hidden="true">
|
||||
{openIndex === i ? '−' : '+'}
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
<div
|
||||
id="panel-{i}"
|
||||
role="region"
|
||||
aria-labelledby="header-{i}"
|
||||
class="accordion-content"
|
||||
class:open={openIndex === i}
|
||||
hidden={openIndex !== i}
|
||||
>
|
||||
{item.body}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.accordion-header {
|
||||
min-height: 44px; /* WCAG 2.5.8 Target Size */
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
.accordion-header:focus-visible {
|
||||
outline: 2px solid #005fcc;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -321,3 +321,341 @@ export class MyComponent {
|
||||
border-width: 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Fix Patterns Catalog
|
||||
|
||||
### React / Next.js Fix Patterns
|
||||
|
||||
#### Missing Alt Text (1.1.1)
|
||||
|
||||
```tsx
|
||||
// BEFORE
|
||||
<img src={hero} />
|
||||
|
||||
// AFTER - Informational image
|
||||
<img src={hero} alt="Team collaborating around a whiteboard" />
|
||||
|
||||
// AFTER - Decorative image
|
||||
<img src={divider} alt="" role="presentation" />
|
||||
```
|
||||
|
||||
#### Non-Interactive Element with Click Handler (2.1.1)
|
||||
|
||||
```tsx
|
||||
// BEFORE
|
||||
<div onClick={handleClick}>Click me</div>
|
||||
|
||||
// AFTER - If it navigates
|
||||
<Link href="/destination">Click me</Link>
|
||||
|
||||
// AFTER - If it performs an action
|
||||
<button type="button" onClick={handleClick}>Click me</button>
|
||||
```
|
||||
|
||||
#### Missing Focus Management in Modals (2.4.3)
|
||||
|
||||
```tsx
|
||||
// BEFORE
|
||||
function Modal({ isOpen, onClose, children }) {
|
||||
if (!isOpen) return null;
|
||||
return <div className="modal-overlay">{children}</div>;
|
||||
}
|
||||
|
||||
// AFTER
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
function Modal({ isOpen, onClose, children, title }) {
|
||||
const modalRef = useRef(null);
|
||||
const previousFocus = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
previousFocus.current = document.activeElement;
|
||||
modalRef.current?.focus();
|
||||
} else {
|
||||
previousFocus.current?.focus();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const handleKeydown = (e) => {
|
||||
if (e.key === 'Escape') onClose();
|
||||
if (e.key === 'Tab') {
|
||||
const focusable = modalRef.current?.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
if (!focusable?.length) return;
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault();
|
||||
last.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
return () => document.removeEventListener('keydown', handleKeydown);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay" onClick={onClose} aria-hidden="true">
|
||||
<div
|
||||
ref={modalRef}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={title}
|
||||
tabIndex={-1}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
onClick={onClose}
|
||||
aria-label="Close dialog"
|
||||
className="modal-close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Focus Appearance (2.4.11 -- NEW in WCAG 2.2)
|
||||
|
||||
```css
|
||||
/* BEFORE */
|
||||
button:focus {
|
||||
outline: none; /* Removes default focus indicator */
|
||||
}
|
||||
|
||||
/* AFTER - Meets WCAG 2.2 Focus Appearance */
|
||||
button:focus-visible {
|
||||
outline: 2px solid #005fcc;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// Tailwind CSS pattern
|
||||
<button className="focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">
|
||||
Submit
|
||||
</button>
|
||||
```
|
||||
|
||||
### Vue Fix Patterns
|
||||
|
||||
#### Missing Form Labels (1.3.1)
|
||||
|
||||
```vue
|
||||
<!-- BEFORE -->
|
||||
<input type="text" v-model="name" placeholder="Name" />
|
||||
|
||||
<!-- AFTER -->
|
||||
<label for="user-name">Name</label>
|
||||
<input id="user-name" type="text" v-model="name" autocomplete="name" />
|
||||
```
|
||||
|
||||
#### Dynamic Content Without Live Region (4.1.3)
|
||||
|
||||
```vue
|
||||
<!-- BEFORE -->
|
||||
<div v-if="status">{{ statusMessage }}</div>
|
||||
|
||||
<!-- AFTER -->
|
||||
<div aria-live="polite" aria-atomic="true">
|
||||
<p v-if="status">{{ statusMessage }}</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Vue Router Navigation Announcements (2.4.2)
|
||||
|
||||
```typescript
|
||||
// router/index.ts
|
||||
router.afterEach((to) => {
|
||||
const title = to.meta.title || 'Page';
|
||||
document.title = `${title} | My App`;
|
||||
|
||||
// Announce route change to screen readers
|
||||
const announcer = document.getElementById('route-announcer');
|
||||
if (announcer) {
|
||||
announcer.textContent = `Navigated to ${title}`;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```vue
|
||||
<!-- App.vue - Add announcer element -->
|
||||
<div
|
||||
id="route-announcer"
|
||||
role="status"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
class="sr-only"
|
||||
></div>
|
||||
```
|
||||
|
||||
### Angular Fix Patterns
|
||||
|
||||
#### Missing ARIA on Custom Components (4.1.2)
|
||||
|
||||
```typescript
|
||||
// BEFORE
|
||||
@Component({
|
||||
selector: 'app-dropdown',
|
||||
template: `
|
||||
<div (click)="toggle()">{{ selected }}</div>
|
||||
<div *ngIf="isOpen">
|
||||
<div *ngFor="let opt of options" (click)="select(opt)">{{ opt }}</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
|
||||
// AFTER
|
||||
@Component({
|
||||
selector: 'app-dropdown',
|
||||
template: `
|
||||
<button
|
||||
role="combobox"
|
||||
[attr.aria-expanded]="isOpen"
|
||||
aria-haspopup="listbox"
|
||||
[attr.aria-label]="label"
|
||||
(click)="toggle()"
|
||||
(keydown)="handleKeydown($event)"
|
||||
>
|
||||
{{ selected }}
|
||||
</button>
|
||||
<ul *ngIf="isOpen" role="listbox" [attr.aria-label]="label + ' options'">
|
||||
<li
|
||||
*ngFor="let opt of options; let i = index"
|
||||
role="option"
|
||||
[attr.aria-selected]="opt === selected"
|
||||
[attr.id]="'option-' + i"
|
||||
(click)="select(opt)"
|
||||
(keydown)="handleOptionKeydown($event, opt, i)"
|
||||
tabindex="-1"
|
||||
>
|
||||
{{ opt }}
|
||||
</li>
|
||||
</ul>
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
#### Angular CDK A11y Module Integration
|
||||
|
||||
```typescript
|
||||
// Use Angular CDK for focus trap in dialogs
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div cdkTrapFocus cdkTrapFocusAutoCapture>
|
||||
<h2 id="dialog-title">Edit Profile</h2>
|
||||
<!-- dialog content -->
|
||||
</div>
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### Svelte Fix Patterns
|
||||
|
||||
#### Accessible Announcements (4.1.3)
|
||||
|
||||
```svelte
|
||||
<!-- BEFORE -->
|
||||
{#if message}
|
||||
<p class="toast">{message}</p>
|
||||
{/if}
|
||||
|
||||
<!-- AFTER -->
|
||||
<div aria-live="polite" class="sr-only">
|
||||
{#if message}
|
||||
<p>{message}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="toast" aria-hidden="true">
|
||||
{#if message}
|
||||
<p>{message}</p>
|
||||
{/if}
|
||||
</div>
|
||||
```
|
||||
|
||||
#### SvelteKit Page Titles (2.4.2)
|
||||
|
||||
```svelte
|
||||
<!-- +page.svelte -->
|
||||
<svelte:head>
|
||||
<title>Dashboard | My App</title>
|
||||
</svelte:head>
|
||||
```
|
||||
|
||||
### Plain HTML Fix Patterns
|
||||
|
||||
#### Skip Navigation Link (2.4.1)
|
||||
|
||||
```html
|
||||
<!-- BEFORE -->
|
||||
<body>
|
||||
<nav><!-- long navigation --></nav>
|
||||
<main><!-- content --></main>
|
||||
</body>
|
||||
|
||||
<!-- AFTER -->
|
||||
<body>
|
||||
<a href="#main-content" class="skip-link">Skip to main content</a>
|
||||
<nav aria-label="Main navigation"><!-- long navigation --></nav>
|
||||
<main id="main-content" tabindex="-1"><!-- content --></main>
|
||||
</body>
|
||||
```
|
||||
|
||||
```css
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 0;
|
||||
padding: 8px 16px;
|
||||
background: #005fcc;
|
||||
color: #fff;
|
||||
z-index: 1000;
|
||||
transition: top 0.2s;
|
||||
}
|
||||
.skip-link:focus {
|
||||
top: 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### Accessible Data Table (1.3.1)
|
||||
|
||||
```html
|
||||
<!-- BEFORE -->
|
||||
<table>
|
||||
<tr><td>Name</td><td>Email</td><td>Role</td></tr>
|
||||
<tr><td>Alice</td><td>alice@co.com</td><td>Admin</td></tr>
|
||||
</table>
|
||||
|
||||
<!-- AFTER -->
|
||||
<table aria-label="Team members">
|
||||
<caption class="sr-only">List of team members and their roles</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Alice</th>
|
||||
<td>alice@co.com</td>
|
||||
<td>Admin</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
41
engineering-team/a11y-audit/references/testing-checklist.md
Normal file
41
engineering-team/a11y-audit/references/testing-checklist.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Accessibility Testing Checklist
|
||||
|
||||
Use this checklist after applying fixes to verify accessibility manually.
|
||||
|
||||
## Keyboard Navigation
|
||||
- [ ] All interactive elements reachable via Tab key
|
||||
- [ ] Tab order follows visual/logical reading order
|
||||
- [ ] Focus indicator visible on every focusable element (2px+ outline)
|
||||
- [ ] Modals trap focus and return focus on close
|
||||
- [ ] Escape key closes modals, dropdowns, and popups
|
||||
- [ ] Arrow keys navigate within composite widgets (tabs, menus, listboxes)
|
||||
- [ ] No keyboard traps (user can always Tab away)
|
||||
|
||||
## Screen Reader
|
||||
- [ ] All images have appropriate alt text (or `alt=""` for decorative)
|
||||
- [ ] Headings create logical document outline (h1 -> h2 -> h3)
|
||||
- [ ] Form inputs have associated labels
|
||||
- [ ] Error messages announced via `aria-live` or `role="alert"`
|
||||
- [ ] Page title updates on navigation (SPA)
|
||||
- [ ] Dynamic content changes announced appropriately
|
||||
|
||||
## Visual
|
||||
- [ ] Text contrast meets 4.5:1 for normal text, 3:1 for large text
|
||||
- [ ] UI component contrast meets 3:1 against background
|
||||
- [ ] Content reflows without horizontal scrolling at 320px width
|
||||
- [ ] Text resizable to 200% without loss of content
|
||||
- [ ] No information conveyed by color alone
|
||||
- [ ] Focus indicators meet 2.4.11 Focus Appearance criteria
|
||||
|
||||
## Motion and Media
|
||||
- [ ] Animations respect `prefers-reduced-motion`
|
||||
- [ ] No auto-playing media with audio
|
||||
- [ ] No content flashing more than 3 times per second
|
||||
- [ ] Video has captions; audio has transcripts
|
||||
|
||||
## Forms
|
||||
- [ ] All inputs have visible labels
|
||||
- [ ] Required fields indicated (not by color alone)
|
||||
- [ ] Error messages specific and associated with input via `aria-describedby`
|
||||
- [ ] Autocomplete attributes present on common fields (name, email, etc.)
|
||||
- [ ] No CAPTCHA without alternative method (WCAG 2.2 3.3.8)
|
||||
@@ -0,0 +1,79 @@
|
||||
# WCAG 2.2 New Success Criteria Reference
|
||||
|
||||
These criteria were added in WCAG 2.2 and are commonly missed.
|
||||
|
||||
## 2.4.11 Focus Appearance (Level AA)
|
||||
|
||||
The focus indicator must have a minimum area of a 2px perimeter around the component and a contrast ratio of at least 3:1 against adjacent colors.
|
||||
|
||||
**Pattern:**
|
||||
```css
|
||||
:focus-visible {
|
||||
outline: 2px solid #005fcc;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
## 2.5.7 Dragging Movements (Level AA)
|
||||
|
||||
Any functionality that uses dragging must have a single-pointer alternative (click, tap).
|
||||
|
||||
**Pattern:**
|
||||
```tsx
|
||||
// Sortable list: support both drag and button-based reorder
|
||||
<li draggable onDragStart={handleDrag}>
|
||||
{item.name}
|
||||
<button onClick={() => moveUp(index)} aria-label={`Move ${item.name} up`}>
|
||||
Move Up
|
||||
</button>
|
||||
<button onClick={() => moveDown(index)} aria-label={`Move ${item.name} down`}>
|
||||
Move Down
|
||||
</button>
|
||||
</li>
|
||||
```
|
||||
|
||||
## 2.5.8 Target Size (Level AA)
|
||||
|
||||
Interactive targets must be at least 24x24 CSS pixels, with exceptions for inline text links and elements where the spacing provides equivalent clearance.
|
||||
|
||||
**Pattern:**
|
||||
```css
|
||||
button, a, input, select, textarea {
|
||||
min-height: 24px;
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
/* Recommended: 44x44px for touch targets */
|
||||
@media (pointer: coarse) {
|
||||
button, a, input[type="checkbox"], input[type="radio"] {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3.3.7 Redundant Entry (Level A)
|
||||
|
||||
Information previously entered by the user must be auto-populated or available for selection when needed again in the same process.
|
||||
|
||||
**Pattern:**
|
||||
```tsx
|
||||
// Multi-step form: persist data across steps
|
||||
const [formData, setFormData] = useState({});
|
||||
|
||||
// Step 2 pre-fills shipping address from billing
|
||||
<input
|
||||
defaultValue={formData.billingAddress || ''}
|
||||
autoComplete="shipping street-address"
|
||||
/>
|
||||
```
|
||||
|
||||
## 3.3.8 Accessible Authentication (Level AA)
|
||||
|
||||
Authentication must not require cognitive function tests (e.g., remembering a password, solving a puzzle) unless an alternative is provided.
|
||||
|
||||
**Pattern:**
|
||||
- Support password managers (`autocomplete="current-password"`)
|
||||
- Offer passkey / biometric authentication
|
||||
- Allow copy-paste in password fields (never block paste)
|
||||
- Provide email/SMS OTP as alternative to CAPTCHA
|
||||
Reference in New Issue
Block a user