.ai/principles/distilled/frontend-a11y.md
Prerequisite: If you haven't already, also read .ai/principles/distilled/frontend-vue.md - it contains foundational rules that apply to all frontend work.
div/span with ARIA roles (e.g., use <button> not <div role="button">, <a> not <div role="link">)role attributes when a native HTML element with the equivalent implicit role existsdiv/span elements with semantic equivalents like p, button, time, article, ol/ul, table, etc.h1 element exists per pagetext, textarea, select, checkbox, radio, file, toggle) has an associated <label>aria-label when no visible text is presentalt attribute (empty alt="" for decorative images)alt attribute text under approximately 150 characters<fieldset> has <legend> as its first child<figure> has <figcaption> as its first child<table> has <caption> as its first childgl-sr-only class to visually hide labels that must remain accessible to screen readersaria-label<gl-button icon="..." /> not <gl-icon /> with a click handleraria-labelaria-hidden="true" to <gl-icon> — it hides icons from screen readers by defaultalt="" on ``, or role="img" + alt="" on inline SVGsaria-*, role, or tabindex when semantic HTML provides equivalent accessibility:hover styles, add matching :focus styles for keyboard usersoutline without providing an alternative visible focus indicator (e.g., box-shadow)tabindextabindex="0" on interactive elements (<gl-link>, <gl-button>) — they are already tab-accessibletabindex="0" on static/non-interactive elements just to make them readable by screen readerstabindex values (tabindex="1" or greater)tabindex="0" to make a div/span interactive — use a semantic interactive element insteadaxe-core-gem for complete user journeys covering HAML, Vue, and JSFor the full picture, see: