22 CSS Feature Sections18 / 22

Pure CSSMIT licensed

Feature Comparison Section Included Matrix

A real table doing what a table is for: feature rows down the left, three tiers across the top, a ✓ or an em dash in every cell. Row groups keep forty features readable, the header row sticks while you scroll the body, and the highlighted tier column is detected with :has() rather than a class on every cell — so moving the highlight is a one-attribute change.

Published Updated

Live Demo
Try it

The code

<section class="fs-18" aria-labelledby="fs-18-heading">
  <div class="fs-18__hero" aria-hidden="true">
    <span class="fs-18__wordmark">MERIDIAN CLOUD</span>
    <span class="fs-18__heronav">Platform · Solutions · Partners · Support</span>
  </div>

  <div class="fs-18__stage">
    <div class="fs-18__head">
      <p class="fs-18__eyebrow">Included in every plan</p>
      <h2 class="fs-18__heading" id="fs-18-heading">Feature availability by tier</h2>
      <p class="fs-18__sub">Capabilities only — pricing and commitment terms are in the section below.</p>
    </div>

    <div class="fs-18__scroller">
      <table class="fs-18__table">
        <caption class="fs-18__caption">Feature availability across the Basic, Professional and Enterprise tiers.</caption>
        <thead>
          <tr>
            <th class="fs-18__rowhead" scope="col">Capability</th>
            <th scope="col">Basic</th>
            <th scope="col" data-featured><span class="fs-18__ribbon">Recommended</span>Professional</th>
            <th scope="col">Enterprise</th>
          </tr>
        </thead>
        <tbody>
          <tr class="fs-18__grouprow"><th scope="colgroup" colspan="4">Core platform</th></tr>
          <tr><th class="fs-18__rowhead" scope="row">Projects and environments</th><td>Up to 3</td><td>Unlimited</td><td>Unlimited</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Managed Postgres</th><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Point-in-time recovery</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td>7 days</td><td>35 days</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Custom domains with managed TLS</th><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>

          <tr class="fs-18__grouprow"><th scope="colgroup" colspan="4">Collaboration</th></tr>
          <tr><th class="fs-18__rowhead" scope="row">Seats</th><td>3</td><td>25</td><td>Unlimited</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Role-based access control</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Preview environments per branch</th><td>1</td><td>10</td><td>Unlimited</td></tr>

          <tr class="fs-18__grouprow"><th scope="colgroup" colspan="4">Security and compliance</th></tr>
          <tr><th class="fs-18__rowhead" scope="row">SSO with SAML and OIDC</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
          <tr><th class="fs-18__rowhead" scope="row">SCIM provisioning</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Audit log export</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td>30 days</td><td>7 years</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Customer-managed encryption keys</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>

          <tr class="fs-18__grouprow"><th scope="colgroup" colspan="4">Support</th></tr>
          <tr><th class="fs-18__rowhead" scope="row">Response target</th><td>Community</td><td>4 business hours</td><td>15 minutes, 24/7</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Named technical contact</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
        </tbody>
      </table>
    </div>
    <p class="fs-18__foot">Need a capability that is not listed? <a class="fs-18__link" href="#fs-18-heading">Ask the solutions team</a></p>
  </div>

  <div class="fs-18__below" aria-hidden="true">Pricing and terms · Service level agreement</div>
</section>
.fs-18 {
  width: 100%;
  min-height: 100vh;
  display: block;
  box-sizing: border-box;
  --page: #f2f5fa;
  --page: oklch(0.965 0.008 250);
  --card: #ffffff;
  --ink: #0f1c33;
  --ink: oklch(0.23 0.038 258);
  --muted: #56637c;
  --muted: oklch(0.49 0.03 262);
  --rule: #d3dbe8;
  --rule: oklch(0.87 0.014 255);
  --accent: #1450c8;
  --accent: oklch(0.47 0.19 262);
  --cellpad: 11px;
  --font-display: "Avenir Next Condensed", "Arial Narrow", "Roboto Condensed", system-ui, sans-serif;
  background: var(--page);
  color: var(--ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
}

.fs-18 *,
.fs-18 *::before,
.fs-18 *::after {
  box-sizing: border-box;
}

.fs-18__hero {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  justify-content: space-between;
  align-items: baseline;
  padding: 14px 20px;
  background: var(--card);
  border-block-end: 3px solid var(--accent);
}

.fs-18__wordmark {
  font: 700 13px/1 var(--font-display);
  letter-spacing: 0.16em;
}

.fs-18__heronav {
  font-size: 11.5px;
  color: var(--muted);
}

.fs-18__stage {
  display: grid;
  place-items: start center;
  max-inline-size: 100%;
  padding: clamp(26px, 4.5vw, 52px) 14px;
}

.fs-18__head {
  inline-size: 100%;
  max-inline-size: 70rem;
  min-inline-size: 0;
  margin: 0 0 20px;
}

.fs-18__eyebrow {
  margin: 0 0 10px;
  font: 700 10.5px/1 var(--font-display);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}

.fs-18__heading {
  margin: 0 0 8px;
  font: 700 clamp(24px, 4.4vw, 37px)/1.1 var(--font-display);
  letter-spacing: 0.005em;
  text-wrap: balance;
}

.fs-18__sub {
  margin: 0;
  max-inline-size: 58ch;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  text-wrap: pretty;
}

.fs-18__scroller {
  inline-size: 100%;
  max-inline-size: 70rem;
  min-inline-size: 0;
  overflow-x: auto;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 6px;
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 22px), transparent 100%);
}

.fs-18__table {
  inline-size: 100%;
  min-inline-size: 620px;
  border-collapse: collapse;
  font-size: 13px;
}

.fs-18__caption {
  padding: 12px 14px;
  text-align: start;
  font-size: 11.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  border-block-end: 1px solid var(--rule);
}

.fs-18__table thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  padding: 14px var(--cellpad);
  text-align: center;
  vertical-align: bottom;
  font: 700 12.5px/1.3 var(--font-display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--card);
  border-block-end: 2px solid var(--ink);
}

.fs-18__table thead th[data-featured] {
  position: sticky;
  color: var(--accent);
}

.fs-18__ribbon {
  display: block;
  margin: 0 auto 6px;
  inline-size: fit-content;
  padding: 3px 8px;
  font: 700 9px/1.3 var(--font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
  background: var(--accent);
  border-radius: 2px;
}

.fs-18__rowhead {
  text-align: start !important;
  font-weight: 600;
  min-inline-size: 0;
}

.fs-18__table tbody th,
.fs-18__table tbody td {
  padding: var(--cellpad);
  border-block-end: 1px solid color-mix(in oklab, var(--rule) 65%, transparent);
}

.fs-18__table tbody td {
  text-align: center;
  color: var(--muted);
}

.fs-18__table tbody tr:hover td,
.fs-18__table tbody tr:hover th {
  background: color-mix(in oklab, var(--accent) 5%, var(--card));
}

.fs-18__grouprow th {
  padding: 10px var(--cellpad);
  text-align: start;
  font: 700 10.5px/1 var(--font-display);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink);
  background: color-mix(in oklab, var(--ink) 6%, var(--card));
  border-block-end: 1px solid var(--rule);
}

.fs-18__table:has(thead th:nth-child(3)[data-featured]) tbody td:nth-child(3) {
  background: color-mix(in oklab, var(--accent) 7%, var(--card));
  color: var(--ink);
  font-weight: 600;
}

.fs-18__table:has(thead th:nth-child(3)[data-featured]) tbody tr:hover td:nth-child(3) {
  background: color-mix(in oklab, var(--accent) 12%, var(--card));
}

.fs-18__no {
  color: var(--muted);
  opacity: 0.55;
}

.fs-18__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  clip-path: inset(50%);
  overflow: hidden;
}

.fs-18__foot {
  inline-size: 100%;
  max-inline-size: 70rem;
  margin: 14px 0 0;
  font-size: 13px;
  color: var(--muted);
}

.fs-18__link {
  color: var(--accent);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.fs-18__link:hover {
  color: var(--ink);
}

.fs-18__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.fs-18__below {
  padding: 14px 20px;
  background: var(--card);
  border-block-start: 1px solid var(--rule);
  font-size: 11.5px;
  color: var(--muted);
}

@media (prefers-color-scheme: dark) {
  .fs-18 {
    --page: #0b1220;
    --card: #121a2a;
    --ink: #e9eef8;
    --muted: #93a1bb;
    --rule: #222d42;
    --accent: #5b91ff;
  }

  .fs-18__ribbon {
    color: #0b1220;
  }
}

[data-theme="dark"] .fs-18 {
  --page: #0b1220;
  --card: #121a2a;
  --ink: #e9eef8;
  --muted: #93a1bb;
  --rule: #222d42;
  --accent: #5b91ff;
}

[data-theme="dark"] .fs-18 .fs-18__ribbon {
  color: #0b1220;
}
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 Feature Section 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: Feature Comparison Section Included Matrix
Source: https://codefronts.com/layouts/css-feature-sections/css-feature-comparison-section/

A real table doing what a table is for: feature rows down the left, three tiers across the top, a ✓ or an em dash in every cell. Row groups keep forty features readable, the header row sticks while you scroll the body, and the highlighted tier column is detected with :has() rather than a class on every cell — so moving the highlight is a one-attribute change.
## HTML
```html
<section class="fs-18" aria-labelledby="fs-18-heading">
  <div class="fs-18__hero" aria-hidden="true">
    <span class="fs-18__wordmark">MERIDIAN CLOUD</span>
    <span class="fs-18__heronav">Platform · Solutions · Partners · Support</span>
  </div>

  <div class="fs-18__stage">
    <div class="fs-18__head">
      <p class="fs-18__eyebrow">Included in every plan</p>
      <h2 class="fs-18__heading" id="fs-18-heading">Feature availability by tier</h2>
      <p class="fs-18__sub">Capabilities only — pricing and commitment terms are in the section below.</p>
    </div>

    <div class="fs-18__scroller">
      <table class="fs-18__table">
        <caption class="fs-18__caption">Feature availability across the Basic, Professional and Enterprise tiers.</caption>
        <thead>
          <tr>
            <th class="fs-18__rowhead" scope="col">Capability</th>
            <th scope="col">Basic</th>
            <th scope="col" data-featured><span class="fs-18__ribbon">Recommended</span>Professional</th>
            <th scope="col">Enterprise</th>
          </tr>
        </thead>
        <tbody>
          <tr class="fs-18__grouprow"><th scope="colgroup" colspan="4">Core platform</th></tr>
          <tr><th class="fs-18__rowhead" scope="row">Projects and environments</th><td>Up to 3</td><td>Unlimited</td><td>Unlimited</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Managed Postgres</th><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Point-in-time recovery</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td>7 days</td><td>35 days</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Custom domains with managed TLS</th><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>

          <tr class="fs-18__grouprow"><th scope="colgroup" colspan="4">Collaboration</th></tr>
          <tr><th class="fs-18__rowhead" scope="row">Seats</th><td>3</td><td>25</td><td>Unlimited</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Role-based access control</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Preview environments per branch</th><td>1</td><td>10</td><td>Unlimited</td></tr>

          <tr class="fs-18__grouprow"><th scope="colgroup" colspan="4">Security and compliance</th></tr>
          <tr><th class="fs-18__rowhead" scope="row">SSO with SAML and OIDC</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
          <tr><th class="fs-18__rowhead" scope="row">SCIM provisioning</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Audit log export</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td>30 days</td><td>7 years</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Customer-managed encryption keys</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>

          <tr class="fs-18__grouprow"><th scope="colgroup" colspan="4">Support</th></tr>
          <tr><th class="fs-18__rowhead" scope="row">Response target</th><td>Community</td><td>4 business hours</td><td>15 minutes, 24/7</td></tr>
          <tr><th class="fs-18__rowhead" scope="row">Named technical contact</th><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span class="fs-18__no" aria-hidden="true">—</span><span class="fs-18__sr">Not included</span></td><td><span aria-hidden="true">✓</span><span class="fs-18__sr">Included</span></td></tr>
        </tbody>
      </table>
    </div>
    <p class="fs-18__foot">Need a capability that is not listed? <a class="fs-18__link" href="#fs-18-heading">Ask the solutions team</a></p>
  </div>

  <div class="fs-18__below" aria-hidden="true">Pricing and terms · Service level agreement</div>
</section>
```
## CSS
```css
.fs-18 {
  width: 100%;
  min-height: 100vh;
  display: block;
  box-sizing: border-box;
  --page: #f2f5fa;
  --page: oklch(0.965 0.008 250);
  --card: #ffffff;
  --ink: #0f1c33;
  --ink: oklch(0.23 0.038 258);
  --muted: #56637c;
  --muted: oklch(0.49 0.03 262);
  --rule: #d3dbe8;
  --rule: oklch(0.87 0.014 255);
  --accent: #1450c8;
  --accent: oklch(0.47 0.19 262);
  --cellpad: 11px;
  --font-display: "Avenir Next Condensed", "Arial Narrow", "Roboto Condensed", system-ui, sans-serif;
  background: var(--page);
  color: var(--ink);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
}

.fs-18 *,
.fs-18 *::before,
.fs-18 *::after {
  box-sizing: border-box;
}

.fs-18__hero {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  justify-content: space-between;
  align-items: baseline;
  padding: 14px 20px;
  background: var(--card);
  border-block-end: 3px solid var(--accent);
}

.fs-18__wordmark {
  font: 700 13px/1 var(--font-display);
  letter-spacing: 0.16em;
}

.fs-18__heronav {
  font-size: 11.5px;
  color: var(--muted);
}

.fs-18__stage {
  display: grid;
  place-items: start center;
  max-inline-size: 100%;
  padding: clamp(26px, 4.5vw, 52px) 14px;
}

.fs-18__head {
  inline-size: 100%;
  max-inline-size: 70rem;
  min-inline-size: 0;
  margin: 0 0 20px;
}

.fs-18__eyebrow {
  margin: 0 0 10px;
  font: 700 10.5px/1 var(--font-display);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}

.fs-18__heading {
  margin: 0 0 8px;
  font: 700 clamp(24px, 4.4vw, 37px)/1.1 var(--font-display);
  letter-spacing: 0.005em;
  text-wrap: balance;
}

.fs-18__sub {
  margin: 0;
  max-inline-size: 58ch;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  text-wrap: pretty;
}

.fs-18__scroller {
  inline-size: 100%;
  max-inline-size: 70rem;
  min-inline-size: 0;
  overflow-x: auto;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 6px;
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 22px), transparent 100%);
}

.fs-18__table {
  inline-size: 100%;
  min-inline-size: 620px;
  border-collapse: collapse;
  font-size: 13px;
}

.fs-18__caption {
  padding: 12px 14px;
  text-align: start;
  font-size: 11.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  border-block-end: 1px solid var(--rule);
}

.fs-18__table thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  padding: 14px var(--cellpad);
  text-align: center;
  vertical-align: bottom;
  font: 700 12.5px/1.3 var(--font-display);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--card);
  border-block-end: 2px solid var(--ink);
}

.fs-18__table thead th[data-featured] {
  position: sticky;
  color: var(--accent);
}

.fs-18__ribbon {
  display: block;
  margin: 0 auto 6px;
  inline-size: fit-content;
  padding: 3px 8px;
  font: 700 9px/1.3 var(--font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
  background: var(--accent);
  border-radius: 2px;
}

.fs-18__rowhead {
  text-align: start !important;
  font-weight: 600;
  min-inline-size: 0;
}

.fs-18__table tbody th,
.fs-18__table tbody td {
  padding: var(--cellpad);
  border-block-end: 1px solid color-mix(in oklab, var(--rule) 65%, transparent);
}

.fs-18__table tbody td {
  text-align: center;
  color: var(--muted);
}

.fs-18__table tbody tr:hover td,
.fs-18__table tbody tr:hover th {
  background: color-mix(in oklab, var(--accent) 5%, var(--card));
}

.fs-18__grouprow th {
  padding: 10px var(--cellpad);
  text-align: start;
  font: 700 10.5px/1 var(--font-display);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink);
  background: color-mix(in oklab, var(--ink) 6%, var(--card));
  border-block-end: 1px solid var(--rule);
}

.fs-18__table:has(thead th:nth-child(3)[data-featured]) tbody td:nth-child(3) {
  background: color-mix(in oklab, var(--accent) 7%, var(--card));
  color: var(--ink);
  font-weight: 600;
}

.fs-18__table:has(thead th:nth-child(3)[data-featured]) tbody tr:hover td:nth-child(3) {
  background: color-mix(in oklab, var(--accent) 12%, var(--card));
}

.fs-18__no {
  color: var(--muted);
  opacity: 0.55;
}

.fs-18__sr {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  clip-path: inset(50%);
  overflow: hidden;
}

.fs-18__foot {
  inline-size: 100%;
  max-inline-size: 70rem;
  margin: 14px 0 0;
  font-size: 13px;
  color: var(--muted);
}

.fs-18__link {
  color: var(--accent);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.fs-18__link:hover {
  color: var(--ink);
}

.fs-18__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.fs-18__below {
  padding: 14px 20px;
  background: var(--card);
  border-block-start: 1px solid var(--rule);
  font-size: 11.5px;
  color: var(--muted);
}

@media (prefers-color-scheme: dark) {
  .fs-18 {
    --page: #0b1220;
    --card: #121a2a;
    --ink: #e9eef8;
    --muted: #93a1bb;
    --rule: #222d42;
    --accent: #5b91ff;
  }

  .fs-18__ribbon {
    color: #0b1220;
  }
}

[data-theme="dark"] .fs-18 {
  --page: #0b1220;
  --card: #121a2a;
  --ink: #e9eef8;
  --muted: #93a1bb;
  --rule: #222d42;
  --accent: #5b91ff;
}

[data-theme="dark"] .fs-18 .fs-18__ribbon {
  color: #0b1220;
}
```

How this works

This is tabular data, so it is a table. A grid of divs loses the one thing that makes a comparison matrix usable with a screen reader: header association. <th scope='col'> on the tier names and <th scope='row'> on the feature names means a cell is announced as 'Audit log export, Pro, included' instead of just 'included'. Add a <caption> and the table can also be understood out of context.

Row groups are what make forty rows survivable. Splitting the body into <tbody> sections with a spanning header row per group — Core, Collaboration, Security, Support — gives the eye somewhere to rest and lets a reader jump to the section they care about. Without grouping, a long matrix is a wall, and visitors bounce to the pricing page to guess instead.

:has() removes the per-cell class. Marking a featured tier traditionally means adding a class to one <th> and every <td> in that column. Instead put data-featured on the header cell and select the column with :has() plus nth-child — one rule, and moving the highlight is a single attribute move. It is also the only approach that stays correct when rows are added by a CMS.

The trap is horizontal overflow. Four columns of real feature names cannot fit 320px, and the wrong fix is font-size: 10px. Wrap the table in a scroll container with overflow-x: auto and a mask-fade on the trailing edge so the affordance is visible, keep the row-header column sticky at the inline start, and the matrix stays usable on a phone. Never let the table itself set the page width.

Make it yours

  • Move the highlight by putting data-featured on a different <th>.
  • Add a feature row anywhere — no per-column classes to maintain.
  • Add a fourth tier column; the :has() rule adapts to the new column count.
  • Swap the em dash for an ✕ if absence should read as harder exclusion.
  • Set --cellpad to 14px if your audience prefers a roomier table.
  • Remove the sticky header by dropping the position: sticky rule if the section is short.

Gotchas — read before shipping

  • Rebuilding a comparison matrix out of divs loses header association entirely — a screen reader reads a column of ticks with no context.
  • position: sticky on a <th> needs the cell itself to be sticky, not the row, and it fails outright if any ancestor clips overflow.
  • Colour-only tier highlighting disappears in high-contrast mode; keep the ribbon label and a border.
  • Shrinking the font to make four columns fit at 320px fails the contrast and target-size checks — scroll the table instead.
  • A tick glyph with no accessible text is announced as nothing at all; pair every marker with visually-hidden words.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by :has(), oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 105+.

:has() dictates the floor (Chromium 105, Safari 15.4, Firefox 121) because the featured-column tint is selected with it. Older engines render the full matrix with no column highlight — every value still present and correctly associated through th scope. Native table markup with caption and scope needs no gate at all.

Techniques used in this demo

Search CodeFronts

Loading…