AgentStack
SKILL verified MIT Self-run

Rgaa Forms

skill-mteelokee-rgaa-accessibility-audit-rgaa-forms · by MTeelokee

Audit a web page for RGAA v4.1.2 Theme 11 – Forms accessibility compliance. Checks that form fields have labels, labels are relevant and visible, labels are properly associated, related fields are grouped with fieldset/legend, select options are grouped, buttons have relevant names, input validation is accessible and helpful, error suggestions are provided, and autocomplete attributes are used. U…

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-mteelokee-rgaa-accessibility-audit-rgaa-forms

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

Are you the author of Rgaa Forms? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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 required or aria-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 via aria-describedby or aria-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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.