---
description: Focus order and focus styles accessibility standards per WCAG 2.4.7 Focus Visible, 1.4.11 Non-Text Contrast, 2.4.13 Focus Appearance, and 2.4.11 Focus Not Obscured requirements
globs: *.vue, *.jsx, *.tsx, *.html, *.php, *.js, *.ts, *.liquid, *.css, *.scss, *.sass, *.less
alwaysApply: true
---

# Focus Order and Focus Styles Accessibility Standards

Ensures proper focus order, tabindex usage, and focus indicators following WCAG 2.4.7 Focus Visible, 1.4.11 Non-Text Contrast, 2.4.13 Focus Appearance, and 2.4.11 Focus Not Obscured requirements.

<rule>
name: focus_order_and_styles_accessibility_standards
description: Enforce focus order and focus styles accessibility standards per WCAG requirements
filters:
  - type: file_extension
    pattern: "\\.(vue|jsx|tsx|html|liquid|php|js|ts|css|scss|sass|less)$"

actions:

- type: enforce
    conditions:

    # Positive tabindex values (should not be used)

  - pattern: "tabindex=\"[1-9]\""
        message: "Positive tabindex values create illogical focus order. Use DOM order instead or tabindex=\"0\" for custom focusable elements."

    # Missing focus styles (outline: 0 or outline: none)

  - pattern: "outline:\\s*0|outline:\\s*none"
        message: "Focus styles should not be removed. Use custom focus indicators that meet WCAG contrast requirements."

    # Focus styles with insufficient contrast (light colors)

  - pattern: "outline.*#[89abcdefABCDEF]{6}|outline.*#[cdefCDEF]{3,6}"
        message: "Light focus outline colors may not meet 3:1 contrast ratio requirement for UI component identification."

    # Missing focus-visible implementation

  - pattern: ":focus\\s*\\{"
        pattern_negate: ":focus-visible|:focus:not\\(:focus-visible\\)"
        message: "Consider implementing :focus-visible for better keyboard-only focus indication."

    # Focus styles that may be obscured

  - pattern: "outline-offset:\\s*-?0\\.?0*px|outline-offset:\\s*0"
        message: "Consider using positive outline-offset to prevent focus indicators from being obscured by adjacent elements."

    # Missing forced-colors media query for Windows High Contrast

  - pattern: "@media\\s*\\(forced-colors:\\s*active\\)"
        pattern_negate: "outline.*transparent"
        message: "Windows High Contrast Mode requires transparent outline for native focus appearance."

    # Custom focusable elements without proper tabindex

  - pattern: "<(div|span|button)[^>]*onclick|onkeydown|onkeypress"
        pattern_negate: "tabindex=\"[0-9]\"|role=\"button\"|role=\"link\""
        message: "Custom interactive elements should have tabindex=\"0\" or appropriate ARIA role for keyboard accessibility."

    # Focus styles with insufficient area

  - pattern: "outline-width:\\s*1px|outline-width:\\s*0\\.1rem"
        message: "Thin focus outlines may not meet WCAG 2.4.13 Focus Appearance requirements for minimum area."

    # Focus styles that blend with background

  - pattern: "outline.*rgba\\([^)]*0\\.1[^)]*\\)|outline.*rgba\\([^)]*0\\.2[^)]*\\)"
        message: "Very transparent focus outlines may not provide sufficient contrast for visibility."

    # Missing focus styles on interactive elements

  - pattern: "<(button|a|input|select|textarea)[^>]*>"
        pattern_negate: ":focus|:focus-visible|tabindex"
        message: "Interactive elements should have visible focus styles for keyboard navigation accessibility."

    # Dynamic content removal without focus management

  - pattern: "\\.remove\\(\\)|removeChild|innerHTML\\s*="
        pattern_negate: "focus\\(|focus\\(\\)"
        message: "When removing dynamic content, ensure proper focus management by restoring focus to a logical location."

- type: suggest
    message: |
      **WCAG Focus Order and Focus Styles Requirements:**

      **Focus Order Requirements:**

      **1. Logical DOM Order:**
  - **Default:** Focus order follows DOM element order
  - **Navigation:** Tab key moves forward, Shift+Tab moves backward
  - **Avoid:** Positive tabindex values (1, 2, 3, etc.)

      **2. Tabindex Usage:**

      ```html
      <!-- Good: Use DOM order (default) -->
      <button>First Button</button>
      <button>Second Button</button>
      <button>Third Button</button>

      <!-- Good: tabindex="0" for custom focusable elements -->
      <div role="button" tabindex="0" onclick="handleClick()">
        Custom Button
      </div>

      <!-- Good: tabindex="-1" for programmatic focus only -->
      <div id="target" tabindex="-1">Focus target</div>
      <button onclick="document.getElementById('target').focus()">
        Focus Target
      </button>

      <!-- Bad: Positive tabindex values -->
      <button tabindex="1">First</button>
      <button tabindex="3">Third</button>
      <button tabindex="2">Second</button>
      ```

      **Focus Styles Requirements:**

      **1. WCAG 2.4.7 Focus Visible (Level A):**
  - **Requirement:** Focus indicator must exist
  - **Purpose:** Keyboard users need visible focus indication

      **2. WCAG 1.4.11 Non-Text Contrast (Level AA):**
  - **Requirement:** Minimum 3:1 contrast ratio for UI components
  - **Applies to:** Focus indicators, borders, focus outlines

      **3. WCAG 2.4.13 Focus Appearance (Level AAA):**
  - **Requirement:** Minimum area and contrast for focus indicators
  - **Area:** Focus indicator should be clearly visible

      **4. WCAG 2.4.11 Focus Not Obscured (Level AA):**
  - **Requirement:** Focused element not hidden by other content
  - **Solution:** Use outline-offset to prevent overlap

      **Focus Styles Implementation:**

      **1. Basic Focus Styles:**

      ```css
      /* Good: Visible focus indicator */
      button:focus {
        outline: 2px solid #0056b3;
        outline-offset: 2px;
      }

      /* Good: Custom focus styles */
      .custom-button:focus {
        outline: 3px solid #dc3545;
        outline-offset: 3px;
        box-shadow: 0 0 8px rgba(220, 53, 69, 0.5);
      }
      ```

      **2. Focus-Visible Implementation:**

      ```css
      /* Default focus styles */
      button:focus {
        outline: 2px solid #0056b3;
        outline-offset: 2px;
      }

      /* Remove focus styles for mouse users */
      button:focus:not(:focus-visible) {
        outline: none;
        box-shadow: none;
      }

      /* Enhanced focus styles for keyboard users */
      button:focus-visible {
        outline: 3px solid #0056b3;
        outline-offset: 3px;
        box-shadow: 0 0 8px rgba(0, 86, 179, 0.5);
      }
      ```

      **3. High Contrast Focus Styles:**

      ```css
      /* Default focus styles */
      *:focus-visible {
        outline: 0.2rem solid rgba(var(--color-foreground-rgb), 0.5);
        outline-offset: -0.2rem;
        box-shadow: 0 0 0.2rem rgba(var(--color-foreground-rgb), 0.3);
      }

      /* Windows High Contrast Mode */
      @media (forced-colors: active) {
        *:focus {
          outline: 0.2rem solid transparent;
        }
      }
      ```

      **4. Component-Specific Focus Styles:**

      ```css
      /* Form inputs */
      input:focus-visible,
      textarea:focus-visible,
      select:focus-visible {
        outline: 2px solid #0056b3;
        outline-offset: 2px;
        border-color: #0056b3;
      }

      /* Links */
      a:focus-visible {
        outline: 2px solid #0056b3;
        outline-offset: 2px;
        text-decoration: underline;
      }

      /* Buttons */
      button:focus-visible {
        outline: 2px solid #0056b3;
        outline-offset: 2px;
        box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #0056b3;
      }
      ```

      **Focus Order Best Practices:**

      **1. Logical Content Flow:**

      ```html
      <!-- Good: Logical reading and focus order -->
      <header>
        <h1>Page Title</h1>
        <nav>
          <a href="/">Home</a>
          <a href="/about">About</a>
          <a href="/contact">Contact</a>
        </nav>
      </header>

      <main>
        <h2>Main Content</h2>
        <form>
          <label for="name">Name:</label>
          <input type="text" id="name">

          <label for="email">Email:</label>
          <input type="email" id="email">

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

      **2. Custom Interactive Elements:**

      ```html
      <!-- Good: Proper focusable custom element -->
      <div role="button"
           tabindex="0"
           onclick="handleClick()"
           onkeydown="handleKeydown(event)"
           class="custom-button">
        Custom Button
      </div>

      <!-- CSS for custom button focus -->
      .custom-button:focus-visible {
        outline: 2px solid #0056b3;
        outline-offset: 2px;
        background-color: #e7f3ff;
      }
      ```

      **3. Skip Links and Focus Management:**

      ```html
      <!-- Skip link for main content -->
      <a href="#main-content" class="skip-link">
        Skip to main content
      </a>

      <!-- Main content with id for focus target -->
      <main id="main-content" tabindex="-1">
        <h1>Main Content</h1>
        <!-- Content here -->
      </main>

      <!-- CSS for skip link -->
      .skip-link {
        position: absolute;
        top: -40px;
        left: 6px;
        background: #0056b3;
        color: #ffffff;
        padding: 8px;
        text-decoration: none;
        z-index: 1000;
      }

      .skip-link:focus {
        top: 6px;
        outline: 2px solid #ffffff;
        outline-offset: 2px;
      }
      ```

      **Focus Styles Guidelines:**

      **1. Contrast Requirements:**
  - **Minimum:** 3:1 contrast ratio for focus indicators
  - **Recommended:** 4.5:1 or higher for better visibility
  - **Test:** Against adjacent colors and backgrounds

      **2. Size and Visibility:**
  - **Outline width:** Minimum 2px for visibility
  - **Outline offset:** Use positive values to prevent overlap
  - **Area:** Focus indicator should be clearly visible

      **3. Color Selection:**

      ```css
      /* High contrast focus colors */
      :focus-visible {
        outline: 2px solid #0056b3; /* Blue - high contrast */
        outline-offset: 2px;
      }

      /* Alternative high contrast colors */
      :focus-visible {
        outline: 2px solid #dc3545; /* Red - high contrast */
        outline-offset: 2px;
      }

      :focus-visible {
        outline: 2px solid #198754; /* Green - high contrast */
        outline-offset: 2px;
      }
      ```

      **4. Focus Not Obscured:**

      ```css
      /* Prevent focus indicator overlap */
      button:focus-visible {
        outline: 2px solid #0056b3;
        outline-offset: 3px; /* Space between element and outline */
      }

      /* Alternative: Use box-shadow for non-overlapping focus */
      button:focus-visible {
        outline: none;
        box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #0056b3;
      }
      ```

      **5. Dynamic Content Focus Management:**

      ```javascript
      // Focus management best practices
      class FocusManager {
        constructor() {
          this.focusHistory = [];
          this.currentFocus = null;
        }

        // Save focus before making changes
        saveFocus() {
          this.currentFocus = document.activeElement;
          this.focusHistory.push(this.currentFocus);
        }

        // Restore focus to previous location
        restoreFocus() {
          if (this.focusHistory.length > 0) {
            const previousFocus = this.focusHistory.pop();
            if (previousFocus && document.contains(previousFocus)) {
              previousFocus.focus();
            }
          }
        }

        // Focus first interactive element in new content
        focusNewContent(container) {
          const focusableElements = container.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
          );

          if (focusableElements.length > 0) {
            focusableElements[0].focus();
          }
        }

        // Find logical focus target when content is removed
        findLogicalFocusTarget(removedElement, container) {
          // Try to focus next sibling element
          const nextSibling = removedElement.nextElementSibling;
          if (nextSibling) {
            const focusableElement = nextSibling.querySelector(
              'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
            );
            if (focusableElement) {
              return focusableElement;
            }
          }

          // Try to focus previous sibling element
          const prevSibling = removedElement.previousElementSibling;
          if (prevSibling) {
            const focusableElement = prevSibling.querySelector(
              'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
            );
            if (focusableElement) {
              return focusableElement;
            }
          }

          // Fallback to container or trigger button
          return container.querySelector('button, [href], input') ||
                 document.querySelector('[aria-haspopup="dialog"]');
        }
      }

      // Usage example
      const focusManager = new FocusManager();

      function addContent() {
        focusManager.saveFocus();

        // Add new content
        const newContent = createNewContent();
        container.appendChild(newContent);

        // Focus first interactive element
        focusManager.focusNewContent(newContent);
      }

      function removeContent(element) {
        const container = element.parentElement;

        // Find logical focus target before removing
        const focusTarget = focusManager.findLogicalFocusTarget(element, container);

        // Remove the element
        element.remove();

        // Focus the logical target
        if (focusTarget) {
          focusTarget.focus();
        }
      }
      ```

      **Testing and Validation:**

      **1. Keyboard Navigation Testing:**
  - Navigate using Tab and Shift+Tab
  - Verify focus order is logical
  - Check that focus indicators are visible
  - Test with different focus styles

      **2. Contrast Testing:**
  - Use browser dev tools for contrast ratios
  - Test against different backgrounds
  - Verify 3:1 minimum contrast requirement
  - Test with color blindness simulators

      **3. Focus Visibility Testing:**
  - Test with screen readers
  - Verify focus indicators are not obscured
  - Check focus styles in different themes
  - Test Windows High Contrast Mode

      **4. Dynamic Content Focus Testing:**
  - Test focus management when adding new content
  - Verify focus moves to first interactive element in new content
  - Test focus restoration when removing content
  - Ensure focus returns to logical location
  - Test focus management with multiple dynamic elements
  - Verify focus history is maintained correctly

      **Common Mistakes to Avoid:**

      **1. Focus Order Issues:**
  - Using positive tabindex values
  - Skipping focusable elements
  - Illogical DOM structure
  - Missing focusable elements

      **2. Focus Style Problems:**
  - Removing focus styles with `outline: none`
  - Insufficient contrast ratios
  - Focus indicators that are too small
  - Focus styles that blend with background

      **3. Implementation Issues:**
  - Missing focus-visible implementation
  - Not testing with keyboard navigation
  - Ignoring Windows High Contrast Mode
  - Focus indicators that are obscured

      **4. Accessibility Violations:**
  - No visible focus indicators
  - Focus order that doesn't match reading order
  - Custom elements without proper focus management
  - Missing keyboard event handlers

      **Advanced Focus Management:**

      **1. Programmatic Focus Control:**

      ```javascript
      // Focus management for modals
      function openModal() {
        const modal = document.getElementById('modal');
        const closeButton = document.getElementById('close-modal');

        modal.style.display = 'block';
        closeButton.focus(); // Focus close button when modal opens
      }

      // Trap focus within modal
      function trapFocus(modal) {
        const focusableElements = modal.querySelectorAll(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );

        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];

        // Handle Tab key
        document.addEventListener('keydown', function(e) {
          if (e.key === 'Tab') {
            if (e.shiftKey) {
              if (document.activeElement === firstElement) {
                lastElement.focus();
                e.preventDefault();
              }
            } else {
              if (document.activeElement === lastElement) {
                firstElement.focus();
                e.preventDefault();
              }
            }
          }
        });
      }
      ```

      **2. Dynamic Focus Management:**

      ```javascript
      // Focus restoration after dynamic content
      function loadContent() {
        const container = document.getElementById('content');
        const previousFocus = document.activeElement;

        // Load new content
        container.innerHTML = newContent;

        // Restore focus or set to first focusable element
        const firstFocusable = container.querySelector(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );

        if (firstFocusable) {
          firstFocusable.focus();
        } else if (previousFocus) {
          previousFocus.focus();
        }
      }
      ```

      **3. Focus Indicators for Complex Components:**

      ```css
      /* Multi-state focus indicators */
      .accordion-item:focus-visible {
        outline: 2px solid #0056b3;
        outline-offset: 2px;
      }

      .accordion-item[aria-expanded="true"]:focus-visible {
        outline-color: #198754; /* Different color for expanded state */
      }

      /* Focus indicators for different interaction states */
      .interactive-element:focus-visible {
        outline: 2px solid #0056b3;
        outline-offset: 2px;
      }

      .interactive-element:hover:focus-visible {
        outline-color: #004085; /* Darker on hover + focus */
      }

      .interactive-element:active:focus-visible {
        outline-color: #002752; /* Even darker when active */
      }
      ```

      **4. Dynamic Content Focus Management:**

      ```javascript
      // When adding content: focus first interactive element
      function addDynamicContent() {
        const container = document.getElementById('content');
        const newElement = document.createElement('div');
        newElement.innerHTML = `
          <h3>New Content</h3>
          <button class="btn">Action Button</button>
        `;

        container.appendChild(newElement);

        // Focus the first focusable element in new content
        const firstFocusable = newElement.querySelector('button');
        if (firstFocusable) {
          firstFocusable.focus();
        }
      }

      // When removing content: restore focus to logical location
      function removeDynamicContent(element) {
        const triggerButton = document.querySelector('[onclick="addDynamicContent()"]');

        // Store reference to element being removed
        const removedElement = element;

        // Remove the element
        element.remove();

        // Restore focus to the trigger button
        if (triggerButton) {
          triggerButton.focus();
        }
      }

      // Advanced focus management for multiple elements
      function removeSpecificElement(element, elementType) {
        const container = element.parentElement;
        const triggerButton = document.querySelector(`[onclick="add${elementType}()"]`);

        // Find the next logical focus target
        let nextFocusTarget = triggerButton;

        // If there are other elements, focus the next one
        const remainingElements = container.querySelectorAll(`.${elementType.toLowerCase()}`);
        if (remainingElements.length > 0) {
          const targetElement = remainingElements[0];
          const focusableElement = targetElement.querySelector('button, a, input');
          if (focusableElement) {
            nextFocusTarget = focusableElement;
          }
        }

        // Remove the element
        element.remove();

        // Set focus to the appropriate target
        nextFocusTarget.focus();
      }
      ```

metadata:
  priority: high
  version: 1.0
</rule>
description:
globs:
alwaysApply: false
---
