---
description: Flip Card component accessibility compliance pattern
globs: *.vue, *.jsx, *.tsx, *.html, *.php, *.js, *.ts, *.liquid
alwaysApply: false
---
# Flip Card Component Accessibility Standards

Ensures flip card components follow WCAG compliance and provide proper state management for screen reader users.

<rule>
name: flip_card_accessibility_standards
description: Enforce flip card component accessibility standards and proper state management
filters:
  - type: file_extension
    pattern: "\\.(vue|jsx|tsx|html|liquid|php|js|ts)$"

actions:
  - type: enforce
    conditions:
      # Flip button requirement
      - pattern: "(?i)<(div|section)[^>]*(?:card|flip)[^>]*>"
        pattern_negate: "<button[^>]*aria-pressed"
        message: "Flip cards must contain a button with aria-pressed attribute to control card state."

      # aria-pressed attribute requirement
      - pattern: "(?i)<button[^>]*(?:flip|card)[^>]*>"
        pattern_negate: "aria-pressed=\"(true|false)\""
        message: "Flip card buttons must have aria-pressed attribute set to 'true' or 'false'."

      # Card front/back structure requirement
      - pattern: "(?i)<(div|section)[^>]*(?:card|flip)[^>]*>"
        pattern_negate: "(card--front|card--back|front|back)"
        message: "Flip cards must have both front and back content sections for proper structure."

      # Unique accessible name requirement
      - pattern: "(?i)<button[^>]*aria-pressed[^>]*>"
        pattern_negate: "(aria-label|aria-labelledby|>.*[A-Za-z]{10,})"
        message: "Flip card buttons must have unique, descriptive accessible names that reference visible card content."

      # Keyboard focus indicator requirement
      - pattern: "(?i)<(div|section)[^>]*(?:card|flip)[^>]*>"
        pattern_negate: "(focus|:focus|focus-visible|:focus-visible)"
        message: "Flip card containers should have visible keyboard focus indicators when the flip button is focused."

      # Content visibility management
      - pattern: "(?i)aria-pressed=\"(true|false)\""
        pattern_negate: "(visibility.*hidden|display.*none|hidden)"
        message: "Use aria-pressed state to control content visibility - false shows front, true shows back. Prefer visibility: hidden/visible for smooth animations."

      # Missing flip button type
      - pattern: "(?i)<button[^>]*(?:flip|card)[^>]*>"
        pattern_negate: "type=\"button\""
        message: "Flip card buttons should have type='button' to prevent form submission behavior."

      # Incomplete card structure
      - pattern: "(?i)<div[^>]*class=\"card[^>]*>"
        pattern_negate: "(card--front.*card--back|card--back.*card--front)"
        message: "Flip cards must contain both front and back content sections for proper functionality."

  - type: suggest
    message: |
      **Flip Card Component Accessibility Best Practices:**

      **Required ARIA Attributes:**
      - **aria-pressed:** 'false' shows front content, 'true' shows back content
      - **type="button":** Prevents form submission behavior
      - **Unique accessible name:** Should reference visible card content

      **DOM Structure Requirements:**
      - Card container with front and back content sections
      - Flip button positioned between or adjacent to content sections
      - Use CSS display: none to hide non-visible content
      - Maintain logical reading order in the DOM

      **Content Visibility Management:**
      - **aria-pressed="false":** Show front content, hide back content
      - **aria-pressed="true":** Show back content, hide front content
      - Use CSS display property for smooth transitions
      - Ensure only one side is visible at a time

      **Keyboard and Focus Requirements:**
      - **Enter:** Toggle card state
      - **Space:** Toggle card state
      - **Tab:** Move focus to next focusable element
      - **Shift+Tab:** Move focus to previous focusable element
      - **Focus indicator:** Should wrap the card content container
      - **Hover state:** Blue border matching focus indicator for visual consistency

      **Implementation Example:**
      ```html
      <!-- ✅ Correct: Proper flip card structure -->
      <div class="card">
        <div class="card--front">
          <h3>Card Title</h3>
          <img src="front-image.jpg" alt="Front view of the product">
        </div>

        <button type="button"
                class="flip-button"
                aria-pressed="false"
                aria-label="More about Card Title">
        </button>

        <div class="card--back">
          <p class="card--tagline">Inspiring content</p>
          <img src="back-image-1.jpg" alt="Product detail view 1">
          <img src="back-image-2.jpg" alt="Product detail view 2">
          <img src="back-image-3.jpg" alt="Product detail view 3">
          <p>More detailed content about the product</p>
          <a href="/product-details">
            <img src="link-icon.svg" alt="View full product details">
          </a>
        </div>
      </div>
      ```

      **CSS Implementation:**
      ```css
      .card {
        position: relative;
        perspective: 1000px;
        /* Focus indicator for keyboard navigation */
        outline: 2px solid transparent;
        outline-offset: 2px;
        /* Visual affordance for clickable card */
        cursor: pointer;
      }

      .card:focus-within {
        outline-color: #0056b3;
        outline-width: 3px;
      }

      .card:hover {
        outline-color: #0056b3;
        outline-width: 3px;
      }

      .card--front,
      .card--back {
        transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out, visibility 0.3s ease-in-out;
      }

      /* Show front by default */
      .card--front {
        visibility: visible;
        opacity: 1;
        transform: rotateY(0deg);
      }

      .card--back {
        visibility: hidden;
        opacity: 0;
        transform: rotateY(180deg);
      }

      /* Show back when aria-pressed="true" */
      .card[data-pressed="true"] .card--front {
        visibility: hidden;
        opacity: 0;
        transform: rotateY(-180deg);
      }

      .card[data-pressed="true"] .card--back {
        visibility: visible;
        opacity: 1;
        transform: rotateY(0deg);
      }

      .flip-button {
        position: absolute;
        top: 1rem;
        right: 1rem;
        background: #0056b3;
        color: #ffffff;
        border: none;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        cursor: pointer;
        transition: all 0.2s ease;
        z-index: 10;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 18px;
        line-height: 1;
      }

      .flip-button:hover {
        background: #004085;
        transform: scale(1.1);
        box-shadow: 0 4px 12px rgba(0, 86, 179, 0.3);
      }

      .flip-button:focus {
        outline: 3px solid #ffd700;
        outline-offset: 2px;
        background: #004085;
      }

      .flip-button:active {
        transform: scale(0.95);
      }

      /* Button icon states */
      .flip-button::before {
        content: "↻"; /* Circular arrow icon for front state */
        transition: all 0.3s ease;
      }

      /* Show X icon when back is visible */
      .card[data-pressed="true"] .flip-button::before {
        content: "×"; /* X icon for back state */
        font-size: 24px;
        font-weight: bold;
      }

      /* Reduced motion support */
      @media (prefers-reduced-motion: reduce) {
        .card--front,
        .card--back {
          transition: none;
        }

        .flip-button {
          transition: none;
        }

        .flip-button:hover {
          transform: none;
          box-shadow: none;
        }

        .flip-button:active {
          transform: none;
        }

        .card {
          transition: none;
        }
      }
      ```

      **JavaScript State Management:**
      ```javascript
      const flipCards = document.querySelectorAll('.card');

      flipCards.forEach(card => {
        const button = card.querySelector('.flip-button');
        const front = card.querySelector('.card--front');
        const back = card.querySelector('.card--back');

        function toggleCard() {
          const isPressed = button.getAttribute('aria-pressed') === 'true';
          const newState = !isPressed;

          // Update ARIA state
          button.setAttribute('aria-pressed', newState);

          // Update card data attribute for CSS
          card.setAttribute('data-pressed', newState);

          // Announce state change to screen readers
          const announcement = newState ? 'Showing back of card' : 'Showing front of card';
          announceToScreenReader(announcement);
        }

        // Button click handler
        button.addEventListener('click', (event) => {
          event.stopPropagation(); // Prevent card click when button is clicked
          toggleCard();
        });

        // Card container click handler for mouse/touch users
        card.addEventListener('click', toggleCard);

        // Keyboard handler
        button.addEventListener('keydown', (event) => {
          if (event.key === 'Enter' || event.key === ' ') {
            event.preventDefault();
            toggleCard();
          }
        });
      });

      // Screen reader announcement helper
      function announceToScreenReader(message) {
        const announcement = document.createElement('div');
        announcement.setAttribute('aria-live', 'polite');
        announcement.setAttribute('aria-atomic', 'true');
        announcement.className = 'sr-only';
        announcement.textContent = message;

        document.body.appendChild(announcement);

        setTimeout(() => {
          document.body.removeChild(announcement);
        }, 1000);
      }
      ```

      **Accessibility Guidelines:**

      **Button Requirements:**
      - Must have type="button" to prevent form submission
      - aria-pressed attribute must be present and toggle between "true" and "false"
      - Accessible name should reference visible card content via aria-label
      - Should handle both click and keyboard events
      - Position in top-right corner for intuitive placement
      - Use dynamic icons: ↻ (arrow) for front state, × (X) for back state

      **Card Container Interaction:**
      - **Mouse/Touch Support:** Allow clicking anywhere on card to flip
      - **Visual Affordance:** Use cursor: pointer to indicate clickable area
      - **Event Handling:** Prevent conflicts between card and button clicks
      - **Accessibility Maintained:** All keyboard and screen reader functionality preserved

      **Content Structure:**
      - Front and back content must be present
      - Only one side visible at a time
      - Use semantic HTML for content (headings, paragraphs, images)
      - Maintain logical reading order
      - Use `visibility: hidden/visible` for smooth animations while maintaining accessibility

      **Focus Management:**
      - Focus indicator should wrap the entire card when button is focused
      - Use :focus-within CSS pseudo-class for container focus
      - Ensure focus indicator has sufficient contrast (3:1 minimum)
      - Maintain focus order during state changes

      **Screen Reader Support:**
      - aria-pressed state announces current card side
      - Consider adding aria-live region for state changes
      - Ensure content is properly labeled and described
      - Test with screen readers to verify announcements

      **Animation Considerations:**
      - Use CSS transitions for smooth flipping effects
      - Ensure animations don't interfere with accessibility
      - **Always implement prefers-reduced-motion media query**
      - Remove all animations, transforms, and transitions when reduced motion is preferred
      - Maintain content visibility during transitions
      - Respect user's motion sensitivity preferences

      **Button Design Best Practices:**
      - **Positioning**: Place in top-right corner for intuitive access
      - **Shape**: Use circular design for modern, clean appearance
      - **Icons**: Implement dynamic icons that change with card state
        - Front state: ↻ (circular arrow) indicating "click to flip"
        - Back state: × (X) indicating "click to return"
      - **Visual Feedback**: Include hover, focus, and active states
      - **Accessibility**: Maintain aria-label for screen reader context
      - **Responsive**: Scale appropriately for different card sizes

      **Testing Checklist:**
      - Verify aria-pressed toggles correctly
      - Test keyboard navigation (Enter, Space, Tab)
      - Check focus indicator visibility and contrast
      - Validate screen reader announcements
      - Test content visibility changes
      - Verify accessible names are descriptive via aria-label
      - Check that only one side is visible at a time
      - Verify button positioning in top-right corner
      - Test dynamic icon changes (↻ to ×) with state changes
      - Ensure button remains accessible in all card states
      - Validate responsive button sizing for different card layouts
      - Test card container click functionality (anywhere on card)
      - Verify hover states show blue border matching focus indicator
      - Check cursor pointer appears on card hover
      - Ensure no conflicts between card and button click events
      - Test reduced motion preference support (prefers-reduced-motion: reduce)
      - Verify all animations are disabled when reduced motion is preferred

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