Cursor rule
.cursor/rules/carousel-accessibility.mdcCarousel component accessibility compliance pattern
Cursor rules
Quality
32/100
Scores the file, not the repository.Length
1,618 words
1 headings · 5 code blocksRepository
428
— · pushed 17 days agoLast changed
3 days ago
First indexed 3 days ago.123456# Carousel Accessibility Standards78Ensures carousel components follow WCAG compliance and WAI-ARIA Carousel Pattern specifications.910<rule>11name: carousel_accessibility_standards12description: Enforce carousel component accessibility standards and WAI-ARIA Carousel Pattern compliance13filters:14 - type: file_extension15 pattern: "\\.(vue|jsx|tsx|html|liquid|php|js|ts)$"1617actions:18 - type: enforce19 conditions:20 # Carousel container role requirement21 - pattern: "(?i)<(div|section)[^>]*(?:carousel|slider|slideshow)[^>]*>"22 pattern_negate: "(role=\"(region|group)\"|aria-roledescription=\"carousel\")"23 message: "Carousel container must have role='region' or role='group' and aria-roledescription='carousel'."2425 # Carousel label requirement26 - pattern: "(?i)<[^>]*role=\"(region|group)\"[^>]*aria-roledescription=\"carousel\"[^>]*>"27 pattern_negate: "(aria-labelledby|aria-label)=\"[^\"]+\""28 message: "Carousel must have either aria-labelledby or aria-label for accessibility."2930 # Slide role requirement31 - pattern: "(?i)<(div|section)[^>]*(?:slide|carousel-item)[^>]*>"32 pattern_negate: "(role=\"group\"|aria-roledescription=\"slide\")"33 message: "Slide containers must have role='group' and aria-roledescription='slide'."3435 # Slide label requirement36 - pattern: "(?i)<[^>]*role=\"group\"[^>]*aria-roledescription=\"slide\"[^>]*>"37 pattern_negate: "(aria-labelledby|aria-label)=\"[^\"]+\""38 message: "Slides must have either aria-labelledby or aria-label for accessibility."3940 # Rotation control requirement41 - pattern: "(?i)<button[^>]*(?:rotation|auto-play|autoplay)[^>]*>"42 pattern_negate: "aria-label=\"[^\"]*(?:Start|Stop)[^\"]*slide[^\"]*rotation[^\"]*\""43 message: "Rotation control must have aria-label indicating its current state (Start/Stop slide rotation)."4445 # Navigation controls requirement46 - pattern: "(?i)<button[^>]*(?:next|previous|prev)[^>]*>"47 pattern_negate: "aria-label=\"[^\"]*(?:Next|Previous)[^\"]*slide[^\"]*\""48 message: "Navigation controls must have aria-label indicating their purpose (Next/Previous slide)."4950 # Navigation button disabling check51 - pattern: "(?i)\\.disabled.*next|previous.*disabled"52 message: "Navigation buttons should not be disabled. Implement wrap-around navigation to first/last slide instead for better user experience."5354 # Missing keyboard event handlers55 - pattern: "(?i)<button[^>]*(?:rotation|next|previous|prev)[^>]*>"56 pattern_negate: "(onKeyDown|onkeydown|@keydown|v-on:keydown)"57 message: "Carousel controls should handle keyboard events (Enter, Space)."5859 # Auto-rotation interval check (WCAG 2.2.2)60 - pattern: "setInterval\\([^,]+,\\s*(?:[0-4]\\d{3}|[0-9]{1,4})\\)"61 message: "Auto-rotation interval must be at least 5000ms (5 seconds) to comply with WCAG 2.2.2 Pause, Stop, Hide."6263 # Mouse hover event handlers check64 - pattern: "(?i)<(div|section)[^>]*(?:carousel|slider|slideshow)[^>]*>"65 pattern_negate: "(onMouseEnter|onmouseenter|@mouseenter|v-on:mouseenter|onMouseLeave|onmouseleave|@mouseleave|v-on:mouseleave)"66 message: "Carousel must handle mouseenter/mouseleave events to pause/resume auto-rotation."6768 # aria-live attribute check69 - pattern: "(?i)<[^>]*aria-live=\"[^\"]*\"[^>]*>"70 pattern_negate: "aria-live=\"(off|polite)\""71 message: "Carousel container must have aria-live set to 'off' during rotation and 'polite' when paused."7273 - type: suggest74 message: |75 **Carousel Component Accessibility Best Practices:**7677 **Required ARIA Attributes:**78 - **role='region' or role='group':** Set on carousel container79 - **aria-roledescription='carousel':** Set on carousel container80 - **aria-labelledby/aria-label:** Set on carousel container81 - **role='group':** Set on slide containers82 - **aria-roledescription='slide':** Set on slide containers83 - **aria-labelledby/aria-label:** Set on slide containers84 - **aria-label:** Set on rotation control (changes with state)85 - **aria-label:** Set on navigation controls86 - **aria-live:** Set to 'off' during rotation, 'polite' when paused8788 **Optional ARIA Attributes:**89 - **aria-atomic='false':** On slide wrapper90 - **aria-hidden='true':** Set on inactive slides to hide from screen readers91 - **visibility: hidden:** CSS property on inactive slides to hide from keyboard and visual users9293 **Keyboard Interaction Requirements:**94 - **Tab/Shift+Tab:** Navigate through interactive elements95 - **Enter/Space:** Activate controls96 - **Auto-rotation:** Stops on focus or mouse hover, resumes on blur or mouse away97 - **Rotation Control:** First in tab sequence9899 **Navigation Button Best Practices:**100 - **Always Enabled:** Navigation buttons should never be disabled101 - **Wrap-Around Navigation:** Next button wraps to first slide, Previous button wraps to last slide102 - **Consistent Behavior:** Users can always navigate in both directions103 - **Better UX:** Prevents users from getting "stuck" at slide boundaries104105 **Auto-rotation Requirements (WCAG 2.2.2):**106 - Minimum interval between slides: 5 seconds107 - Must provide pause/stop control108 - Must stop on user interaction109 - Must stop when any element receives focus110 - Must stop when mouse hovers over carousel111 - Must resume when mouse leaves carousel (unless manually paused)112 - Must not restart automatically after manual pause113114 **Mouse Interaction Requirements:**115 - Pause auto-rotation on mouseenter116 - Resume auto-rotation on mouseleave (unless manually paused)117 - Maintain pause state when manually stopped118 - Clear visual indication of pause state119120 **Play/Pause Button Requirements:**121 - Button state should reflect user's explicit choice122 - Button state should not change with temporary auto-rotation pauses123 - Button should maintain its state across mouse/focus events124 - Button should only change state when explicitly activated125 - Button should provide clear visual feedback of current state126 - Button state should be independent of focus/hover pause behavior127128 **State Management Requirements:**129 - Track rotation state (isRotating)130 - Track manual pause state (wasManuallyPaused)131 - Track focus/hover pause state (isPausedByFocus)132 - Update aria-live attribute based on rotation state133 - Maintain button state across temporary pauses134 - Handle state transitions appropriately135136 **Structure Requirements:**137 - Use native button elements for controls138 - Handle auto-rotation state changes139 - Provide clear visual indicators140 - Ensure proper slide labeling141 - Hide off-screen slides from keyboard and screen reader users142143 **Slide Visibility Management:**144 - Use `visibility: hidden` on inactive slides to hide from all users145 - Use `visibility: visible` on active slide to make accessible146 - The `visibility` property can be animated with CSS transitions147 - Prevents keyboard focus on hidden slides148 - Prevents screen reader access to hidden slides149 - Improves performance by removing off-screen content from accessibility tree150151 **Implementation Patterns:**152153 **Basic Carousel:**154```html155 <div role="region"156 aria-roledescription="carousel"157 aria-label="Featured Products"158 onmouseenter="pauseRotation()"159 onmouseleave="resumeRotation()">160 <div class="carousel-container" aria-live="off">161 <button aria-label="Stop slide rotation">162 Pause163 </button>164 <div role="group"165 aria-roledescription="slide"166 aria-label="Product 1 of 3">167 <img src="product1.jpg" alt="Product 1">168 <h3>Product 1</h3>169 </div>170 <button aria-label="Previous slide">171 Previous172 </button>173 <button aria-label="Next slide">174 Next175 </button>176 </div>177 </div>178```179180 **Tab Controls Implementation:**181```html182 <div role="region"183 aria-roledescription="carousel"184 aria-label="Featured Products"185 onmouseenter="pauseRotation()"186 onmouseleave="resumeRotation()">187 <div class="carousel-container" aria-live="off">188 <!-- Play/Pause Button -->189 <button aria-label="Stop slide rotation"190 onclick="toggleRotation()"191 onkeydown="handleKeyPress(event)">192 Pause193 </button>194195 <!-- Slides -->196 <div role="group"197 aria-roledescription="slide"198 aria-label="Product 1 of 3">199 <img src="product1.jpg" alt="Product 1">200 <h3>Product 1</h3>201 </div>202203 <!-- Navigation Controls -->204 <button aria-label="Previous slide"205 onclick="previousSlide()"206 onkeydown="handleKeyPress(event)">207 Previous208 </button>209 <button aria-label="Next slide"210 onclick="nextSlide()"211 onkeydown="handleKeyPress(event)">212 Next213 </button>214215 <!-- Tab Controls -->216 <div role="tablist" aria-label="Slide navigation" class="carousel-tabs">217 <button role="tab"218 aria-selected="true"219 aria-controls="slide-1"220 id="tab-1"221 onclick="goToSlide(0)"222 onkeydown="handleTabKeyPress(event)">223 Slide 1224 </button>225 <button role="tab"226 aria-selected="false"227 aria-controls="slide-2"228 id="tab-2"229 onclick="goToSlide(1)"230 onkeydown="handleTabKeyPress(event)">231 Slide 2232 </button>233 <button role="tab"234 aria-selected="false"235 aria-controls="slide-3"236 id="tab-3"237 onclick="goToSlide(2)"238 onkeydown="handleTabKeyPress(event)">239 Slide 3240 </button>241 </div>242 </div>243 </div>244245 <style>246 /* Tab Controls Styles with WCAG 2.2 compliant contrast */247 .carousel-tabs {248 display: flex;249 gap: 1rem;250 margin-top: 1rem;251 }252253 .carousel-tabs [role="tab"] {254 padding: 0.5rem 1rem;255 border: 2px solid #495057; /* 8.3:1 contrast with white */256 background: #ffffff;257 color: #212529; /* 16.6:1 contrast with white */258 border-radius: 4px;259 cursor: pointer;260 font-weight: 500;261 }262263 /* Selected tab state */264 .carousel-tabs [role="tab"][aria-selected="true"] {265 background: #0056b3; /* 7.7:1 contrast with white */266 color: #ffffff;267 border-color: #004085;268 }269270 /* Unselected tab state - still maintaining 4.5:1 contrast */271 .carousel-tabs [role="tab"][aria-selected="false"] {272 background: #ffffff;273 color: #495057; /* 8.3:1 contrast with white */274 border-color: #6c757d; /* 5.4:1 contrast with white */275 }276277 /* Focus state */278 .carousel-tabs [role="tab"]:focus {279 outline: 3px solid #0056b3; /* 7.7:1 contrast with white */280 outline-offset: 2px;281 }282283 /* Hover state */284 .carousel-tabs [role="tab"]:hover {285 background: #f8f9fa;286 border-color: #0056b3;287 }288289 /* Hover state for selected tab */290 .carousel-tabs [role="tab"][aria-selected="true"]:hover {291 background: #004085;292 }293294 /* High contrast mode support */295 @media (prefers-contrast: more) {296 .carousel-tabs [role="tab"] {297 border: 3px solid #000000;298 color: #000000;299 }300301 .carousel-tabs [role="tab"][aria-selected="true"] {302 background: #000000;303 color: #ffffff;304 }305306 .carousel-tabs [role="tab"]:focus {307 outline: 3px solid #000000;308 }309 }310 </style>311312 <script>313 // Tab Controls JavaScript314 function handleTabKeyPress(event) {315 const tabs = Array.from(document.querySelectorAll('[role="tab"]'));316 const currentTab = event.target;317 const currentIndex = tabs.indexOf(currentTab);318319 switch (event.key) {320 case 'ArrowLeft':321 event.preventDefault();322 const prevTab = tabs[currentIndex - 1] || tabs[tabs.length - 1];323 prevTab.focus();324 break;325 case 'ArrowRight':326 event.preventDefault();327 const nextTab = tabs[currentIndex + 1] || tabs[0];328 nextTab.focus();329 break;330 case 'Home':331 event.preventDefault();332 tabs[0].focus();333 break;334 case 'End':335 event.preventDefault();336 tabs[tabs.length - 1].focus();337 break;338 }339 }340341 function goToSlide(index) {342 // Update tab states343 const tabs = document.querySelectorAll('[role="tab"]');344 tabs.forEach((tab, i) => {345 tab.setAttribute('aria-selected', i === index);346 });347348 // Update slide visibility349 const slides = document.querySelectorAll('[role="group"]');350 slides.forEach((slide, i) => {351 slide.setAttribute('aria-hidden', i !== index);352 });353354 // Pause rotation when manually navigating355 pauseRotation();356 }357 </script>358```359360 **JavaScript Considerations:**361 - Implement auto-rotation pause on focus or mouse hover362 - Handle keyboard navigation363 - Update ARIA labels for rotation state364 - Implement proper tab sequence365 - Handle slide picker controls366 - Consider touch/swipe interactions367 - Ensure minimum 5-second interval between slides368 - Stop rotation on user interaction369 - Do not restart rotation automatically after manual pause370 - Handle mouse enter/leave events for pause/resume371 - Maintain manual pause state across mouse events372 - Separate auto-rotation pause behavior from Play/Pause button state373 - Update button state only on explicit user interaction374 - **Update aria-live attribute dynamically based on rotation state**375 - Track and manage multiple pause states (manual vs. focus/hover)376377 **aria-live Management Example:**378```javascript379 function toggleRotation() {380 const carouselContainer = document.querySelector('[aria-live]');381 wasManuallyPaused = isRotating;382 isRotating = !isRotating;383384 if (isRotating) {385 startRotation();386 playPauseButton.textContent = 'Pause';387 playPauseButton.setAttribute('aria-label', 'Stop slide rotation');388 // Set to 'off' when rotating - prevents announcement overload389 carouselContainer.setAttribute('aria-live', 'off');390 } else {391 stopRotation();392 playPauseButton.textContent = 'Play';393 playPauseButton.setAttribute('aria-label', 'Start slide rotation');394 // Set to 'polite' when paused - allows screen readers to announce changes395 carouselContainer.setAttribute('aria-live', 'polite');396 }397 }398399 // Also update aria-live on hover pause/resume400 function pauseRotation() {401 if (!wasManuallyPaused && isRotating) {402 stopRotation();403 const carouselContainer = document.querySelector('[aria-live]');404 carouselContainer.setAttribute('aria-live', 'polite');405 }406 }407408 function resumeRotation() {409 if (!wasManuallyPaused && isRotating) {410 startRotation();411 const carouselContainer = document.querySelector('[aria-live]');412 carouselContainer.setAttribute('aria-live', 'off');413 }414 }415```416417 **Slide Visibility Management Example:**418```css419 /* Hide all slides by default */420 .carousel-slide {421 visibility: hidden;422 opacity: 0;423 transition: visibility 0.5s ease, opacity 0.5s ease, transform 0.5s ease;424 }425426 /* Show active slide */427 .carousel-slide.active {428 visibility: visible;429 opacity: 1;430 }431432 /* Alternative: Use data attribute for active state */433 .carousel-slide[data-active="false"] {434 visibility: hidden;435 opacity: 0;436 }437438 .carousel-slide[data-active="true"] {439 visibility: visible;440 opacity: 1;441 }442```443444```javascript445 function updateSlide(index) {446 const slides = document.querySelectorAll('.carousel-slide');447 currentSlide = (index + slides.length) % slides.length;448449 slides.forEach((slide, i) => {450 if (i === currentSlide) {451 // Show active slide452 slide.classList.add('active');453 slide.setAttribute('aria-hidden', 'false');454 slide.setAttribute('data-active', 'true');455 } else {456 // Hide inactive slides457 slide.classList.remove('active');458 slide.setAttribute('aria-hidden', 'true');459 slide.setAttribute('data-active', 'false');460 }461 });462463 // Update visual position464 slidesContainer.style.transform = `translateX(-${currentSlide * 100}%)`;465 }466```467468 **Accessibility Notes:**469 - Auto-rotation should be paused by default470 - Provide clear visual focus indicators471 - Ensure sufficient color contrast472 - Test with screen readers473 - Consider motion sensitivity474 - Provide alternative navigation methods475 - Comply with WCAG 2.2.2 Pause, Stop, Hide requirement476 - Ensure mouse hover behavior is consistent and predictable477 - Maintain clear distinction between temporary and permanent pause states478 - Ensure screen reader announcements are appropriate for current state479480metadata:481 priority: high482 version: 1.0483</rule>484
Also in Shopify/horizon
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| Shopify/horizon.cursor/rules/accordion-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/animation-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/blocks.mdc · 428 | Cursor rules | stylearchtypesdatabase+2 | 62/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/breadcrumb-accessibility.mdc · 428 | Cursor rules | ui | 36/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/cart-drawer-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/chat-window-accessibility.mdc · 428 | Cursor rules | styleui | 24/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/color-contrast-accessibility.mdc · 428 | Cursor rules | lint-formatui | 44/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/color-swatch-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/commit-messages.mdc · 428 | Cursor rules | setuplint-formattypesgit | 62/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/css-standards.mdc · 428 | Cursor rules | stylearchuiperformance+3 | 49/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/disclosure-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/dropdown-navigation-accessibility.mdc · 428 | Cursor rules | styleui | 36/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/flip-card-accessibility.mdc · 428 | Cursor rules | styleui | 36/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/form-accessibility.mdc · 428 | Cursor rules | ui | 32/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/javascript-standards.mdc · 428 | Cursor rules | stylearchtypesui+2 | 54/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/landmark-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/liquid.mdc · 428 | Cursor rules | buildstyletypesdatabase+2 | 77/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/locales.mdc · 428 | Cursor rules | arch | 49/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/localization.mdc · 428 | Cursor rules | style | 54/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/mobile-accessibility-standards.mdc · 428 | Cursor rules | styleui | 36/100 | 3 days ago |
Diff against .cursor/rules/accordion-accessibility.mdc Diff against .cursor/rules/animation-accessibility.mdc Diff against .cursor/rules/blocks.mdc Diff against .cursor/rules/breadcrumb-accessibility.mdc Diff against .cursor/rules/cart-drawer-accessibility.mdc Diff against .cursor/rules/chat-window-accessibility.mdc Diff against .cursor/rules/color-contrast-accessibility.mdc Diff against .cursor/rules/color-swatch-accessibility.mdc Diff against .cursor/rules/commit-messages.mdc Diff against .cursor/rules/css-standards.mdc Diff against .cursor/rules/disclosure-accessibility.mdc Diff against .cursor/rules/dropdown-navigation-accessibility.mdc Diff against .cursor/rules/flip-card-accessibility.mdc Diff against .cursor/rules/form-accessibility.mdc Diff against .cursor/rules/javascript-standards.mdc Diff against .cursor/rules/landmark-accessibility.mdc Diff against .cursor/rules/liquid.mdc Diff against .cursor/rules/locales.mdc Diff against .cursor/rules/localization.mdc Diff against .cursor/rules/mobile-accessibility-standards.mdc
