Install
$ agentstack add skill-mteelokee-rgaa-accessibility-audit-rgaa-forms ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
RGAA Forms Accessibility Audit (Theme 11)
Audits a web page against the 13 criteria of RGAA v4.1.2 Theme 11: Forms.
Audit procedure
Step 1: Gather form data
(() => {
const forms = [];
document.querySelectorAll('form, [role="form"]').forEach((form, fi) => {
const fields = [];
form.querySelectorAll('input, select, textarea, [role="checkbox"], [role="radio"], [role="combobox"], [role="listbox"], [role="slider"], [role="spinbutton"], [role="textbox"], [role="switch"]').forEach(el => {
const id = el.id;
const associatedLabel = id ? document.querySelector(`label[for="${id}"]`) : null;
const parentLabel = el.closest('label');
fields.push({
tag: el.tagName.toLowerCase(),
type: el.type || el.getAttribute('role'),
id: id,
name: el.name,
required: el.required || el.getAttribute('aria-required'),
ariaLabel: el.getAttribute('aria-label'),
ariaLabelledby: el.getAttribute('aria-labelledby'),
ariaDescribedby: el.getAttribute('aria-describedby'),
ariaInvalid: el.getAttribute('aria-invalid'),
ariaErrormessage: el.getAttribute('aria-errormessage'),
title: el.getAttribute('title'),
placeholder: el.getAttribute('placeholder'),
autocomplete: el.getAttribute('autocomplete'),
hasAssociatedLabel: !!associatedLabel,
labelText: associatedLabel?.textContent.trim().substring(0, 60) || parentLabel?.textContent.trim().substring(0, 60),
isInFieldset: !!el.closest('fieldset'),
fieldsetLegend: el.closest('fieldset')?.querySelector('legend')?.textContent.trim().substring(0, 60),
isHidden: el.type === 'hidden'
});
});
// Fieldsets
const fieldsets = [];
form.querySelectorAll('fieldset, [role="group"], [role="radiogroup"]').forEach(fs => {
fieldsets.push({
tag: fs.tagName.toLowerCase(),
role: fs.getAttribute('role'),
legendText: fs.querySelector('legend')?.textContent.trim().substring(0, 60),
ariaLabel: fs.getAttribute('aria-label'),
ariaLabelledby: fs.getAttribute('aria-labelledby'),
fieldCount: fs.querySelectorAll('input, select, textarea').length
});
});
// Buttons
const buttons = [];
form.querySelectorAll('button, input[type="submit"], input[type="reset"], input[type="button"], input[type="image"], [role="button"]').forEach(btn => {
buttons.push({
tag: btn.tagName.toLowerCase(),
type: btn.type,
text: btn.textContent.trim().substring(0, 60) || btn.value || btn.getAttribute('aria-label'),
ariaLabel: btn.getAttribute('aria-label'),
title: btn.getAttribute('title')
});
});
// Select groups
const selects = [];
form.querySelectorAll('select').forEach(sel => {
const optgroups = sel.querySelectorAll('optgroup');
selects.push({
name: sel.name,
hasOptgroups: optgroups.length > 0,
optgroupLabels: Array.from(optgroups).map(og => og.label),
optionCount: sel.options.length
});
});
forms.push({
index: fi,
action: form.action?.substring(0, 100),
method: form.method,
ariaLabel: form.getAttribute('aria-label'),
fields: fields.slice(0, 30),
fieldsets: fieldsets,
buttons: buttons,
selects: selects
});
});
// Also check for isolated form fields not inside
const isolatedFields = document.querySelectorAll('input:not(form input), select:not(form select), textarea:not(form textarea)');
return JSON.stringify({ forms, isolatedFieldCount: isolatedFields.length }, null, 2);
})()
Step 2: Evaluate criteria
Criterion 11.1 – Every form field has a label. Each field must have a label via one of: aria-labelledby, aria-label, `, or title`. A placeholder alone is NOT a valid label.
Criterion 11.2 – Labels are relevant. The label text must clearly describe what input is expected.
Criterion 11.3 – Labels are coherent. The accessible name (computed from aria-label, aria-labelledby, label, title) must be consistent with any visible label.
Criterion 11.4 – Labels are visually close to their fields. Labels should be adjacent to their fields:
- Above or to the left for text inputs
- Above or to the left for select/textarea
- To the right or below for checkboxes and radios
Criterion 11.5 – Related fields are grouped. Fields of the same nature (e.g., address parts, date components, radio choices) must be grouped with ` + , or [role="group"]/[role="radiogroup"] + aria-label/aria-labelledby`.
Criterion 11.6 – Field groupings have a legend. Each ` must have a non-empty . Each [role="group"] or [role="radiogroup"] must have aria-label or aria-labelledby`.
Criterion 11.7 – Grouping legends are relevant. The legend must describe the purpose of the group.
Criterion 11.8 – Select options of the same nature are grouped. Related options in ` should use `.
Criterion 11.9 – Button labels are relevant. Submit, reset, and other buttons must have descriptive accessible names, not just "Submit" on a multi-form page.
Criterion 11.10 – Input validation is accessible.
- Required fields are indicated with
requiredoraria-required="true"and a visible indicator - Field type/format is communicated (e.g., "Date: DD/MM/YYYY")
- Errors are communicated:
aria-invalid="true"on the field, error message linked viaaria-describedbyoraria-errormessage
Criterion 11.11 – Error messages suggest corrections. When a field value is rejected, the error message must explain what is expected (format, allowed values, examples). Not just "Invalid input".
Criterion 11.12 – Data modification/deletion is confirmable. Forms that modify or delete data, or submit financial/legal transactions, must provide one of: undo mechanism, confirmation step before submission, or ability to review and correct before final submission.
Criterion 11.13 – Field purpose is programmatically determinable (autocomplete). Fields for personal data (name, email, phone, address, etc.) should use the appropriate autocomplete attribute value to help browsers and assistive technologies.
Step 3: Generate report
For each form, list all fields with their label status, grouping, validation attributes, and any issues found.
WCAG references: 9.1.3.1 (A), 9.1.3.5 (AA), 9.2.4.6 (AA), 9.3.3.1 (A), 9.3.3.2 (A), 9.3.3.3 (AA), 9.3.3.4 (AA), 9.4.1.2 (A)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: MTeelokee
- Source: MTeelokee/rgaa-accessibility-audit
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.