accessibility-journey
Audit keyboard navigation by tabbing through a page, capturing focus state at each stop, and running axe-core for WCAG violations.
What this skill does
# Accessibility Journey
Perform a complete keyboard navigation audit by tabbing through every focusable element on a page. At each tab stop, capture the focused element's properties, check for visible focus indicators, and record the tab order. Combine this with an axe-core WCAG audit for a comprehensive accessibility report.
## When to Use
- Auditing a page for WCAG 2.1 AA keyboard accessibility compliance
- Verifying that all interactive elements are reachable via Tab key
- Checking for missing focus indicators (outline, box-shadow) on focusable elements
- Detecting focus traps where keyboard users get stuck in a component
- Validating tab order matches the expected visual reading order
- Reviewing heading hierarchy and ARIA attribute correctness
- QA before accessibility certification or remediation planning
## Prerequisites
- Playwright MCP server connected with a browser session available
- Target page must be publicly accessible or already authenticated
- Page should be fully loaded (all interactive elements rendered)
- axe-core CDN must be reachable (or the page must already include axe-core)
## Workflow
### Step 1: Navigate to the Target Page
```
browser_navigate({ url: "https://example.com/page" })
```
Wait for the page to be ready:
```
browser_wait_for({ time: 3 })
```
### Step 2: Inject axe-core
Use `browser_evaluate` to load axe-core from CDN. This provides automated WCAG violation detection.
```javascript
browser_evaluate({
function: `() => {
return new Promise((resolve, reject) => {
if (window.axe) {
resolve('axe-core already loaded');
return;
}
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.9.1/axe.min.js';
script.onload = () => resolve('axe-core loaded: v' + window.axe.version);
script.onerror = () => reject('Failed to load axe-core from CDN');
document.head.appendChild(script);
});
}`
})
```
### Step 3: Run axe-core Audit
Use `browser_evaluate` to execute the full axe-core scan. This runs asynchronously and returns all violations, incomplete checks, and passes.
```javascript
browser_evaluate({
function: `() => {
return axe.run().then(results => {
return {
violations: results.violations.map(v => ({
id: v.id,
impact: v.impact,
description: v.description,
helpUrl: v.helpUrl,
nodes: v.nodes.length,
targets: v.nodes.slice(0, 5).map(n => ({
target: n.target.join(' '),
html: n.html.substring(0, 120),
failureSummary: n.failureSummary
}))
})),
incomplete: results.incomplete.map(i => ({
id: i.id,
impact: i.impact,
description: i.description,
nodes: i.nodes.length
})),
violationCount: results.violations.length,
incompleteCount: results.incomplete.length,
passCount: results.passes.length
};
});
}`
})
```
### Step 4: Initialize Tab Journey Tracker
Use `browser_evaluate` to set up the tracking data structure before starting the tab loop.
```javascript
browser_evaluate({
function: `() => {
window.__tabJourney = [];
window.__tabStartTime = performance.now();
// Reset focus to the beginning of the document
document.body.focus();
document.activeElement?.blur?.();
return {
activeElement: document.activeElement?.tagName || 'BODY',
journeyInitialized: true
};
}`
})
```
### Step 5: Tab Loop -- Press Tab and Capture Focus State
Repeat the following cycle up to 50 times or until focus returns to `<body>` or wraps around to the first element.
**5a: Press Tab**
```
browser_press_key({ key: "Tab" })
```
**5b: Capture focused element details**
Use `browser_evaluate` to inspect `document.activeElement` and check for focus indicator visibility.
```javascript
browser_evaluate({
function: `() => {
const el = document.activeElement;
if (!el || el === document.body) {
return { reachedBody: true, journeyLength: window.__tabJourney.length };
}
const rect = el.getBoundingClientRect();
const style = getComputedStyle(el);
const outlineStyle = style.outlineStyle;
const outlineWidth = parseFloat(style.outlineWidth) || 0;
const outlineColor = style.outlineColor;
const boxShadow = style.boxShadow;
const hasOutline = outlineStyle !== 'none' && outlineWidth > 0;
const hasBoxShadow = boxShadow && boxShadow !== 'none';
const hasFocusIndicator = hasOutline || hasBoxShadow;
const stop = {
index: window.__tabJourney.length,
tag: el.tagName.toLowerCase(),
role: el.getAttribute('role') || '',
ariaLabel: el.getAttribute('aria-label') || '',
ariaLabelledBy: el.getAttribute('aria-labelledby') || '',
text: (el.textContent || '').trim().substring(0, 60),
href: el.getAttribute('href') || '',
type: el.getAttribute('type') || '',
tabIndex: el.tabIndex,
id: el.id || '',
className: String(el.className || '').substring(0, 60),
rect: {
top: Math.round(rect.top),
left: Math.round(rect.left),
width: Math.round(rect.width),
height: Math.round(rect.height)
},
focusIndicator: {
hasOutline: hasOutline,
outlineStyle: outlineStyle,
outlineWidth: outlineWidth,
outlineColor: outlineColor,
hasBoxShadow: hasBoxShadow,
visible: hasFocusIndicator
},
isOffscreen: rect.top < 0 || rect.left < 0 ||
rect.bottom > window.innerHeight || rect.right > window.innerWidth,
timestamp: Math.round(performance.now() - window.__tabStartTime)
};
window.__tabJourney.push(stop);
// Check for focus trap (same element as previous 2 stops)
const journey = window.__tabJourney;
const isTrap = journey.length >= 3 &&
journey[journey.length - 1].tag === journey[journey.length - 2].tag &&
journey[journey.length - 1].id === journey[journey.length - 2].id &&
journey[journey.length - 2].tag === journey[journey.length - 3].tag &&
journey[journey.length - 2].id === journey[journey.length - 3].id;
return {
stop: stop,
journeyLength: journey.length,
focusTrapDetected: isTrap,
reachedBody: false
};
}`
})
```
**5c: Take screenshot at each tab stop (optional, for detailed audits)**
```
browser_take_screenshot({ type: "png", filename: "tab-stop-{index}.png" })
```
Replace `{index}` with the current tab stop number.
**5d: Check termination conditions**
Stop the tab loop when any of these conditions is met:
- `reachedBody` is `true` (focus returned to body)
- `journeyLength` reaches 50
- `focusTrapDetected` is `true` (log it and break)
- The current element matches the first element in the journey (focus wrapped around)
### Step 6: Capture Accessibility Tree Snapshot
Use `browser_snapshot` to get the full accessibility tree as Playwright sees it.
```
browser_snapshot()
```
### Step 7: Extract Final Journey Report
Use `browser_evaluate` to compile the complete journey data and summary statistics.
```javascript
browser_evaluate({
function: `() => {
const journey = window.__tabJourney;
const missingFocusIndicator = journey.filter(s => !s.focusIndicator.visible);
const offscreenStops = journey.filter(s => s.isOffscreen);
const interactiveElements = journey.filter(s =>
['a', 'button', 'input', 'select', 'textarea'].includes(s.tag)
);
const nonInteractiveTabStops = journey.filter(s =>
!['a', 'button', 'input', 'select', 'textarea'].includes(s.tag) &&
!s.role
);
// Check for skip link (first tab stop with href starting with #)
const skipLink = journey.length > 0 && journey[0].tag === 'a' &&
journey[0].href.startsWith('#');
// Extract heading hierarchy from page
const headings = Array.from(documRelated in Security
mac-ops
IncludedComprehensive macOS workstation operations — diagnose kernel panics, identify failing drives, audit launchd startup items, decode wake reasons, triage TCC permission denials, manage APFS snapshots, recover from no-boot. Use for: Mac is slow, slow bootup, won't boot, kernel panic, kernel_task hot, mds_stores CPU, photoanalysisd, cloudd, login loop, gray screen, sleep wake failure, drive failing, IO errors, APFS snapshots eating space, Time Machine local snapshots, Spotlight indexing, launchd, LaunchAgent, LaunchDaemon, login items, TCC permissions, Full Disk Access, Screen Recording denied, Gatekeeper, quarantine, com.apple.quarantine, app is damaged, helper tool, /Library/PrivilegedHelperTools, pmset, wake reasons, dark wake, sysdiagnose, panic.ips, DiagnosticReports, configuration profile, MDM profile, remote diagnostics over SSH.
a11y-audit
IncludedRun accessibility audits on web projects combining automated scanning (axe-core, Lighthouse) with WCAG 2.1 AA compliance mapping, manual check guidance, and structured reporting. Output is configurable: markdown report only, markdown plus machine-readable JSON, or markdown plus issue tracker integration. Use this skill whenever the user mentions "accessibility audit", "a11y audit", "WCAG audit", "accessibility check", "compliance scan", or asks to check a web project for accessibility issues. Also trigger when the user wants to verify WCAG conformance or map findings to a specific standard (CAN-ASC-6.2, EN 301 549, ADA/AODA).
erpclaw
IncludedAI-native ERP system with self-extending OS. Full accounting, invoicing, inventory, purchasing, tax, billing, HR, payroll, advanced accounting (ASC 606/842, intercompany, consolidation), and financial reporting. 413 actions across 14 domains, 43 expansion modules. Constitutional guardrails, adversarial audit, schema migration. Double-entry GL, immutable audit trail, US GAAP.
assess
IncludedAssesses and rates quality 0-10 across multiple dimensions (correctness, maintainability, security, performance, testability, simplicity) with pros/cons analysis. Compares against project conventions and prior decisions from memory. Produces structured evaluation reports with actionable improvement suggestions. Use when evaluating code, designs, architectures, or comparing alternative approaches.
spring-boot-security-jwt
IncludedProvides JWT authentication and authorization patterns for Spring Boot 3.5.x covering token generation with JJWT, Bearer/cookie authentication, database/OAuth2 integration, and RBAC/permission-based access control using Spring Security 6.x. Use when implementing authentication or authorization in Spring Boot applications.
code-hardcode-audit
IncludedDetect hardcoded values, magic numbers, and leaked secrets. TRIGGERS - hardcode audit, magic numbers, PLR2004, secret scanning.