A place to cache linked articles (think custom and personal wayback machine)
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

index.html 40KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Style with Stateful, Semantic Selectors (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://benmyers.dev/blog/semantic-selectors/">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>Style with Stateful, Semantic Selectors</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://benmyers.dev/blog/semantic-selectors/" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-01-08
  72. </p>
  73. </nav>
  74. <hr>
  75. <hgroup class="heading-wrapper">
  76. <h2 id="introduction" tabindex="-1">Introduction</h2>
  77. <a href="#introduction" rel="bookmark" aria-label="Permalink to “Introduction”">#</a></hgroup>
  78. <p>In web development, we frequently need to style elements to visually indicate some state they're in. We give form fields red outlines to indicate invalid values. We show disabled or inactive elements in gray. We use any number of colors, icons, borders, and more to indicate what kind of state an element is in. Behind the hood, those visual styles are often handled by toggling <abbr>CSS</abbr> classes.</p>
  79. <p>And yet: screenreaders, for instance, don't expose colors or borders or underlines or most of our other visual styles. They have no idea what our <code>.is-invalid</code> or <code>.selected</code> classes mean. This can pose an accessibility gap, since we have a discrepancy between visually-conveyed information and information conveyed via assistive technologies. <strong>If a state is important enough to indicate visually, it's probably important enough to expose to assistive technologies.</strong></p>
  80. <p>Let's take <q>current page</q> indicators for nav links, for instance. It's a common practice to style the current page's link in a navbar, maybe coloring it differently or giving it a different border. This usually accomplishes two things:</p>
  81. <ul>
  82. <li>It orients the user as to where they are in the site's architecture (which is especially useful if they've come directly to that page from some external source like Google)</li>
  83. <li>It indicates that they probably don't need to click that particular link lest they reload the page.</li>
  84. </ul>
  85. <p>Often, you might end up with markup like this:</p>
  86. <pre class="language-html" tabindex="0"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>nav</span><span class="token punctuation">&gt;</span></span></span><br><mark class="highlight-line highlight-line-active"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/about<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>current-page<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>About<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span></mark><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/talks<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Talks<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/projects<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Projects<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/contact<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Contact Me<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">&gt;</span></span></span></code></pre>
  87. <p>There's a problem here: that <q>current page</q> status would be super useful to screenreader users — after all, if a screenreader user reloads the current page, they might be flooded with announcements as the screenreader begins reading the page anew — and yet, screenreaders have no clue that this extra context exists.</p>
  88. <p>We could address this with an <a href="/blog/aria/"><abbr>ARIA</abbr> state attribute</a> — specifically, setting <a href="https://www.aditus.io/aria/aria-current/"><code>aria-current="page"</code></a> on the link. When <code>aria-current="page"</code> is provided on a link, the screenreader will announce something like <q>link, About, current page.</q> The exact announcement may differ depending on which screenreader is used. Now, our markup looks like this:</p>
  89. <pre class="language-html" tabindex="0"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>nav</span><span class="token punctuation">&gt;</span></span></span><br><mark class="highlight-line highlight-line-active"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/about<span class="token punctuation">"</span></span> <span class="token attr-name">aria-current</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>page<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>current-page<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>About<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span></mark><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/talks<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Talks<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/projects<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Projects<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/contact<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Contact Me<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">&gt;</span></span></span></code></pre>
  90. <p>We've introduced a new problem, though. Now we have to keep track of <em>two</em> things: the class and the <abbr>ARIA</abbr> attribute. In theory, everything should be fine, so long as we always remember to keep these two in sync. But what if they diverge, and we end up with a link that has the <code>.current-page</code> class but not <code>aria-current="page"</code>? It happens — sighted developers are much more likely to remember the visual indication and less so the semantic indication. Or, admittedly less likely, we remember to add <code>aria-current="page"</code> but we accidentally omit our <code>.current-page</code> class? <strong>We forget things. Bugs happen.</strong></p>
  91. <p>We can reduce the duplication and the risk of bugs, <a href="https://kentcdodds.com/blog/make-impossible-states-impossible">making impossible states impossible</a>, by instead using the <abbr>ARIA</abbr> attribute as our selector:</p>
  92. <pre class="language-css" tabindex="0"><code class="language-css"><span class="highlight-line"><span class="token selector">a[aria-current="page"]</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">border-bottom</span><span class="token punctuation">:</span> 7px solid yellow<span class="token punctuation">;</span> </span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
  93. <p>Now, to get the desired <em>visual</em> indication, we <em>have</em> to provide the necessary semantics for assistive technology. We can't get one without the other. I call this <strong>styling with stateful, semantic selectors</strong>. In my experience, it makes my code much more robust and ensures I don't accidentally omit necessary accessible semantics.</p>
  94. <p>There are many ways you can style with stateful, semantic selectors, but I'd like to show you a few more examples I love:</p>
  95. <hgroup class="heading-wrapper">
  96. <h2 id="expandcollapse-triggers" tabindex="-1">Expand/Collapse Triggers</h2>
  97. <a href="#expandcollapse-triggers" rel="bookmark" aria-label="Permalink to “Expand/Collapse Triggers”">#</a></hgroup>
  98. <p>In this pattern, you have a button which, when clicked, will show or hide some content. Frequently, this button will have some visual indication of whether that content is currently being shown or not — often, it'll have a caret that's in one orientation when the content is expanded, and it's rotated when the content is collapsed.</p>
  99. <p>As before, we <em>could</em> toggle that caret state with a CSS class — let's call it <code>.is-expanded</code>.</p>
  100. <div class="side-by-side outer"><p class="side-by-side inner"></p><div class="side-by-side inner"><pre class="language-html" tabindex="0"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>is-expanded<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> Additional Details</span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span></span></code></pre>
  101. <pre class="language-css" tabindex="0"><code class="language-css"><span class="highlight-line"><span class="token selector">button::before</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> </span><br><span class="highlight-line"> </span><br><span class="highlight-line"> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'❯'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">transition</span><span class="token punctuation">:</span> transform 0.2s<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">button.is-expanded::before</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</span><span class="token punctuation">(</span>90deg<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
  102. </div></div>
  103. <p>If you were to try to use the above button with a screenreader, you'd have an issue. When you press the button, the caret will rotate, but your screenreader will remain silent. It has no context that the button has hidden or revealed some content, so it says nothing. As a screenreader user, you're left without any feedback, and you may start to wonder whether you even clicked the button at all.</p>
  104. <p>Fortunately, there's an <abbr>ARIA</abbr> state attribute for this exact purpose, called <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-expanded"><code>aria-expanded</code></a>! When a screenreader navigates to a button with <code>aria-expanded="false"</code>, it'll announce the button along with something like <q>collapsed,</q> such as <q>button, Additional Details, collapsed.</q> This tells screenreader users that the button controls toggling some content, and that content is currently hidden. When the attribute is toggled to <code>aria-expanded="true"</code>, the screenreader announcement will update to include <q>expanded</q> (or something to that effect), and say something like <q>button, Additional Details, expanded.</q></p>
  105. <p>Our code might update to something like:</p>
  106. <div class="side-by-side outer"><p class="side-by-side inner"></p><div class="side-by-side inner"><pre class="language-html" tabindex="0"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span></span></span><br><span class="highlight-line"> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>is-expanded<span class="token punctuation">"</span></span></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"> <span class="token attr-name">aria-expanded</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span></span></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> Additional Details</span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span></span></span></span></code></pre>
  107. <pre class="language-css" tabindex="0"><code class="language-css"><span class="highlight-line"><span class="token selector">button::before</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> </span><br><span class="highlight-line"> </span><br><span class="highlight-line"> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'❯'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">transition</span><span class="token punctuation">:</span> transform 0.2s<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">button.is-expanded::before</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</span><span class="token punctuation">(</span>90deg<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
  108. </div></div>
  109. <p>Upon every click of the button, a script toggles the <code>.is-expanded</code> class and flips <code>aria-expanded</code> between <code>"true"</code> and <code>"false"</code>. However… we're once again doing two things where we could be doing just one thing, and we're risking impossible states if <code>aria-expanded</code> and the <code>.is-expanded</code> class fall out of sync with each other.</p>
  110. <p>Instead, let's use <code>aria-expanded</code> as the source of truth for our styles:</p>
  111. <div class="side-by-side outer"><p class="side-by-side inner"></p><div class="side-by-side inner"><pre class="language-html" tabindex="0"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">aria-expanded</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>false<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> Additional Details</span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span></span></code></pre>
  112. </div></div>
  113. <pre class="language-css" tabindex="0"><code class="language-css"><span class="highlight-line"><span class="token selector">button::before</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> </span><br><span class="highlight-line"> </span><br><span class="highlight-line"> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'❯'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span></span><br><span class="highlight-line"> <span class="token property">transition</span><span class="token punctuation">:</span> transform 0.2s<span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">button[aria-expanded="true"]::before</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</span><span class="token punctuation">(</span>90deg<span class="token punctuation">)</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
  114. <hgroup class="heading-wrapper">
  115. <h2 id="sorted-table-columns" tabindex="-1">Sorted Table Columns</h2>
  116. <a href="#sorted-table-columns" rel="bookmark" aria-label="Permalink to “Sorted Table Columns”">#</a></hgroup>
  117. <p>Say you've got a sortable table, and you'd like to indicate which column the table is sorted by, and in which direction. No sweat — this time, we can use the <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-sort"><code>aria-sort</code></a> attribute.</p>
  118. <p>The <code>aria-sort</code> attribute should be applied to at most <em>one</em> column header at a time. For most sortable tables <i>(sor-tables?)</i>, you'll want to apply <code>aria-sort="ascending"</code> to the sorted column's table header when the column is sorted in ascending order, apply <code>aria-sort="descending"</code> to the sorted column's table header when the column is sorted in descending order, and remove <code>aria-sort</code> from the table header altogether when the sort is cleared. When a screenreader user navigates to a table where a column header has <code>aria-sort="ascending"</code> or <code>aria-sort="descending"</code>, their screenreader will tell them the name of the sorted column and its direction.</p>
  119. <p>Assuming you have buttons inside each of the table headers to let the user sort, your markup might look something like this:</p>
  120. <pre class="language-html" tabindex="0"><code class="language-html"><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>table</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>thead</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span> <span class="token attr-name">scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>col<span class="token punctuation">"</span></span> <span class="token attr-name">aria-sort</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>ascending<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">aria-describedby</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sort-description<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> Title</span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span> <span class="token attr-name">scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>col<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">aria-describedby</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sort-description<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> Author</span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span> <span class="token attr-name">scope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>col<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">aria-describedby</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sort-description<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> ISBN-13</span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>thead</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tbody</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"> …</span><br><span class="highlight-line"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tbody</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>table</span><span class="token punctuation">&gt;</span></span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sort-description<span class="token punctuation">"</span></span> <span class="token attr-name">hidden</span><span class="token punctuation">&gt;</span></span>Sort by column<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span></span></code></pre>
  121. <p>If we wanted to add some arrows inside the sorted column's header's button to indicate the current sort direction, the <code>[aria-sort]</code> attribute selector will see us through:</p>
  122. <pre class="language-css" tabindex="0"><code class="language-css"><span class="highlight-line"><span class="token selector">th[aria-sort="ascending"] button::after</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> </span><br><span class="highlight-line"> </span><br><span class="highlight-line"> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'↑'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span><br><span class="highlight-line"></span><br><span class="highlight-line"><span class="token selector">th[aria-sort="descending"] button::after</span> <span class="token punctuation">{</span></span><br><span class="highlight-line"> </span><br><span class="highlight-line"> </span><br><span class="highlight-line"> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">'↓'</span><span class="token punctuation">;</span></span><br><span class="highlight-line"><span class="token punctuation">}</span></span></code></pre>
  123. <p><i>(Simplified for the sake of demonstration. Check out <a href="https://adrianroselli.com/2021/04/sortable-table-columns.html">Adrian Roselli's sortable tables article</a> for a much more thorough and robust approach)</i></p>
  124. <hgroup class="heading-wrapper">
  125. <h2 id="and-more" tabindex="-1">And More!</h2>
  126. <a href="#and-more" rel="bookmark" aria-label="Permalink to “And More!”">#</a></hgroup>
  127. <p>These examples aren't the only times stateful, semantic selectors could prove helpful. Here are just a few more I didn't get into:</p>
  128. <p>In short, <strong>building with accessible semantics from the get-go can give you expressive, meaningful style hooks for free.</strong> Leaning on those style hooks in your <abbr>CSS</abbr> selectors lets you reduce the number of moving parts in your site or application, and it can prevent accessibility bugs from creeping in down the road.</p>
  129. <p>As with any web development recommendation, <strong>use your best judgment</strong>. If you find yourself <em>contorting</em> an element's semantics or markup to get the appearance you want, it's a sign to take a step back and revisit. Maybe classes are your friend, or maybe you need to revisit your design and determine whether it still makes sense.</p>
  130. <hgroup class="heading-wrapper">
  131. <a href="#related-reads" rel="bookmark" aria-label="Permalink to “Related Reads”">#</a></hgroup>
  132. <p>I'm definitely not the first to write about this approach. Here are a few other articles on the subject I'd recommend reading:</p>
  133. <ul>
  134. <li><a href="https://css-tricks.com/user-facing-state/"><q>User Facing State</q> by Scott O'Hara on CSS Tricks</a></li>
  135. <li><a href="https://adrianroselli.com/2021/06/using-css-to-enforce-accessibility.html"><q>Using CSS to Enforce Accessibility</q> by Adrian Roselli</a></li>
  136. <li><a href="https://www.aleksandrhovhannisyan.com/blog/represent-state-with-html-attributes-not-class-names/"><q>Represent State with HTML Attributes, Not Class Names</q> by Aleksandr Hovhannisyan</a></li>
  137. </ul>
  138. </article>
  139. <hr>
  140. <footer>
  141. <p>
  142. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  143. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  144. </svg> Accueil</a> •
  145. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  146. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  147. </svg> Suivre</a> •
  148. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  149. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  150. </svg> Pro</a> •
  151. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  152. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  153. </svg> Email</a> •
  154. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  155. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  156. </svg> Légal</abbr>
  157. </p>
  158. <template id="theme-selector">
  159. <form>
  160. <fieldset>
  161. <legend><svg class="icon icon-brightness-contrast">
  162. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  163. </svg> Thème</legend>
  164. <label>
  165. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  166. </label>
  167. <label>
  168. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  169. </label>
  170. <label>
  171. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  172. </label>
  173. </fieldset>
  174. </form>
  175. </template>
  176. </footer>
  177. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  178. <script>
  179. function loadThemeForm(templateName) {
  180. const themeSelectorTemplate = document.querySelector(templateName)
  181. const form = themeSelectorTemplate.content.firstElementChild
  182. themeSelectorTemplate.replaceWith(form)
  183. form.addEventListener('change', (e) => {
  184. const chosenColorScheme = e.target.value
  185. localStorage.setItem('theme', chosenColorScheme)
  186. toggleTheme(chosenColorScheme)
  187. })
  188. const selectedTheme = localStorage.getItem('theme')
  189. if (selectedTheme && selectedTheme !== 'undefined') {
  190. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  191. }
  192. }
  193. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  194. window.addEventListener('load', () => {
  195. let hasDarkRules = false
  196. for (const styleSheet of Array.from(document.styleSheets)) {
  197. let mediaRules = []
  198. for (const cssRule of styleSheet.cssRules) {
  199. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  200. continue
  201. }
  202. // WARNING: Safari does not have/supports `conditionText`.
  203. if (cssRule.conditionText) {
  204. if (cssRule.conditionText !== prefersColorSchemeDark) {
  205. continue
  206. }
  207. } else {
  208. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  209. continue
  210. }
  211. }
  212. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  213. }
  214. // WARNING: do not try to insert a Rule to a styleSheet you are
  215. // currently iterating on, otherwise the browser will be stuck
  216. // in a infinite loop…
  217. for (const mediaRule of mediaRules) {
  218. styleSheet.insertRule(mediaRule.cssText)
  219. hasDarkRules = true
  220. }
  221. }
  222. if (hasDarkRules) {
  223. loadThemeForm('#theme-selector')
  224. }
  225. })
  226. </script>
  227. </body>
  228. </html>