Cursor rule
.cursor/rules/focus-order-and-styles-accessibility.mdcFocus 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
Cursor rules
Quality
32/100
Scores the file, not the repository.Length
2,016 words
1 headings · 15 code blocksRepository
428
— · pushed 17 days agoLast changed
3 days ago
First indexed 3 days ago.1234567# Focus Order and Focus Styles Accessibility Standards89Ensures 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.1011<rule>12name: focus_order_and_styles_accessibility_standards13description: Enforce focus order and focus styles accessibility standards per WCAG requirements14filters:15 - type: file_extension16 pattern: "\\.(vue|jsx|tsx|html|liquid|php|js|ts|css|scss|sass|less)$"1718actions:1920- type: enforce21 conditions:2223 # Positive tabindex values (should not be used)2425 - pattern: "tabindex=\"[1-9]\""26 message: "Positive tabindex values create illogical focus order. Use DOM order instead or tabindex=\"0\" for custom focusable elements."2728 # Missing focus styles (outline: 0 or outline: none)2930 - pattern: "outline:\\s*0|outline:\\s*none"31 message: "Focus styles should not be removed. Use custom focus indicators that meet WCAG contrast requirements."3233 # Focus styles with insufficient contrast (light colors)3435 - pattern: "outline.*#[89abcdefABCDEF]{6}|outline.*#[cdefCDEF]{3,6}"36 message: "Light focus outline colors may not meet 3:1 contrast ratio requirement for UI component identification."3738 # Missing focus-visible implementation3940 - pattern: ":focus\\s*\\{"41 pattern_negate: ":focus-visible|:focus:not\\(:focus-visible\\)"42 message: "Consider implementing :focus-visible for better keyboard-only focus indication."4344 # Focus styles that may be obscured4546 - pattern: "outline-offset:\\s*-?0\\.?0*px|outline-offset:\\s*0"47 message: "Consider using positive outline-offset to prevent focus indicators from being obscured by adjacent elements."4849 # Missing forced-colors media query for Windows High Contrast5051 - pattern: "@media\\s*\\(forced-colors:\\s*active\\)"52 pattern_negate: "outline.*transparent"53 message: "Windows High Contrast Mode requires transparent outline for native focus appearance."5455 # Custom focusable elements without proper tabindex5657 - pattern: "<(div|span|button)[^>]*onclick|onkeydown|onkeypress"58 pattern_negate: "tabindex=\"[0-9]\"|role=\"button\"|role=\"link\""59 message: "Custom interactive elements should have tabindex=\"0\" or appropriate ARIA role for keyboard accessibility."6061 # Focus styles with insufficient area6263 - pattern: "outline-width:\\s*1px|outline-width:\\s*0\\.1rem"64 message: "Thin focus outlines may not meet WCAG 2.4.13 Focus Appearance requirements for minimum area."6566 # Focus styles that blend with background6768 - pattern: "outline.*rgba\\([^)]*0\\.1[^)]*\\)|outline.*rgba\\([^)]*0\\.2[^)]*\\)"69 message: "Very transparent focus outlines may not provide sufficient contrast for visibility."7071 # Missing focus styles on interactive elements7273 - pattern: "<(button|a|input|select|textarea)[^>]*>"74 pattern_negate: ":focus|:focus-visible|tabindex"75 message: "Interactive elements should have visible focus styles for keyboard navigation accessibility."7677 # Dynamic content removal without focus management7879 - pattern: "\\.remove\\(\\)|removeChild|innerHTML\\s*="80 pattern_negate: "focus\\(|focus\\(\\)"81 message: "When removing dynamic content, ensure proper focus management by restoring focus to a logical location."8283- type: suggest84 message: |85 **WCAG Focus Order and Focus Styles Requirements:**8687 **Focus Order Requirements:**8889 **1. Logical DOM Order:**90 - **Default:** Focus order follows DOM element order91 - **Navigation:** Tab key moves forward, Shift+Tab moves backward92 - **Avoid:** Positive tabindex values (1, 2, 3, etc.)9394 **2. Tabindex Usage:**9596```html97 <!-- Good: Use DOM order (default) -->98 <button>First Button</button>99 <button>Second Button</button>100 <button>Third Button</button>101102 <!-- Good: tabindex="0" for custom focusable elements -->103 <div role="button" tabindex="0" onclick="handleClick()">104 Custom Button105 </div>106107 <!-- Good: tabindex="-1" for programmatic focus only -->108 <div id="target" tabindex="-1">Focus target</div>109 <button onclick="document.getElementById('target').focus()">110 Focus Target111 </button>112113 <!-- Bad: Positive tabindex values -->114 <button tabindex="1">First</button>115 <button tabindex="3">Third</button>116 <button tabindex="2">Second</button>117```118119 **Focus Styles Requirements:**120121 **1. WCAG 2.4.7 Focus Visible (Level A):**122 - **Requirement:** Focus indicator must exist123 - **Purpose:** Keyboard users need visible focus indication124125 **2. WCAG 1.4.11 Non-Text Contrast (Level AA):**126 - **Requirement:** Minimum 3:1 contrast ratio for UI components127 - **Applies to:** Focus indicators, borders, focus outlines128129 **3. WCAG 2.4.13 Focus Appearance (Level AAA):**130 - **Requirement:** Minimum area and contrast for focus indicators131 - **Area:** Focus indicator should be clearly visible132133 **4. WCAG 2.4.11 Focus Not Obscured (Level AA):**134 - **Requirement:** Focused element not hidden by other content135 - **Solution:** Use outline-offset to prevent overlap136137 **Focus Styles Implementation:**138139 **1. Basic Focus Styles:**140141```css142 /* Good: Visible focus indicator */143 button:focus {144 outline: 2px solid #0056b3;145 outline-offset: 2px;146 }147148 /* Good: Custom focus styles */149 .custom-button:focus {150 outline: 3px solid #dc3545;151 outline-offset: 3px;152 box-shadow: 0 0 8px rgba(220, 53, 69, 0.5);153 }154```155156 **2. Focus-Visible Implementation:**157158```css159 /* Default focus styles */160 button:focus {161 outline: 2px solid #0056b3;162 outline-offset: 2px;163 }164165 /* Remove focus styles for mouse users */166 button:focus:not(:focus-visible) {167 outline: none;168 box-shadow: none;169 }170171 /* Enhanced focus styles for keyboard users */172 button:focus-visible {173 outline: 3px solid #0056b3;174 outline-offset: 3px;175 box-shadow: 0 0 8px rgba(0, 86, 179, 0.5);176 }177```178179 **3. High Contrast Focus Styles:**180181```css182 /* Default focus styles */183 *:focus-visible {184 outline: 0.2rem solid rgba(var(--color-foreground-rgb), 0.5);185 outline-offset: -0.2rem;186 box-shadow: 0 0 0.2rem rgba(var(--color-foreground-rgb), 0.3);187 }188189 /* Windows High Contrast Mode */190 @media (forced-colors: active) {191 *:focus {192 outline: 0.2rem solid transparent;193 }194 }195```196197 **4. Component-Specific Focus Styles:**198199```css200 /* Form inputs */201 input:focus-visible,202 textarea:focus-visible,203 select:focus-visible {204 outline: 2px solid #0056b3;205 outline-offset: 2px;206 border-color: #0056b3;207 }208209 /* Links */210 a:focus-visible {211 outline: 2px solid #0056b3;212 outline-offset: 2px;213 text-decoration: underline;214 }215216 /* Buttons */217 button:focus-visible {218 outline: 2px solid #0056b3;219 outline-offset: 2px;220 box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #0056b3;221 }222```223224 **Focus Order Best Practices:**225226 **1. Logical Content Flow:**227228```html229 <!-- Good: Logical reading and focus order -->230 <header>231 <h1>Page Title</h1>232 <nav>233 <a href="/">Home</a>234 <a href="/about">About</a>235 <a href="/contact">Contact</a>236 </nav>237 </header>238239 <main>240 <h2>Main Content</h2>241 <form>242 <label for="name">Name:</label>243 <input type="text" id="name">244245 <label for="email">Email:</label>246 <input type="email" id="email">247248 <button type="submit">Submit</button>249 </form>250 </main>251```252253 **2. Custom Interactive Elements:**254255```html256 <!-- Good: Proper focusable custom element -->257 <div role="button"258 tabindex="0"259 onclick="handleClick()"260 onkeydown="handleKeydown(event)"261 class="custom-button">262 Custom Button263 </div>264265 <!-- CSS for custom button focus -->266 .custom-button:focus-visible {267 outline: 2px solid #0056b3;268 outline-offset: 2px;269 background-color: #e7f3ff;270 }271```272273 **3. Skip Links and Focus Management:**274275```html276 <!-- Skip link for main content -->277 <a href="#main-content" class="skip-link">278 Skip to main content279 </a>280281 <!-- Main content with id for focus target -->282 <main id="main-content" tabindex="-1">283 <h1>Main Content</h1>284 <!-- Content here -->285 </main>286287 <!-- CSS for skip link -->288 .skip-link {289 position: absolute;290 top: -40px;291 left: 6px;292 background: #0056b3;293 color: #ffffff;294 padding: 8px;295 text-decoration: none;296 z-index: 1000;297 }298299 .skip-link:focus {300 top: 6px;301 outline: 2px solid #ffffff;302 outline-offset: 2px;303 }304```305306 **Focus Styles Guidelines:**307308 **1. Contrast Requirements:**309 - **Minimum:** 3:1 contrast ratio for focus indicators310 - **Recommended:** 4.5:1 or higher for better visibility311 - **Test:** Against adjacent colors and backgrounds312313 **2. Size and Visibility:**314 - **Outline width:** Minimum 2px for visibility315 - **Outline offset:** Use positive values to prevent overlap316 - **Area:** Focus indicator should be clearly visible317318 **3. Color Selection:**319320```css321 /* High contrast focus colors */322 :focus-visible {323 outline: 2px solid #0056b3; /* Blue - high contrast */324 outline-offset: 2px;325 }326327 /* Alternative high contrast colors */328 :focus-visible {329 outline: 2px solid #dc3545; /* Red - high contrast */330 outline-offset: 2px;331 }332333 :focus-visible {334 outline: 2px solid #198754; /* Green - high contrast */335 outline-offset: 2px;336 }337```338339 **4. Focus Not Obscured:**340341```css342 /* Prevent focus indicator overlap */343 button:focus-visible {344 outline: 2px solid #0056b3;345 outline-offset: 3px; /* Space between element and outline */346 }347348 /* Alternative: Use box-shadow for non-overlapping focus */349 button:focus-visible {350 outline: none;351 box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #0056b3;352 }353```354355 **5. Dynamic Content Focus Management:**356357```javascript358 // Focus management best practices359 class FocusManager {360 constructor() {361 this.focusHistory = [];362 this.currentFocus = null;363 }364365 // Save focus before making changes366 saveFocus() {367 this.currentFocus = document.activeElement;368 this.focusHistory.push(this.currentFocus);369 }370371 // Restore focus to previous location372 restoreFocus() {373 if (this.focusHistory.length > 0) {374 const previousFocus = this.focusHistory.pop();375 if (previousFocus && document.contains(previousFocus)) {376 previousFocus.focus();377 }378 }379 }380381 // Focus first interactive element in new content382 focusNewContent(container) {383 const focusableElements = container.querySelectorAll(384 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'385 );386387 if (focusableElements.length > 0) {388 focusableElements[0].focus();389 }390 }391392 // Find logical focus target when content is removed393 findLogicalFocusTarget(removedElement, container) {394 // Try to focus next sibling element395 const nextSibling = removedElement.nextElementSibling;396 if (nextSibling) {397 const focusableElement = nextSibling.querySelector(398 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'399 );400 if (focusableElement) {401 return focusableElement;402 }403 }404405 // Try to focus previous sibling element406 const prevSibling = removedElement.previousElementSibling;407 if (prevSibling) {408 const focusableElement = prevSibling.querySelector(409 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'410 );411 if (focusableElement) {412 return focusableElement;413 }414 }415416 // Fallback to container or trigger button417 return container.querySelector('button, [href], input') ||418 document.querySelector('[aria-haspopup="dialog"]');419 }420 }421422 // Usage example423 const focusManager = new FocusManager();424425 function addContent() {426 focusManager.saveFocus();427428 // Add new content429 const newContent = createNewContent();430 container.appendChild(newContent);431432 // Focus first interactive element433 focusManager.focusNewContent(newContent);434 }435436 function removeContent(element) {437 const container = element.parentElement;438439 // Find logical focus target before removing440 const focusTarget = focusManager.findLogicalFocusTarget(element, container);441442 // Remove the element443 element.remove();444445 // Focus the logical target446 if (focusTarget) {447 focusTarget.focus();448 }449 }450```451452 **Testing and Validation:**453454 **1. Keyboard Navigation Testing:**455 - Navigate using Tab and Shift+Tab456 - Verify focus order is logical457 - Check that focus indicators are visible458 - Test with different focus styles459460 **2. Contrast Testing:**461 - Use browser dev tools for contrast ratios462 - Test against different backgrounds463 - Verify 3:1 minimum contrast requirement464 - Test with color blindness simulators465466 **3. Focus Visibility Testing:**467 - Test with screen readers468 - Verify focus indicators are not obscured469 - Check focus styles in different themes470 - Test Windows High Contrast Mode471472 **4. Dynamic Content Focus Testing:**473 - Test focus management when adding new content474 - Verify focus moves to first interactive element in new content475 - Test focus restoration when removing content476 - Ensure focus returns to logical location477 - Test focus management with multiple dynamic elements478 - Verify focus history is maintained correctly479480 **Common Mistakes to Avoid:**481482 **1. Focus Order Issues:**483 - Using positive tabindex values484 - Skipping focusable elements485 - Illogical DOM structure486 - Missing focusable elements487488 **2. Focus Style Problems:**489 - Removing focus styles with `outline: none`490 - Insufficient contrast ratios491 - Focus indicators that are too small492 - Focus styles that blend with background493494 **3. Implementation Issues:**495 - Missing focus-visible implementation496 - Not testing with keyboard navigation497 - Ignoring Windows High Contrast Mode498 - Focus indicators that are obscured499500 **4. Accessibility Violations:**501 - No visible focus indicators502 - Focus order that doesn't match reading order503 - Custom elements without proper focus management504 - Missing keyboard event handlers505506 **Advanced Focus Management:**507508 **1. Programmatic Focus Control:**509510```javascript511 // Focus management for modals512 function openModal() {513 const modal = document.getElementById('modal');514 const closeButton = document.getElementById('close-modal');515516 modal.style.display = 'block';517 closeButton.focus(); // Focus close button when modal opens518 }519520 // Trap focus within modal521 function trapFocus(modal) {522 const focusableElements = modal.querySelectorAll(523 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'524 );525526 const firstElement = focusableElements[0];527 const lastElement = focusableElements[focusableElements.length - 1];528529 // Handle Tab key530 document.addEventListener('keydown', function(e) {531 if (e.key === 'Tab') {532 if (e.shiftKey) {533 if (document.activeElement === firstElement) {534 lastElement.focus();535 e.preventDefault();536 }537 } else {538 if (document.activeElement === lastElement) {539 firstElement.focus();540 e.preventDefault();541 }542 }543 }544 });545 }546```547548 **2. Dynamic Focus Management:**549550```javascript551 // Focus restoration after dynamic content552 function loadContent() {553 const container = document.getElementById('content');554 const previousFocus = document.activeElement;555556 // Load new content557 container.innerHTML = newContent;558559 // Restore focus or set to first focusable element560 const firstFocusable = container.querySelector(561 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'562 );563564 if (firstFocusable) {565 firstFocusable.focus();566 } else if (previousFocus) {567 previousFocus.focus();568 }569 }570```571572 **3. Focus Indicators for Complex Components:**573574```css575 /* Multi-state focus indicators */576 .accordion-item:focus-visible {577 outline: 2px solid #0056b3;578 outline-offset: 2px;579 }580581 .accordion-item[aria-expanded="true"]:focus-visible {582 outline-color: #198754; /* Different color for expanded state */583 }584585 /* Focus indicators for different interaction states */586 .interactive-element:focus-visible {587 outline: 2px solid #0056b3;588 outline-offset: 2px;589 }590591 .interactive-element:hover:focus-visible {592 outline-color: #004085; /* Darker on hover + focus */593 }594595 .interactive-element:active:focus-visible {596 outline-color: #002752; /* Even darker when active */597 }598```599600 **4. Dynamic Content Focus Management:**601602```javascript603 // When adding content: focus first interactive element604 function addDynamicContent() {605 const container = document.getElementById('content');606 const newElement = document.createElement('div');607 newElement.innerHTML = `608 <h3>New Content</h3>609 <button class="btn">Action Button</button>610 `;611612 container.appendChild(newElement);613614 // Focus the first focusable element in new content615 const firstFocusable = newElement.querySelector('button');616 if (firstFocusable) {617 firstFocusable.focus();618 }619 }620621 // When removing content: restore focus to logical location622 function removeDynamicContent(element) {623 const triggerButton = document.querySelector('[onclick="addDynamicContent()"]');624625 // Store reference to element being removed626 const removedElement = element;627628 // Remove the element629 element.remove();630631 // Restore focus to the trigger button632 if (triggerButton) {633 triggerButton.focus();634 }635 }636637 // Advanced focus management for multiple elements638 function removeSpecificElement(element, elementType) {639 const container = element.parentElement;640 const triggerButton = document.querySelector(`[onclick="add${elementType}()"]`);641642 // Find the next logical focus target643 let nextFocusTarget = triggerButton;644645 // If there are other elements, focus the next one646 const remainingElements = container.querySelectorAll(`.${elementType.toLowerCase()}`);647 if (remainingElements.length > 0) {648 const targetElement = remainingElements[0];649 const focusableElement = targetElement.querySelector('button, a, input');650 if (focusableElement) {651 nextFocusTarget = focusableElement;652 }653 }654655 // Remove the element656 element.remove();657658 // Set focus to the appropriate target659 nextFocusTarget.focus();660 }661```662663metadata:664 priority: high665 version: 1.0666</rule>667description:668globs:669alwaysApply: false670---671
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/carousel-accessibility.mdc · 428 | Cursor rules | ui | 32/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 |
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/carousel-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
