---
description: Form component accessibility standards and WCAG compliance for form inputs, labels, instructions, and error handling
globs: *.vue, *.jsx, *.tsx, *.html, *.php, *.js, *.ts, *.liquid
alwaysApply: true
---

# Form Accessibility Standards

Ensures form components follow WCAG compliance and provide proper accessibility for all users including screen reader users and keyboard-only users.

<rule>
name: form_accessibility_standards
description: Enforce form component accessibility standards and WCAG compliance for form inputs, labels, instructions, and error handling
filters:
  - type: file_extension
    pattern: "\\.(vue|jsx|tsx|html|liquid|php|js|ts)$"

actions:
  - type: enforce
    conditions:
      # Missing label association for form inputs
      - pattern: "(?i)<(input|textarea|select)[^>]*>"
        pattern_negate: "(<label[^>]*for|id.*for|aria-label|aria-labelledby|title=)"
        message: "Form inputs must have programmatically associated labels via label/for, aria-label, aria-labelledby, or title attributes."

      # Empty or meaningless labels
      - pattern: "(?i)<label[^>]*>\\s*(?:label|input|field|required)\\s*</label>"
        message: "Form labels must contain meaningful text that describes the input purpose, not generic terms."

      # Placeholder as only label
      - pattern: "(?i)<input[^>]*placeholder=\"[^\"]+\"[^>]*>"
        pattern_negate: "(<label|aria-label|aria-labelledby|title=)"
        message: "Placeholder text cannot be the only method of providing a label. Add a proper label element or aria-label."

      # Missing required field indicators
      - pattern: "(?i)<(input|textarea|select)[^>]*required[^>]*>"
        pattern_negate: "(data-required|aria-required=\"true\"|\\*|aria-describedby)"
        message: "Required fields should use data-required='true' instead of native required attribute, with visual indicators and aria-required='true' for screen readers."

      # Missing fieldset for grouped inputs
      - pattern: "(?i)<input[^>]*type=\"(radio|checkbox)\"[^>]*name=\"[^\"]+\"[^>]*>"
        pattern_negate: "(<fieldset|<div[^>]*role=\"group\")"
        message: "Radio button and checkbox groups should be wrapped in fieldset or have role='group' for proper grouping."

      # Missing legend for fieldset
      - pattern: "(?i)<fieldset[^>]*>"
        pattern_negate: "<legend"
        message: "Fieldset elements must have legend elements to provide context for the group."

      # Missing input purpose identification
      - pattern: "(?i)<input[^>]*type=\"(text|email|tel|url|password)\"[^>]*>"
        pattern_negate: "(autocomplete|aria-describedby|aria-label|placeholder)"
        message: "Text inputs should have autocomplete attributes or other methods to identify their purpose for personal data collection."

      # Missing error association
      - pattern: "(?i)<[^>]*aria-invalid=\"true\"[^>]*>"
        pattern_negate: "(aria-describedby|aria-errormessage)"
        message: "Inputs with aria-invalid='true' should have error messages associated via aria-describedby or aria-errormessage."

      # Missing error message visibility
      - pattern: "(?i)<[^>]*class=\"[^\"]*(?:error|invalid)[^\"]*\"[^>]*>"
        pattern_negate: "(display.*none|visibility.*hidden|hidden|aria-hidden=\"true\")"
        message: "Error messages should be visible to users and not hidden with CSS or aria-hidden."

      # Missing focus management for error summaries
      - pattern: "(?i)<(div|section)[^>]*(?:error.*summary|summary.*error)[^>]*>"
        pattern_negate: "(tabindex|focus|scrollIntoView)"
        message: "Error summary containers should implement focus management to shift focus to the error banner heading when errors appear, improving user experience and accessibility."

      # Missing form instructions association
      - pattern: "(?i)<(div|p)[^>]*(?:instruction|help|hint)[^>]*>"
        pattern_negate: "(aria-describedby|aria-labelledby|id=)"
        message: "Form instructions should be programmatically associated with their corresponding inputs via aria-describedby."

      # Missing success message
      - pattern: "(?i)<form[^>]*>.*</form>"
        pattern_negate: "(role=\"alert\"|aria-live|success|confirmation)"
        message: "Forms should provide success confirmation messages, especially for critical operations like financial transactions."

      # Missing focus management for success messages
      - pattern: "(?i)<(div|section)[^>]*(?:success|confirmation)[^>]*>"
        pattern_negate: "(tabindex|focus|scrollIntoView)"
        message: "Success message containers should implement focus management to shift focus to the success banner heading when messages appear, improving user experience and accessibility."

      # Missing focusable headings for error/success messages
      - pattern: "(?i)<(h2|h3)[^>]*(?:error|success|confirmation)[^>]*>"
        pattern_negate: "tabindex=\"-1\""
        message: "Error and success message headings should have tabindex='-1' to make them programmatically focusable for focus management."

      # Missing time limit controls
      - pattern: "(?i)<form[^>]*>.*(?:time.*limit|session.*expir|expir.*time)"
        pattern_negate: "(extend|turn.*off|adjust|customize|20.*hour)"
        message: "Forms with time limits must provide options to extend, turn off, or adjust the time limit, or allow at least 20 hours."

      # Missing error prevention for critical forms
      - pattern: "(?i)<form[^>]*(?:legal|financial|transaction|payment|commitment)[^>]*>"
        pattern_negate: "(confirm|review|reversible|check.*error)"
        message: "Critical forms (legal/financial) must implement error prevention techniques like confirmation, review, or reversibility."

      # Missing disabled field alternatives
      - pattern: "(?i)<input[^>]*disabled[^>]*>"
        pattern_negate: "(aria-describedby|aria-label|title=|role=\"text\")"
        message: "Disabled fields that are essential to understanding content must have alternative ways to communicate their information."

      # Missing data input restrictions communication
      - pattern: "(?i)<input[^>]*(?:maxlength|pattern|min|max)[^>]*>"
        pattern_negate: "(aria-describedby|title=|placeholder|aria-label)"
        message: "Inputs with restrictions (maxlength, pattern, min, max) should communicate these restrictions in labels or instructions."

      # Help text using div instead of semantic small element
      - pattern: "(?i)<div[^>]*class=\"[^\"]*help-text[^\"]*\"[^>]*>"
        pattern_negate: "<small"
        message: "Help text should use the semantic small element instead of div for better HTML5 compliance and accessibility."

      # Form sections nested within main element
      - pattern: "(?i)<main[^>]*>.*<section[^>]*class=\"[^\"]*form-section[^\"]*\"[^>]*>"
        message: "Form sections should be direct children of the container, not nested within the main element. Use main only for the form itself."

      # Missing proper section structure
      - pattern: "(?i)<section[^>]*class=\"[^\"]*form-section[^\"]*\"[^>]*>"
        pattern_negate: "(</section>|aria-labelledby)"
        message: "Form sections must be properly closed with </section> tags and have aria-labelledby for accessibility."

      # Improper section nesting within main element
      - pattern: "(?i)<main[^>]*>.*<section[^>]*class=\"[^\"]*form-section[^\"]*\"[^>]*>.*</section>"
        message: "Form sections should not be nested within the main element. Move sections outside main and use main only for the form itself."

      # Missing container wrapper for form sections
      - pattern: "(?i)<section[^>]*class=\"[^\"]*form-section[^\"]*\"[^>]*>"
        pattern_negate: "<div[^>]*class=\"[^\"]*container[^\"]*\"[^>]*>"
        message: "Form sections should be wrapped in a container div for proper organization and styling."

      # Missing proper heading structure in form sections
      - pattern: "(?i)<section[^>]*class=\"[^\"]*form-section[^\"]*\"[^>]*>"
        pattern_negate: "<h[1-6][^>]*>"
        message: "Form sections must contain proper heading elements (h1-h6) for accessibility and content structure."

      # Missing redundant entry prevention
      - pattern: "(?i)<form[^>]*>.*<input[^>]*name=\"(email|password|confirm)[^\"]*\"[^>]*>"
        pattern_negate: "(autocomplete|auto.*populate|select.*previous)"
        message: "Forms requiring redundant information entry should provide auto-population or selection of previously entered data."

      # Missing keyboard navigation support
      - pattern: "(?i)<(input|textarea|select|button)[^>]*>"
        pattern_negate: "(tabindex|onKeyDown|onkeydown|@keydown|v-on:keydown)"
        message: "Form elements should support keyboard navigation and not interfere with tab order."

      # Missing focus indicators
      - pattern: "(?i)<(input|textarea|select|button)[^>]*>"
        pattern_negate: "(focus|:focus|outline|box-shadow)"
        message: "Form elements must have visible focus indicators for keyboard navigation."

      # Missing focus indicators for error/success message headings
      - pattern: "(?i)<(h2|h3)[^>]*tabindex=\"-1\"[^>]*>"
        pattern_negate: "(focus|:focus|outline|box-shadow)"
        message: "Focusable error and success message headings must have visible focus indicators for keyboard navigation."

      # Help text with insufficient color contrast
      - pattern: "(?i)<[^>]*class=\"[^\"]*help-text[^\"]*\"[^>]*>"
        pattern_negate: "(color:\\s*#[0-4][0-9a-fA-F]{5}|color:\\s*#[5-9a-fA-F][0-9a-fA-F]{5})"
        message: "Help text must have sufficient color contrast (minimum 4.5:1 for normal text, 7:1 recommended). Use darker colors like #495057 for better accessibility."

      # Missing form validation feedback
      - pattern: "(?i)<form[^>]*>"
        pattern_negate: "(validate|check.*error|aria-invalid|aria-describedby)"
        message: "Forms should provide validation feedback and error checking for user input."

  - type: suggest
    message: |
      **Form Accessibility Best Practices:**

      **Label Requirements:**
      - **Programmatic Association:** Labels MUST be programmatically associated with inputs
      - **Meaningful Text:** Labels MUST contain meaningful, descriptive text
      - **Visible Labels:** Labels MUST be visible to users
      - **No Sensory Dependencies:** Labels MUST NOT rely solely on visual characteristics
      - **Icon Labels:** Icons can be used as visual labels if meaning is self-evident AND semantic label is provided

      **Label Implementation Patterns:**

      **Basic Label Association:**
      ```html
      <!-- Good: Label with for attribute -->
      <label for="username">Username</label>
      <input type="text" id="username" name="username">

      <!-- Good: aria-label for simple cases -->
      <input type="text" aria-label="Search products" placeholder="Enter search term">

      <!-- Good: aria-labelledby for complex labels -->
      <h3 id="address-heading">Shipping Address</h3>
      <input type="text" aria-labelledby="address-heading" name="street">
      ```

      **Required Field Indicators:**
      ```html
      <label for="email">
        Email Address <span class="required" aria-label="required">*</span>
      </label>
      <input type="email"
             id="email"
             name="email"
             required
             aria-required="true"
             aria-describedby="email-help">
      <small id="email-help" class="help-text">
        We'll use this to send you order confirmations
      </small>
      ```

      **Input Groups with Fieldset:**
      ```html
      <fieldset>
        <legend>Contact Preferences</legend>
        <label for="email-notifications">
          <input type="checkbox" id="email-notifications" name="notifications[]" value="email">
          Email Notifications
        </label>
        <label for="sms-notifications">
          <input type="checkbox" id="sms-notifications" name="notifications[]" value="sms">
          SMS Notifications
        </label>
      </fieldset>
      ```

      **Input Purpose Identification:**
      ```html
      <!-- Personal data inputs should have autocomplete -->
      <label for="full-name">Full Name</label>
      <input type="text"
             id="full-name"
             name="fullName"
             autocomplete="name">

      <label for="email-address">Email Address</label>
      <input type="email"
             id="email-address"
             name="email"
             autocomplete="email">

      <label for="phone-number">Phone Number</label>
      <input type="tel"
             id="phone-number"
             name="phone"
             autocomplete="tel">
      ```

      **Error Handling and Validation:**

      **Error Message Association:**
      ```html
      <label for="password">Password</label>
      <input type="password"
             id="password"
             name="password"
             aria-describedby="password-requirements password-error"
             aria-invalid="false">

      <small id="password-requirements" class="help-text">
        Password must be at least 8 characters with uppercase, lowercase, and number
      </small>

      <div id="password-error" class="error-message" role="alert" hidden>
        Password does not meet requirements
      </div>
      ```

      **Real-time Validation:**
      ```javascript
      function validatePassword(input) {
        const password = input.value;
        const requirements = document.getElementById('password-requirements');
        const error = document.getElementById('password-error');

        // Check requirements
        const hasLength = password.length >= 8;
        const hasUpper = /[A-Z]/.test(password);
        const hasLower = /[a-z]/.test(password);
        const hasNumber = /\d/.test(password);

        if (hasLength && hasUpper && hasLower && hasNumber) {
          input.setAttribute('aria-invalid', 'false');
          error.hidden = true;
          requirements.className = 'help-text valid';
        } else {
          input.setAttribute('aria-invalid', 'true');
          error.hidden = false;
          requirements.className = 'help-text invalid';
        }
      }
      ```

      **Form Instructions and Help:**

      **Input Instructions:**
      ```html
      <label for="zip-code">ZIP Code</label>
      <input type="text"
             id="zip-code"
             name="zipCode"
             maxlength="5"
             pattern="[0-9]{5}"
             aria-describedby="zip-help">
      <div id="zip-help" class="help-text">
        Enter 5-digit ZIP code (e.g., 12345)
      </div>
      ```

      **Form-level Instructions:**
      ```html
      <form aria-describedby="form-instructions">
        <div id="form-instructions" class="form-instructions">
          <p>All fields marked with * are required. Please review your information before submitting.</p>
        </div>

        <!-- Form fields here -->
      </form>
      ```

      **Success and Error Messages:**

      **Success Confirmation:**
      ```html
      <div id="success-message"
           class="success-message"
           role="alert"
           aria-live="polite"
           hidden>
        Your order has been successfully submitted! Order #12345
      </div>
      ```

      **Error Summary:**
      ```html
      <div id="error-summary"
           class="error-summary"
           role="alert"
           aria-live="assertive"
           hidden>
        <h2 tabindex="-1">Please correct the following errors:</h2>
        <ul>
          <li><a href="#email">Email address is required</a></li>
          <li><a href="#phone">Phone number format is invalid</a></li>
        </ul>
      </div>
      ```

            **Focus Management for Error and Success Messages:**
      ```javascript
      function showErrorSummary(errors) {
        const errorSummary = document.getElementById('error-summary');
        const errorHeading = errorSummary.querySelector('h2');

        // Show error summary
        errorSummary.classList.add('show');

        // Shift focus to error banner heading for better user experience
        // Use requestAnimationFrame to ensure the element is fully visible before focusing
        requestAnimationFrame(() => {
          if (errorHeading) {
            errorHeading.focus();
            errorHeading.scrollIntoView({ behavior: 'smooth', block: 'start' });
          }
        });
      }

      function showSuccessMessage() {
        const successMessage = document.getElementById('success-message');
        const successHeading = successMessage.querySelector('h3');

        successMessage.classList.add('show');

        // Shift focus to success message heading for better user experience
        // Use requestAnimationFrame to ensure the element is fully visible before focusing
        requestAnimationFrame(() => {
          if (successHeading) {
            successHeading.focus();
            successHeading.scrollIntoView({ behavior: 'smooth', block: 'start' });
          }
        });
      }
      ```

      **Focus Management Benefits:**
      - **User Experience:** Places cursor at beginning of error information
      - **Accessibility:** Screen readers announce error content from the start
      - **Navigation:** Users can easily navigate through error list
      - **Context:** Provides clear starting point for error resolution

      **Implementation Considerations:**
      - **Timing:** Use requestAnimationFrame to ensure elements are fully visible before focusing
      - **Fallback:** Provide fallback focus management if headings aren't found
      - **Smooth Scrolling:** Use smooth scrolling for better user experience
      - **Focus Indicators:** Ensure focusable headings have visible focus indicators
      - **Browser Behavior:** Prevent default browser focus behavior on form submission

      **CSS for Focusable Error and Success Message Headings:**
      ```css
      /* Make error and success message headings focusable */
      .error-summary h2:focus,
      .success-message h3:focus {
        outline: 3px solid #0056b3;
        outline-offset: 2px;
        border-radius: 4px;
      }

      /* High contrast mode support */
      @media (prefers-contrast: more) {
        .error-summary h2:focus,
        .success-message h3:focus {
          outline: 3px solid #000000;
        }
      }
      ```

      **Form Structure and Semantic HTML:**

      **Proper Section Organization:**
      ```html
      <div class="container">
        <!-- Form Instructions (standalone section) -->
        <section class="form-section" aria-labelledby="instructions-heading">
          <h2 id="instructions-heading">Form Instructions</h2>
          <small id="form-instructions" class="help-text">
            <!-- Instructions content -->
          </small>
        </section>

        <!-- Time Limit Warning (standalone div) -->
        <div class="time-limit-warning" role="alert" aria-live="polite">
          <!-- Timer content -->
        </div>

        <!-- Error Summary (standalone div) -->
        <div id="error-summary" class="error-summary" role="alert" aria-live="assertive">
          <!-- Error content -->
        </div>

        <!-- Main Form (contained in main landmark) -->
        <main role="main" aria-labelledby="main-heading">
          <h2 id="main-heading" class="sr-only">Form Content</h2>
          <form id="main-form" onsubmit="handleFormSubmit(event)">
            <!-- Form sections within the form -->
            <section class="form-section" aria-labelledby="personal-info-heading">
              <h2 id="personal-info-heading">Personal Information</h2>
              <!-- Form fields -->
            </section>
          </form>
        </main>

        <!-- Additional sections after the form -->
        <section class="form-section" aria-labelledby="disabled-field-heading">
          <h2 id="disabled-field-heading">Disabled Field Example</h2>
          <!-- Content -->
        </section>
      </div>
      ```

      **Help Text Semantic Elements:**
      ```html
      <!-- Good: Use small element for help text -->
      <label for="email">Email Address</label>
      <input type="email" id="email" name="email" aria-describedby="email-help">
      <small id="email-help" class="help-text">
        We'll use this to send you order confirmations
      </small>

      <!-- Avoid: Using div for help text -->
      <div id="email-help" class="help-text">
        We'll use this to send you order confirmations
      </div>
      ```

      **Help Text Color Contrast Requirements:**
      ```css
      .help-text {
        font-size: 0.875rem;
        color: #495057; /* 7.0:1 contrast ratio on white */
        margin-top: 4px;
        margin-bottom: 8px;
        display: block;
        line-height: 1.4;
      }

      /* High contrast mode support */
      @media (prefers-contrast: more) {
        .help-text,
        small.help-text {
          color: #000000; /* Maximum contrast */
        }
      }
      ```

      **Structural Guidelines:**
      - **Container Organization:** All sections should be direct children of the container div
      - **Main Landmark:** Use main element only for the form itself, not for wrapper content
      - **Section Semantics:** Use section elements for major content areas with proper headings
      - **Help Text Elements:** Use small elements for supplementary text and instructions
      - **Color Contrast:** Ensure help text meets minimum 4.5:1 contrast ratio (7.0:1 recommended)
      - **Proper Nesting:** Avoid nesting sections within other sections or main elements

      **Time Limits and Error Prevention:**

      **Time Limit Controls:**
      ```html
      <div class="time-limit-warning" role="alert" aria-live="polite">
        <p>Your session will expire in <span id="time-remaining">14:30</span> minutes</p>
        <button type="button" onclick="extendSession()">Extend Session</button>
        <button type="button" onclick="turnOffTimer()">Turn Off Timer</button>
      </div>
      ```

      **Critical Form Confirmation:**
      ```html
      <form onsubmit="return confirmSubmission()">
        <!-- Form fields -->

        <div class="confirmation-section">
          <h3>Review Your Information</h3>
          <div id="order-summary" class="order-summary">
            <!-- Order summary content -->
          </div>

          <label for="confirm-checkbox">
            <input type="checkbox" id="confirm-checkbox" required>
            I confirm that all information is correct and I agree to the terms
          </label>
        </div>

        <button type="submit">Submit Order</button>
      </form>
      ```

      **JavaScript for Form Accessibility:**

      **Form Validation:**
      ```javascript
      function validateForm() {
        const form = document.querySelector('form');
        const inputs = form.querySelectorAll('input, textarea, select');
        let hasErrors = false;

        inputs.forEach(input => {
          if (input.hasAttribute('required') && !input.value.trim()) {
            showError(input, 'This field is required');
            hasErrors = true;
          } else if (input.getAttribute('aria-invalid') === 'true') {
            hasErrors = true;
          }
        });

        if (hasErrors) {
          showErrorSummary();
          return false;
        }

        return true;
      }

      function showError(input, message) {
        const errorId = input.id + '-error';
        let errorElement = document.getElementById(errorId);

        if (!errorElement) {
          errorElement = document.createElement('div');
          errorElement.id = errorId;
          errorElement.className = 'error-message';
          errorElement.role = 'alert';
          input.parentNode.appendChild(errorElement);
        }

        errorElement.textContent = message;
        errorElement.hidden = false;
        input.setAttribute('aria-invalid', 'true');
        input.setAttribute('aria-describedby',
          input.getAttribute('aria-describedby') + ' ' + errorId);
      }
      ```

      **Focus Management:**
      ```javascript
      function focusFirstError() {
        const firstError = document.querySelector('[aria-invalid="true"]');
        if (firstError) {
          firstError.focus();
          firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
      }

      function focusSuccessMessage() {
        const successMessage = document.getElementById('success-message');
        if (successMessage) {
          successMessage.hidden = false;
          successMessage.focus();
        }
      }
      ```

      **CSS for Accessibility:**

      **Focus Indicators:**
      ```css
      /* High contrast focus indicators */
      input:focus,
      textarea:focus,
      select:focus,
      button:focus {
        outline: 3px solid #0056b3;
        outline-offset: 2px;
        border-color: #0056b3;
      }

      /* Error states */
      input[aria-invalid="true"] {
        border-color: #dc3545;
        border-width: 2px;
      }

      /* Required field indicators */
      .required {
        color: #dc3545;
        font-weight: bold;
      }

      /* Help text styling */
      .help-text {
        font-size: 0.875rem;
        color: #6c757d;
        margin-top: 0.25rem;
      }

      /* Error message styling */
      .error-message {
        color: #dc3545;
        font-size: 0.875rem;
        margin-top: 0.25rem;
        font-weight: 500;
      }

      /* Success message styling */
      .success-message {
        color: #198754;
        background-color: #d1e7dd;
        border: 1px solid #badbcc;
        padding: 1rem;
        border-radius: 0.375rem;
        margin: 1rem 0;
      }
      ```

      **Testing and Validation:**

      **Accessibility Checklist:**
      - All form inputs have associated labels
      - Required fields are clearly indicated
      - Error messages are associated with inputs
      - Form can be completed with keyboard only
      - Focus indicators are visible and clear
      - Screen readers can access all form content
      - Time limits have appropriate controls
      - Critical forms have error prevention
      - Success/error messages are announced
      - Form instructions are accessible

      **Common Mistakes to Avoid:**
      - Using placeholder text as the only label
      - Missing required field indicators
      - Not associating error messages with inputs
      - Missing form-level instructions
      - No success confirmation messages
      - Insufficient time limit controls
      - Missing error prevention for critical forms
      - Poor focus management
      - Inaccessible validation feedback
      - Missing input purpose identification

metadata:
  priority: high
  version: 1.0
</rule>
