Pure CSSMIT licensed

Sidebar Navigation Is Not a Tab List

Applying role="tablist" to site navigation because it looks like a vertical strip is a frequent and serious mistake. Tabs promise that activating an item swaps a panel in place; navigation promises a page change. Get it wrong and a screen-reader user is told the wrong thing about what will happen, and inherits arrow-key conventions navigation should not have. The rail here is the navigation build: a named landmark, a list of links, and aria-current="page" on the active one.

Live Demo
Plain CSSTailwind v4
Try it

Mosaic by Cruip: a complete Tailwind admin dashboard template in HTML, React, Next.js and Vue · $69

Cruip · Mosaic by Cruip · Affiliate link

View Mosaic dashboard

The code

<section class="sdb-04" aria-label="Quill learning centre">
  <div class="sdb-04__theme">
    <input class="sdb-04__theme-input" type="checkbox" id="sdb-04-theme">
    <label class="sdb-04__theme-label" for="sdb-04-theme">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
      <span class="sdb-04__sr">Toggle dark theme</span>
    </label>
  </div>

  <div class="sdb-04__app">
    <nav class="sdb-04__rail" aria-label="Quill learning centre">
      <p class="sdb-04__brand">Quill</p>
      <ul class="sdb-04__list">
        <li><a class="sdb-04__link" href="#sdb-04-main" aria-current="page">Getting started</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Deployments</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Billing</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Audit log</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Integrations</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Certification</a></li>
      </ul>
    </nav>

    <main class="sdb-04__main" id="sdb-04-main">
      <p class="sdb-04__eyebrow">Course &middot; 6 lessons</p>
      <h2 class="sdb-04__title">Getting started</h2>
      <div class="sdb-04__lessons">
        <article class="sdb-04__lesson">
          <p class="sdb-04__lesson-n">01</p>
          <h3 class="sdb-04__lesson-h">Create your first workspace</h3>
          <p class="sdb-04__lesson-m">8 min &middot; video</p>
        </article>
        <article class="sdb-04__lesson">
          <p class="sdb-04__lesson-n">02</p>
          <h3 class="sdb-04__lesson-h">Invite a teammate and set roles</h3>
          <p class="sdb-04__lesson-m">5 min &middot; reading</p>
        </article>
        <article class="sdb-04__lesson">
          <p class="sdb-04__lesson-n">03</p>
          <h3 class="sdb-04__lesson-h">Connect a repository</h3>
          <p class="sdb-04__lesson-m">11 min &middot; walkthrough</p>
        </article>
        <article class="sdb-04__lesson">
          <p class="sdb-04__lesson-n">04</p>
          <h3 class="sdb-04__lesson-h">Ship a preview environment</h3>
          <p class="sdb-04__lesson-m">14 min &middot; walkthrough</p>
        </article>
      </div>
    </main>
  </div>
</section>
.sdb-04 {
  --sdb-04-bg: #f4f4f5;
  --sdb-04-surf: #ffffff;
  --sdb-04-surf2: #ebebed;
  --sdb-04-line: #d4d4d8;
  --sdb-04-ink: #18181b;
  --sdb-04-mut: #5f5f68;
  --sdb-04-acc: #6d3ce0;
  --sdb-04-acc: oklch(52% 0.22 291);
  --sdb-04-acc-soft: #ece4fd;
  --sdb-04-font-display: "Archivo Narrow", "Roboto Condensed", "Helvetica Neue Condensed", ui-sans-serif, system-ui, sans-serif;
  width: 100%;
  min-height: 100vh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--sdb-04-bg);
  color: var(--sdb-04-ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.55;
}

.sdb-04 *,
.sdb-04 *::before,
.sdb-04 *::after {
  box-sizing: border-box;
}

.sdb-04:has(.sdb-04__theme-input:checked) {
  --sdb-04-bg: #0e0e11;
  --sdb-04-surf: #17171b;
  --sdb-04-surf2: #1f1f25;
  --sdb-04-line: #2c2c33;
  --sdb-04-ink: #f2f2f5;
  --sdb-04-mut: #9d9da8;
  --sdb-04-acc: #b79bff;
  --sdb-04-acc: oklch(80% 0.13 292);
  --sdb-04-acc-soft: #241a3d;
}

@media (prefers-color-scheme: dark) {
  .sdb-04 {
    --sdb-04-bg: #0e0e11;
    --sdb-04-surf: #17171b;
    --sdb-04-surf2: #1f1f25;
    --sdb-04-line: #2c2c33;
    --sdb-04-ink: #f2f2f5;
    --sdb-04-mut: #9d9da8;
    --sdb-04-acc: #b79bff;
    --sdb-04-acc: oklch(80% 0.13 292);
    --sdb-04-acc-soft: #241a3d;
  }

  .sdb-04:has(.sdb-04__theme-input:checked) {
    --sdb-04-bg: #f4f4f5;
    --sdb-04-surf: #ffffff;
    --sdb-04-surf2: #ebebed;
    --sdb-04-line: #d4d4d8;
    --sdb-04-ink: #18181b;
    --sdb-04-mut: #5f5f68;
    --sdb-04-acc: #6d3ce0;
    --sdb-04-acc: oklch(52% 0.22 291);
    --sdb-04-acc-soft: #ece4fd;
  }
}

@media (prefers-color-scheme: light) {
  .sdb-04:has(.sdb-04__theme-input:checked) {
    --sdb-04-bg: #0e0e11;
    --sdb-04-surf: #17171b;
    --sdb-04-surf2: #1f1f25;
    --sdb-04-line: #2c2c33;
    --sdb-04-ink: #f2f2f5;
    --sdb-04-mut: #9d9da8;
    --sdb-04-acc: #b79bff;
    --sdb-04-acc: oklch(80% 0.13 292);
    --sdb-04-acc-soft: #241a3d;
  }
}

.sdb-04__theme {
  position: absolute;
  inset-block-start: 0.85rem;
  inset-inline-end: 0.85rem;
  z-index: 60;
}

.sdb-04__theme-input {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.sdb-04__theme-label {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border: 0;
  background: var(--sdb-04-surf2);
  color: var(--sdb-04-ink);
  cursor: pointer;
}

.sdb-04__theme-label svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}

.sdb-04__theme-input:focus-visible + .sdb-04__theme-label {
  outline: 2px solid var(--sdb-04-acc);
  outline-offset: 2px;
}

.sdb-04__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.sdb-04__app {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-block-size: 100vh;
}

@media (min-width: 46rem) {
  .sdb-04__app {
    grid-template-columns: 15rem minmax(0, 1fr);
  }
}

.sdb-04__rail {
  min-inline-size: 0;
  padding: 1rem 0.5rem;
  background: var(--sdb-04-surf);
  border-block-end: 1px solid var(--sdb-04-line);
}

@media (min-width: 46rem) {
  .sdb-04__rail {
    border-block-end: 0;
    border-inline-end: 1px solid var(--sdb-04-line);
  }
}

.sdb-04__brand {
  margin: 0 0 0.9rem;
  padding-inline: 0.8rem;
  font-family: var(--sdb-04-font-display);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.sdb-04__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.sdb-04__link {
  display: flex;
  align-items: center;
  min-block-size: 2.75rem;
  padding: 0.5rem 0.8rem;
  background: var(--sdb-04-surf);
  color: var(--sdb-04-ink);
  text-decoration: none;
  font-size: 0.92rem;
  min-inline-size: 0;
}

.sdb-04__link:hover {
  background: var(--sdb-04-acc-soft);
}

.sdb-04__link:focus-visible {
  outline: 2px solid var(--sdb-04-acc);
  outline-offset: -2px;
}

.sdb-04__link[aria-current="page"] {
  background: var(--sdb-04-acc-soft);
  color: var(--sdb-04-acc);
  font-weight: 650;
  box-shadow: inset 3px 0 0 var(--sdb-04-acc);
}

.sdb-04__main {
  min-inline-size: 0;
  padding: 1.5rem 1.25rem 2.5rem;
  align-content: start;
}

@media (min-width: 46rem) {
  .sdb-04__main {
    padding: 2.5rem 2.25rem 3rem;
  }
}

.sdb-04__eyebrow {
  margin: 0 0 0.35rem;
  font-family: var(--sdb-04-font-display);
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sdb-04-mut);
}

.sdb-04__title {
  margin: 0 0 1.4rem;
  font-family: var(--sdb-04-font-display);
  font-size: clamp(1.6rem, 4.4vw, 2.4rem);
  line-height: 1.05;
  text-transform: uppercase;
}

.sdb-04__lessons {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  background: var(--sdb-04-line);
  border: 1px solid var(--sdb-04-line);
}

@media (min-width: 52rem) {
  .sdb-04__lessons {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.sdb-04__lesson {
  min-inline-size: 0;
  padding: 1rem;
  background: var(--sdb-04-surf);
}

.sdb-04__lesson-n {
  margin: 0 0 0.4rem;
  font-family: var(--sdb-04-font-display);
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  color: var(--sdb-04-acc);
}

.sdb-04__lesson-h {
  margin: 0 0 0.3rem;
  font-size: 1rem;
  line-height: 1.25;
}

.sdb-04__lesson-m {
  margin: 0;
  font-size: 0.83rem;
  color: var(--sdb-04-mut);
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Sidebar Navigation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Sidebar Navigation Is Not a Tab List
Source: https://codefronts.com/navigation/css-sidebar-navigation/sidebar-navigation-is-not-a-tab-list/

Applying role="tablist" to site navigation because it looks like a vertical strip is a frequent and serious mistake. Tabs promise that activating an item swaps a panel in place; navigation promises a page change. Get it wrong and a screen-reader user is told the wrong thing about what will happen, and inherits arrow-key conventions navigation should not have. The rail here is the navigation build: a named landmark, a list of links, and aria-current="page" on the active one.
## HTML
```html
<section class="sdb-04" aria-label="Quill learning centre">
  <div class="sdb-04__theme">
    <input class="sdb-04__theme-input" type="checkbox" id="sdb-04-theme">
    <label class="sdb-04__theme-label" for="sdb-04-theme">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 0 0 17Z" fill="currentColor"/></svg>
      <span class="sdb-04__sr">Toggle dark theme</span>
    </label>
  </div>

  <div class="sdb-04__app">
    <nav class="sdb-04__rail" aria-label="Quill learning centre">
      <p class="sdb-04__brand">Quill</p>
      <ul class="sdb-04__list">
        <li><a class="sdb-04__link" href="#sdb-04-main" aria-current="page">Getting started</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Deployments</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Billing</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Audit log</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Integrations</a></li>
        <li><a class="sdb-04__link" href="#sdb-04-main">Certification</a></li>
      </ul>
    </nav>

    <main class="sdb-04__main" id="sdb-04-main">
      <p class="sdb-04__eyebrow">Course &middot; 6 lessons</p>
      <h2 class="sdb-04__title">Getting started</h2>
      <div class="sdb-04__lessons">
        <article class="sdb-04__lesson">
          <p class="sdb-04__lesson-n">01</p>
          <h3 class="sdb-04__lesson-h">Create your first workspace</h3>
          <p class="sdb-04__lesson-m">8 min &middot; video</p>
        </article>
        <article class="sdb-04__lesson">
          <p class="sdb-04__lesson-n">02</p>
          <h3 class="sdb-04__lesson-h">Invite a teammate and set roles</h3>
          <p class="sdb-04__lesson-m">5 min &middot; reading</p>
        </article>
        <article class="sdb-04__lesson">
          <p class="sdb-04__lesson-n">03</p>
          <h3 class="sdb-04__lesson-h">Connect a repository</h3>
          <p class="sdb-04__lesson-m">11 min &middot; walkthrough</p>
        </article>
        <article class="sdb-04__lesson">
          <p class="sdb-04__lesson-n">04</p>
          <h3 class="sdb-04__lesson-h">Ship a preview environment</h3>
          <p class="sdb-04__lesson-m">14 min &middot; walkthrough</p>
        </article>
      </div>
    </main>
  </div>
</section>
```
## CSS
```css
.sdb-04 {
  --sdb-04-bg: #f4f4f5;
  --sdb-04-surf: #ffffff;
  --sdb-04-surf2: #ebebed;
  --sdb-04-line: #d4d4d8;
  --sdb-04-ink: #18181b;
  --sdb-04-mut: #5f5f68;
  --sdb-04-acc: #6d3ce0;
  --sdb-04-acc: oklch(52% 0.22 291);
  --sdb-04-acc-soft: #ece4fd;
  --sdb-04-font-display: "Archivo Narrow", "Roboto Condensed", "Helvetica Neue Condensed", ui-sans-serif, system-ui, sans-serif;
  width: 100%;
  min-height: 100vh;
  display: block;
  position: relative;
  box-sizing: border-box;
  background: var(--sdb-04-bg);
  color: var(--sdb-04-ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  line-height: 1.55;
}

.sdb-04 *,
.sdb-04 *::before,
.sdb-04 *::after {
  box-sizing: border-box;
}

.sdb-04:has(.sdb-04__theme-input:checked) {
  --sdb-04-bg: #0e0e11;
  --sdb-04-surf: #17171b;
  --sdb-04-surf2: #1f1f25;
  --sdb-04-line: #2c2c33;
  --sdb-04-ink: #f2f2f5;
  --sdb-04-mut: #9d9da8;
  --sdb-04-acc: #b79bff;
  --sdb-04-acc: oklch(80% 0.13 292);
  --sdb-04-acc-soft: #241a3d;
}

@media (prefers-color-scheme: dark) {
  .sdb-04 {
    --sdb-04-bg: #0e0e11;
    --sdb-04-surf: #17171b;
    --sdb-04-surf2: #1f1f25;
    --sdb-04-line: #2c2c33;
    --sdb-04-ink: #f2f2f5;
    --sdb-04-mut: #9d9da8;
    --sdb-04-acc: #b79bff;
    --sdb-04-acc: oklch(80% 0.13 292);
    --sdb-04-acc-soft: #241a3d;
  }

  .sdb-04:has(.sdb-04__theme-input:checked) {
    --sdb-04-bg: #f4f4f5;
    --sdb-04-surf: #ffffff;
    --sdb-04-surf2: #ebebed;
    --sdb-04-line: #d4d4d8;
    --sdb-04-ink: #18181b;
    --sdb-04-mut: #5f5f68;
    --sdb-04-acc: #6d3ce0;
    --sdb-04-acc: oklch(52% 0.22 291);
    --sdb-04-acc-soft: #ece4fd;
  }
}

@media (prefers-color-scheme: light) {
  .sdb-04:has(.sdb-04__theme-input:checked) {
    --sdb-04-bg: #0e0e11;
    --sdb-04-surf: #17171b;
    --sdb-04-surf2: #1f1f25;
    --sdb-04-line: #2c2c33;
    --sdb-04-ink: #f2f2f5;
    --sdb-04-mut: #9d9da8;
    --sdb-04-acc: #b79bff;
    --sdb-04-acc: oklch(80% 0.13 292);
    --sdb-04-acc-soft: #241a3d;
  }
}

.sdb-04__theme {
  position: absolute;
  inset-block-start: 0.85rem;
  inset-inline-end: 0.85rem;
  z-index: 60;
}

.sdb-04__theme-input {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.sdb-04__theme-label {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border: 0;
  background: var(--sdb-04-surf2);
  color: var(--sdb-04-ink);
  cursor: pointer;
}

.sdb-04__theme-label svg {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}

.sdb-04__theme-input:focus-visible + .sdb-04__theme-label {
  outline: 2px solid var(--sdb-04-acc);
  outline-offset: 2px;
}

.sdb-04__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.sdb-04__app {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-block-size: 100vh;
}

@media (min-width: 46rem) {
  .sdb-04__app {
    grid-template-columns: 15rem minmax(0, 1fr);
  }
}

.sdb-04__rail {
  min-inline-size: 0;
  padding: 1rem 0.5rem;
  background: var(--sdb-04-surf);
  border-block-end: 1px solid var(--sdb-04-line);
}

@media (min-width: 46rem) {
  .sdb-04__rail {
    border-block-end: 0;
    border-inline-end: 1px solid var(--sdb-04-line);
  }
}

.sdb-04__brand {
  margin: 0 0 0.9rem;
  padding-inline: 0.8rem;
  font-family: var(--sdb-04-font-display);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.sdb-04__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.sdb-04__link {
  display: flex;
  align-items: center;
  min-block-size: 2.75rem;
  padding: 0.5rem 0.8rem;
  background: var(--sdb-04-surf);
  color: var(--sdb-04-ink);
  text-decoration: none;
  font-size: 0.92rem;
  min-inline-size: 0;
}

.sdb-04__link:hover {
  background: var(--sdb-04-acc-soft);
}

.sdb-04__link:focus-visible {
  outline: 2px solid var(--sdb-04-acc);
  outline-offset: -2px;
}

.sdb-04__link[aria-current="page"] {
  background: var(--sdb-04-acc-soft);
  color: var(--sdb-04-acc);
  font-weight: 650;
  box-shadow: inset 3px 0 0 var(--sdb-04-acc);
}

.sdb-04__main {
  min-inline-size: 0;
  padding: 1.5rem 1.25rem 2.5rem;
  align-content: start;
}

@media (min-width: 46rem) {
  .sdb-04__main {
    padding: 2.5rem 2.25rem 3rem;
  }
}

.sdb-04__eyebrow {
  margin: 0 0 0.35rem;
  font-family: var(--sdb-04-font-display);
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sdb-04-mut);
}

.sdb-04__title {
  margin: 0 0 1.4rem;
  font-family: var(--sdb-04-font-display);
  font-size: clamp(1.6rem, 4.4vw, 2.4rem);
  line-height: 1.05;
  text-transform: uppercase;
}

.sdb-04__lessons {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  background: var(--sdb-04-line);
  border: 1px solid var(--sdb-04-line);
}

@media (min-width: 52rem) {
  .sdb-04__lessons {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.sdb-04__lesson {
  min-inline-size: 0;
  padding: 1rem;
  background: var(--sdb-04-surf);
}

.sdb-04__lesson-n {
  margin: 0 0 0.4rem;
  font-family: var(--sdb-04-font-display);
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  color: var(--sdb-04-acc);
}

.sdb-04__lesson-h {
  margin: 0 0 0.3rem;
  font-size: 1rem;
  line-height: 1.25;
}

.sdb-04__lesson-m {
  margin: 0;
  font-size: 0.83rem;
  color: var(--sdb-04-mut);
}
```

How this works

The two roles make different promises and users act on them. role="tab" tells a screen-reader user "tab 2 of 5, selected" — that this control belongs to a set, that one member is active, and crucially that activating another will replace a panel without leaving the page. A link inside <nav> announces "link, current page", which promises a navigation. When the markup and the outcome disagree, the user's mental model breaks in a way they cannot see or diagnose.

The wrong role also drags in the wrong keyboard contract. The tabs pattern expects arrow keys to move between tabs, usually activating as they go, with the whole tablist as a single tab stop. Navigation expects each link to be its own tab stop and arrow keys to do nothing special. Ship role="tablist" on a sidebar and either you implement arrow-key activation — so arrowing past an item navigates away, which is hostile — or you do not, and you have a tablist that fails its own pattern.

The decision rule is one question about what happens on activation. If content swaps in place with no URL change and no page load, it is tabs, and css-vertical-tabs covers those rails including the icon-only mini sidebar. If the URL changes, or a route loads, or the item is a link a user could open in a new window, it is navigation — <nav>, <ul>, <a>, aria-current="page".

The trap is that the visual is identical, so nothing catches it. Both rails in this demo are styled the same way; a screenshot review, a design QA pass and a visual regression suite all pass. The only thing that surfaces the difference is reading the accessibility tree or listening to the announcement, which is why this belongs in a code review checklist rather than a design one. A cheap check: if the sidebar item cannot sensibly be middle-clicked into a new tab, ask why it is in a sidebar at all.

Make it yours

  • Swap the sample labels for your own product areas; the rail is a plain list of links.
  • Delete the lesson grid and keep the rail alone if navigation is all you need.
  • Add aria-current="true" instead of "page" if the rail marks a step in a flow rather than the current page.
  • Restyle the active item by editing the [aria-current="page"] rule; there is no parallel active class to keep in sync.
  • Give the nav rail a group heading with aria-labelledby instead of aria-label when the heading is already visible on screen.
  • Turn the rail into a collapsible group with <details>, as in demo 01, without changing the role decision.

Gotchas — read before shipping

  • role="tablist" on navigation announces "tab 2 of 5" and promises a panel swap. The user then finds the page has changed, which is exactly the kind of mismatch that makes people stop trusting the announcements.
  • aria-selected is meaningless outside a composite widget like tabs, a listbox or a grid. On a link it is either ignored or reported as a state the user cannot act on.
  • A tablist implies arrow-key movement and a single tab stop. Half-implementing it — the role without the keyboard behaviour — is worse than not using it, because assistive technology now advertises interactions that do not work.
  • Both markups look identical on screen, so visual review, screenshot diffs and design QA all miss this entirely. It only shows up in the accessibility tree or in a screen reader.

Browser support

ChromeSafariFirefoxEdge
111+15.4+121+111+

oklch() and the :has() theme toggle set the floor; the roles and attributes discussed have no version floor at all and the rail is flexbox and grid, Baseline since 2017. Engines below the oklch() floor take the sRGB hex declared first on every token and keep the operating-system colour scheme.

Techniques used in this demo

Search CodeFronts

Loading…