{"id":"l-a11y-4-3","courseId":"web-accessibility","lang":"en","kind":"lesson","summary":"Predict the accessible name and description a browser computes for any control, and give icon buttons and repeated buttons names that are unique and match their visible label.","html":"<p>Ticketly’s event page has three buttons that all say “Book now”, a close button that shows “×”, a search button containing only an SVG magnifying glass, and a newsletter button whose name a developer set to “newsletter-cta” for analytics. In a screen reader’s list of buttons, the page reads: “Book now, Book now, Book now, times, button, newsletter-cta”. Every one of those is a naming problem, and you fix all of them by understanding one algorithm.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">How the browser computes a name</h2>\n<p>The W3C’s Accessible Name and Description Computation spec defines exactly how a browser picks a name. Simplified, it tries these sources in order and takes the <strong>first one that produces text</strong>:</p>\n<ol>\n<li><strong><code dir=\"ltr\">aria-labelledby</code></strong>: the text of the referenced elements, joined with spaces, in the order of the ids.</li>\n<li><strong><code dir=\"ltr\">aria-label</code></strong>: the attribute’s string.</li>\n<li><strong>Native labelling</strong>: a <code dir=\"ltr\">&lt;label&gt;</code> for form fields, <code dir=\"ltr\">alt</code> for images, <code dir=\"ltr\">&lt;legend&gt;</code> for a fieldset, <code dir=\"ltr\">&lt;caption&gt;</code> for a table, <code dir=\"ltr\">&lt;title&gt;</code> for an SVG.</li>\n<li><strong>Content</strong>, for roles that allow it: buttons, links, headings, tabs, table cells, menu items and options. Text inside, plus the <code dir=\"ltr\">alt</code> of images inside, minus anything with <code dir=\"ltr\">aria-hidden=&quot;true&quot;</code>.</li>\n<li><strong><code dir=\"ltr\">title</code></strong> (and <code dir=\"ltr\">placeholder</code> for inputs) as a last resort.</li>\n</ol>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-a11y-4-3-t1\"><defs><marker id=\"arrow-l-a11y-4-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-a11y-4-3-t1\">A ladder of five steps from highest to lowest priority: aria-labelledby, aria-label, native label such as label, alt or legend, content for buttons links and headings, and finally title or placeholder. The first step that yields text becomes the name.</title>\n  <rect class=\"d-box-primary\" x=\"20\" y=\"20\" width=\"300\" height=\"34\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"35\" y=\"42\">1 aria-labelledby</text>\n  <rect class=\"d-box-primary\" x=\"60\" y=\"62\" width=\"300\" height=\"34\" rx=\"6\"/>\n  <text class=\"d-code\" x=\"75\" y=\"84\">2 aria-label</text>\n  <rect class=\"d-box-accent\" x=\"100\" y=\"104\" width=\"300\" height=\"34\" rx=\"6\"/>\n  <text class=\"d-label\" x=\"115\" y=\"126\">3 label, alt, legend, caption</text>\n  <rect class=\"d-box-accent\" x=\"140\" y=\"146\" width=\"300\" height=\"34\" rx=\"6\"/>\n  <text class=\"d-label\" x=\"155\" y=\"168\">4 content (button, link, heading)</text>\n  <rect class=\"d-box\" x=\"180\" y=\"188\" width=\"300\" height=\"34\" rx=\"6\"/>\n  <text class=\"d-label\" x=\"195\" y=\"210\">5 title, placeholder</text>\n  <path class=\"d-arrow\" d=\"M560 30 L560 200\" marker-end=\"url(#arrow-l-a11y-4-3-1)\"/>\n  <text class=\"d-label-muted\" x=\"575\" y=\"110\">lower</text>\n  <text class=\"d-label-muted\" x=\"575\" y=\"130\">priority</text>\n</svg><figcaption>Where a name comes from, highest priority first</figcaption></figure>\n<p>Two consequences matter every day. First, <strong>ARIA naming overrides visible text</strong>: once you add <code dir=\"ltr\">aria-label</code>, the button’s own content is ignored. Second, <strong>names don’t merge</strong>: if <code dir=\"ltr\">aria-labelledby</code> exists, the element’s <code dir=\"ltr\">aria-label</code> is skipped. The one twist: if <code dir=\"ltr\">aria-labelledby</code> lists the element’s own id, that self-reference resolves to its <code dir=\"ltr\">aria-label</code> when it has one, not to its content. You can check any result in the browser: Chrome’s Accessibility pane and Firefox’s Accessibility Inspector both show the computed name.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Icon-only buttons</h2>\n<p>A button with only an icon has no text content, so it has no name, unless you give it one. Three options, in order of preference:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">ticketly/header.html</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">&#x3C;!-- 1. Visible text next to the icon: best for everyone --></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"search\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">svg</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-hidden</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"true\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> viewBox</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"0 0 24 24\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>…&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">svg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  Search</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">&#x3C;!-- 2. Visually hidden text: the design stays icon-only --></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"close\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-hidden</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"true\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>×&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"visually-hidden\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Close banner&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">&#x3C;!-- 3. aria-label: fine, but invisible to translation tools that skip attributes --></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"close\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Close banner\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-hidden</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"true\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>×&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">span</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>Notice the <code dir=\"ltr\">aria-hidden=&quot;true&quot;</code> on the “×”. Without it, option 2’s name would be “× Close banner”, read by some screen readers as “times Close banner”.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Naming the unnameable</span></p>\n<p><code dir=\"ltr\">aria-label</code> on a <code dir=\"ltr\">div</code> or <code dir=\"ltr\">span</code> with no role does nothing reliable: ARIA prohibits names on generic elements, and most screen readers ignore them. If you find yourself labelling a div, either it should be a real control (a button, a link) or the text should be visible on the page.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Repeated buttons and Label in Name</h2>\n<p>Three “Book now” buttons are fine visually, because each sits in its card. In a list of buttons they are indistinguishable. You want names like “Book now, Friday pass” without changing the visible design. <code dir=\"ltr\">aria-labelledby</code> can reference the button itself plus the card heading:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h3</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fri-title\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Friday pass&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fri-book\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-labelledby</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fri-book fri-title\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Book now&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">article</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>The name becomes “Book now Friday pass”. The visible words come <strong>first</strong>, which matters for <strong>2.5.3 Label in Name (A)</strong>: the accessible name must contain the visible label, and best practice is to start with it. Dina, who uses voice control, says “click Book now”; her software finds the three matches and numbers them for her to choose. If the name had been “Reserve Friday” she would have no way to click it by saying what she sees. That is exactly what was wrong with the “newsletter-cta” button: the visible word “Subscribe” isn’t in the name at all. Delete the <code dir=\"ltr\">aria-label</code> and the content provides the right name again.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Descriptions</h2>\n<p>A <strong>description</strong> is secondary information, announced after the name and role, usually after a short pause. <code dir=\"ltr\">aria-describedby</code> works like <code dir=\"ltr\">aria-labelledby</code> but feeds the description instead:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"promo\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Promo code&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">input</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"promo\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-describedby</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"promo-hint\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"promo-hint\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Codes are 8 letters, for example HARBOUR8.&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>NVDA reads “Promo code, edit text, Codes are 8 letters, for example HARBOUR8.” Use descriptions for hints, formats and error messages, as you did in the forms lesson. Keep the name short and the description optional: users can skip it, but they can’t skip a name.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Avoid title for anything important</span></p>\n<p>The <code dir=\"ltr\">title</code> attribute shows a tooltip on mouse hover only. Keyboard and touch users never see it, and screen readers handle it inconsistently. It is the last fallback in the algorithm for a reason. Don’t rely on it for names or instructions.</p>\n</aside>\n<p>Every control on Ticketly now has a name a person could say out loud. The last lesson of this section looks at the words themselves: plain language, and the language attributes that make Ticketly’s Arabic content sound right.</p>\n","toc":[{"id":"sec-1","text":"How the browser computes a name","level":2},{"id":"sec-2","text":"Icon-only buttons","level":2},{"id":"sec-3","text":"Repeated buttons and Label in Name","level":2},{"id":"sec-4","text":"Descriptions","level":2}],"takeaways":["Browsers compute a control’s name in a fixed order: aria-labelledby, then aria-label, then native labelling (label, alt, legend, caption), then the element’s content, then title.","aria-labelledby and aria-label override everything else, including visible text, so use them carefully.","A visible label must appear in the accessible name (2.5.3 Label in Name), ideally at the start, so voice control users can say what they see.","aria-describedby adds secondary information, read after the name and role; it is for hints and errors, not for the name itself."],"further":[{"title":"W3C: Accessible Name and Description Computation 1.2","url":"https://www.w3.org/TR/accname-1.2/"},{"title":"W3C APG: Providing Accessible Names and Descriptions","url":"https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/"},{"title":"W3C: Understanding 2.5.3 Label in Name","url":"https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html"}],"quiz":[{"q":"<p>What is the accessible name of this button?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h3</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fri\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Friday pass&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"b1\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Buy\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-labelledby</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"b1 fri\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Book now&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n","options":[{"text":"“Buy”","why":"aria-labelledby outranks aria-label on the button, so the name also includes the referenced heading."},{"text":"“Book now”","why":"The content is the lowest-priority source here. Both ARIA attributes outrank it."},{"text":"“Buy Friday pass”","why":"Correct. aria-labelledby wins and joins the referenced elements in order. When the algorithm reaches the button itself through that list, it skips aria-labelledby (to avoid a loop) and takes the next source, aria-label “Buy”. The visible “Book now” never makes it in, which also fails Label in Name."},{"text":"“Book now Friday pass”","why":"That is what you would get without the aria-label. With it, the button’s self-reference resolves to “Buy”, not to its content."}],"answer":2},{"q":"<p>A voice control user says “click Get tickets”. Nothing happens. Why?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">a</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> href</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"/tickets\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> aria-label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Purchase festival passes\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Get tickets&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n","options":[{"text":"Links can’t be activated by voice control","why":"Voice control activates links all the time. The words just have to match the name."},{"text":"The aria-label replaces the visible text, so the name doesn’t contain “Get tickets”","why":"Correct. This fails 2.5.3 Label in Name. Remove the aria-label, or start it with the visible words, such as “Get tickets for Harbour Lights”."},{"text":"The link needs role=“button” to be voice controllable","why":"It navigates, so it is correctly a link. The role has nothing to do with voice matching."}],"answer":1},{"q":"<p>Which element’s accessible name comes from its content?</p>","options":[{"text":"<code dir=\"ltr\">&lt;button&gt;Search&lt;/button&gt;</code>","why":"Correct. Buttons, links, headings, tabs and a few other roles take their name from their content, including the alt text of images inside them."},{"text":"<code dir=\"ltr\">&lt;input type=&quot;text&quot;&gt;</code> with the word Search written next to it in a span","why":"Text inputs never take a name from nearby text. They need a label, aria-labelledby or aria-label."},{"text":"<code dir=\"ltr\">&lt;div aria-label=&quot;Search area&quot;&gt;…&lt;/div&gt;</code>","why":"A plain div has a generic role, and ARIA prohibits naming generic elements. Screen readers usually ignore this label."},{"text":"<code dir=\"ltr\">&lt;nav&gt;</code> containing a search link","why":"Landmarks don’t take a name from their content; otherwise the whole menu would be read as the name. Name a nav with aria-label or aria-labelledby."}],"answer":0}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<div class=\"banner\">\n  <p>Early-bird prices end Friday.</p>\n  <button type=\"button\" class=\"close\">×</button>\n</div>\n\n<header class=\"bar\">\n  <strong>Ticketly</strong>\n  <button type=\"button\" class=\"search\"><svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\"><circle cx=\"10\" cy=\"10\" r=\"6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/><path d=\"M15 15 L21 21\" stroke=\"currentColor\" stroke-width=\"2\"/></svg></button>\n</header>\n\n<div class=\"cards\">\n  <article class=\"card\">\n    <h3>Friday pass</h3>\n    <button type=\"button\" class=\"book\">Book now</button>\n  </article>\n  <article class=\"card\">\n    <h3>Saturday pass</h3>\n    <button type=\"button\" class=\"book\">Book now</button>\n  </article>\n  <article class=\"card\">\n    <h3>Weekend pass</h3>\n    <button type=\"button\" class=\"book\">Book now</button>\n  </article>\n</div>\n\n<footer>\n  <label for=\"news-email\">Email</label>\n  <input id=\"news-email\" type=\"email\" autocomplete=\"email\">\n  <button type=\"button\" class=\"subscribe\" aria-label=\"newsletter-cta\">Subscribe</button>\n</footer>","css":".banner { display: flex; justify-content: space-between; align-items: center; background: #fff4d6; padding: 4px 12px; border-radius: 8px; }\n.banner p { margin: 0; }\n.bar { display: flex; justify-content: space-between; align-items: center; margin-block: 12px; }\n.cards { display: grid; gap: 8px; }\n.card { border: 1px solid #c9c4dc; border-radius: 12px; padding: 8px 12px; }\n.card h3 { margin: 0 0 6px; font-size: 1rem; }\nbutton { font: inherit; min-block-size: 32px; min-inline-size: 32px; border-radius: 8px; border: 1px solid #5b3cc4; background: #fff; color: #5b3cc4; }\nfooter { margin-block-start: 12px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n","js":""},"solution":{"html":"<div class=\"banner\">\n  <p>Early-bird prices end Friday.</p>\n  <button type=\"button\" class=\"close\"><span aria-hidden=\"true\">×</span><span class=\"visually-hidden\">Close banner</span></button>\n</div>\n\n<header class=\"bar\">\n  <strong>Ticketly</strong>\n  <button type=\"button\" class=\"search\" aria-label=\"Search\"><svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\"><circle cx=\"10\" cy=\"10\" r=\"6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"/><path d=\"M15 15 L21 21\" stroke=\"currentColor\" stroke-width=\"2\"/></svg></button>\n</header>\n\n<div class=\"cards\">\n  <article class=\"card\">\n    <h3 id=\"fri-title\">Friday pass</h3>\n    <button type=\"button\" class=\"book\" id=\"fri-book\" aria-labelledby=\"fri-book fri-title\">Book now</button>\n  </article>\n  <article class=\"card\">\n    <h3 id=\"sat-title\">Saturday pass</h3>\n    <button type=\"button\" class=\"book\" id=\"sat-book\" aria-labelledby=\"sat-book sat-title\">Book now</button>\n  </article>\n  <article class=\"card\">\n    <h3 id=\"wkd-title\">Weekend pass</h3>\n    <button type=\"button\" class=\"book\" id=\"wkd-book\" aria-labelledby=\"wkd-book wkd-title\">Book now</button>\n  </article>\n</div>\n\n<footer>\n  <label for=\"news-email\">Email</label>\n  <input id=\"news-email\" type=\"email\" autocomplete=\"email\">\n  <button type=\"button\" class=\"subscribe\">Subscribe</button>\n</footer>","css":".banner { display: flex; justify-content: space-between; align-items: center; background: #fff4d6; padding: 4px 12px; border-radius: 8px; }\n.banner p { margin: 0; }\n.bar { display: flex; justify-content: space-between; align-items: center; margin-block: 12px; }\n.cards { display: grid; gap: 8px; }\n.card { border: 1px solid #c9c4dc; border-radius: 12px; padding: 8px 12px; }\n.card h3 { margin: 0 0 6px; font-size: 1rem; }\nbutton { font: inherit; min-block-size: 32px; min-inline-size: 32px; border-radius: 8px; border: 1px solid #5b3cc4; background: #fff; color: #5b3cc4; }\nfooter { margin-block-start: 12px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }\n.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n","js":""},"checks":[{"id":"close-named","test":"const textOf = (node) => { let out = ''; for (const n of node.childNodes) { if (n.nodeType === 3) out += n.textContent; else if (n.nodeType === 1) { if (n.getAttribute('aria-hidden') === 'true') continue; const cs = getComputedStyle(n); if (cs.display === 'none' || cs.visibility === 'hidden') continue; const tag = n.tagName.toLowerCase(); if (tag === 'img') out += ' ' + (n.getAttribute('alt') || '') + ' '; else if (tag === 'svg') { const t = n.querySelector('title'); out += ' ' + (n.getAttribute('aria-label') || (t ? t.textContent : '')) + ' '; } else out += ' ' + (n.getAttribute('aria-label') || textOf(n)) + ' '; } } return out.replace(/\\s+/g, ' ').trim(); }; const nameOf = (el) => { const lb = (el.getAttribute('aria-labelledby') || '').trim(); if (lb) { const t = lb.split(/\\s+/).map((id) => { const r = document.getElementById(id); return r ? (r.getAttribute('aria-label') || textOf(r)) : ''; }).join(' ').replace(/\\s+/g, ' ').trim(); if (t) return t; } const al = (el.getAttribute('aria-label') || '').trim(); if (al) return al; const c = textOf(el); return c || (el.getAttribute('title') || '').trim(); }; const b = $('.close'); return !!b && /close|dismiss/i.test(nameOf(b)) && !/×/.test(nameOf(b));"},{"id":"search-named","test":"const textOf = (node) => { let out = ''; for (const n of node.childNodes) { if (n.nodeType === 3) out += n.textContent; else if (n.nodeType === 1) { if (n.getAttribute('aria-hidden') === 'true') continue; const cs = getComputedStyle(n); if (cs.display === 'none' || cs.visibility === 'hidden') continue; const tag = n.tagName.toLowerCase(); if (tag === 'img') out += ' ' + (n.getAttribute('alt') || '') + ' '; else if (tag === 'svg') { const t = n.querySelector('title'); out += ' ' + (n.getAttribute('aria-label') || (t ? t.textContent : '')) + ' '; } else out += ' ' + (n.getAttribute('aria-label') || textOf(n)) + ' '; } } return out.replace(/\\s+/g, ' ').trim(); }; const nameOf = (el) => { const lb = (el.getAttribute('aria-labelledby') || '').trim(); if (lb) { const t = lb.split(/\\s+/).map((id) => { const r = document.getElementById(id); return r ? (r.getAttribute('aria-label') || textOf(r)) : ''; }).join(' ').replace(/\\s+/g, ' ').trim(); if (t) return t; } const al = (el.getAttribute('aria-label') || '').trim(); if (al) return al; const c = textOf(el); return c || (el.getAttribute('title') || '').trim(); }; const b = $('.search'); return !!b && /search/i.test(nameOf(b));"},{"id":"book-unique","test":"const textOf = (node) => { let out = ''; for (const n of node.childNodes) { if (n.nodeType === 3) out += n.textContent; else if (n.nodeType === 1) { if (n.getAttribute('aria-hidden') === 'true') continue; const cs = getComputedStyle(n); if (cs.display === 'none' || cs.visibility === 'hidden') continue; const tag = n.tagName.toLowerCase(); if (tag === 'img') out += ' ' + (n.getAttribute('alt') || '') + ' '; else if (tag === 'svg') { const t = n.querySelector('title'); out += ' ' + (n.getAttribute('aria-label') || (t ? t.textContent : '')) + ' '; } else out += ' ' + (n.getAttribute('aria-label') || textOf(n)) + ' '; } } return out.replace(/\\s+/g, ' ').trim(); }; const nameOf = (el) => { const lb = (el.getAttribute('aria-labelledby') || '').trim(); if (lb) { const t = lb.split(/\\s+/).map((id) => { const r = document.getElementById(id); return r ? (r.getAttribute('aria-label') || textOf(r)) : ''; }).join(' ').replace(/\\s+/g, ' ').trim(); if (t) return t; } const al = (el.getAttribute('aria-label') || '').trim(); if (al) return al; const c = textOf(el); return c || (el.getAttribute('title') || '').trim(); }; const bs = $$('.book'); if (bs.length !== 3) return false; const names = bs.map(nameOf); const passes = ['Friday', 'Saturday', 'Weekend']; return new Set(names).size === 3 && bs.every((b, i) => names[i].includes(passes[i]));"},{"id":"label-in-name","test":"const textOf = (node) => { let out = ''; for (const n of node.childNodes) { if (n.nodeType === 3) out += n.textContent; else if (n.nodeType === 1) { if (n.getAttribute('aria-hidden') === 'true') continue; const cs = getComputedStyle(n); if (cs.display === 'none' || cs.visibility === 'hidden') continue; const tag = n.tagName.toLowerCase(); if (tag === 'img') out += ' ' + (n.getAttribute('alt') || '') + ' '; else if (tag === 'svg') { const t = n.querySelector('title'); out += ' ' + (n.getAttribute('aria-label') || (t ? t.textContent : '')) + ' '; } else out += ' ' + (n.getAttribute('aria-label') || textOf(n)) + ' '; } } return out.replace(/\\s+/g, ' ').trim(); }; const nameOf = (el) => { const lb = (el.getAttribute('aria-labelledby') || '').trim(); if (lb) { const t = lb.split(/\\s+/).map((id) => { const r = document.getElementById(id); return r ? (r.getAttribute('aria-label') || textOf(r)) : ''; }).join(' ').replace(/\\s+/g, ' ').trim(); if (t) return t; } const al = (el.getAttribute('aria-label') || '').trim(); if (al) return al; const c = textOf(el); return c || (el.getAttribute('title') || '').trim(); }; const bs = [...$$('.book'), $('.subscribe')]; return bs.every((b) => { const visible = b.innerText.replace(/\\s+/g, ' ').trim().toLowerCase(); const name = nameOf(b).toLowerCase(); return name.includes(visible); }) && $$('.book').every((b) => /^book now/i.test(nameOf(b)));"},{"id":"subscribe","test":"const textOf = (node) => { let out = ''; for (const n of node.childNodes) { if (n.nodeType === 3) out += n.textContent; else if (n.nodeType === 1) { if (n.getAttribute('aria-hidden') === 'true') continue; const cs = getComputedStyle(n); if (cs.display === 'none' || cs.visibility === 'hidden') continue; const tag = n.tagName.toLowerCase(); if (tag === 'img') out += ' ' + (n.getAttribute('alt') || '') + ' '; else if (tag === 'svg') { const t = n.querySelector('title'); out += ' ' + (n.getAttribute('aria-label') || (t ? t.textContent : '')) + ' '; } else out += ' ' + (n.getAttribute('aria-label') || textOf(n)) + ' '; } } return out.replace(/\\s+/g, ' ').trim(); }; const nameOf = (el) => { const lb = (el.getAttribute('aria-labelledby') || '').trim(); if (lb) { const t = lb.split(/\\s+/).map((id) => { const r = document.getElementById(id); return r ? (r.getAttribute('aria-label') || textOf(r)) : ''; }).join(' ').replace(/\\s+/g, ' ').trim(); if (t) return t; } const al = (el.getAttribute('aria-label') || '').trim(); if (al) return al; const c = textOf(el); return c || (el.getAttribute('title') || '').trim(); }; const b = $('.subscribe'); return !!b && /subscribe/i.test(nameOf(b)) && !/cta/i.test(nameOf(b));"}],"title":"Name every button on the event page","prompt":"<p>A screen reader lists Ticketly’s buttons as “times, button, Book now, Book now, Book now, newsletter-cta”. Fix the names without changing what sighted users see:</p>\n<ol>\n<li>The <code dir=\"ltr\">.close</code> “×” button needs a name containing “Close” (or “Dismiss”); the “×” itself shouldn’t be read.</li>\n<li>The icon-only <code dir=\"ltr\">.search</code> button needs the name “Search”.</li>\n<li>Each <code dir=\"ltr\">.book</code> button needs a unique name that <strong>starts with “Book now”</strong> and includes its pass (Friday, Saturday or Weekend).</li>\n<li>The <code dir=\"ltr\">.subscribe</code> button’s name must contain its visible word “Subscribe”.</li>\n</ol>\n","hints":["Hide the × with <span aria-hidden=\"true\">, then add visually hidden text or an aria-label on the button.","Give each heading and each Book now button an id, then aria-labelledby=“button-id heading-id” on the button.","For Subscribe, the simplest fix is deleting the aria-label so the button’s content becomes its name."],"explanation":"<p>The close button’s name now comes from visually hidden text (or an aria-label), and the × is hidden so it isn’t read as “times”. The search button gets its name from aria-label because it has no text content, and its decorative SVG is hidden. Each Book now button uses aria-labelledby to reference itself and its card’s heading, producing “Book now Friday pass”: unique in a list of buttons, and still starting with the visible words, so voice control users can say “click Book now” (2.5.3 Label in Name). The Subscribe button was the easiest: removing a wrong aria-label let the visible text become the name again.</p>\n","checkLabels":{"close-named":"The close button is named Close (or Dismiss) and the × isn’t read","search-named":"The search button is named Search","book-unique":"The three Book now buttons have unique names that include their pass","label-in-name":"Every name contains its visible text, and Book now names start with it","subscribe":"The Subscribe button’s name contains “Subscribe”"}},"words":708,"readMinutes":3}