30 CSS Grid Layouts05 / 30

Pure CSSMIT licensed

Dynamic Data Grid

A dashboard data table built on grid + subgrid: the header defines the column tracks once, and every row inherits them with grid-template-columns: subgrid — so columns resize gracefully as one unit, numeric columns stay right-aligned, and status pills never wrap the layout. The upgrade path from <table> styling pain.

Published

Live Demo
Try it

The code

<section class="cgl-05" aria-label="Dynamic data grid demo">
  <div class="cgl-05__table" role="table" aria-label="Deploy activity">
    <div class="cgl-05__row cgl-05__row--head" role="row">
      <span role="columnheader">Service</span><span role="columnheader" class="cgl-05__num">Requests</span><span role="columnheader" class="cgl-05__num">p95 ms</span><span role="columnheader" class="cgl-05__num">Error rate</span><span role="columnheader">Status</span>
    </div>
    <div class="cgl-05__row" role="row">
      <span role="cell">edge-gateway</span><span role="cell" class="cgl-05__num">1,204,118</span><span role="cell" class="cgl-05__num">184</span><span role="cell" class="cgl-05__num">0.02%</span><span role="cell"><em class="cgl-05__pill cgl-05__pill--ok">Healthy</em></span>
    </div>
    <div class="cgl-05__row" role="row">
      <span role="cell">auth-service</span><span role="cell" class="cgl-05__num">386,402</span><span role="cell" class="cgl-05__num">312</span><span role="cell" class="cgl-05__num">0.14%</span><span role="cell"><em class="cgl-05__pill cgl-05__pill--warn">Degraded</em></span>
    </div>
    <div class="cgl-05__row" role="row">
      <span role="cell">billing-worker</span><span role="cell" class="cgl-05__num">92,377</span><span role="cell" class="cgl-05__num">96</span><span role="cell" class="cgl-05__num">0.00%</span><span role="cell"><em class="cgl-05__pill cgl-05__pill--ok">Healthy</em></span>
    </div>
    <div class="cgl-05__row" role="row">
      <span role="cell">search-indexer</span><span role="cell" class="cgl-05__num">14,051</span><span role="cell" class="cgl-05__num">1,240</span><span role="cell" class="cgl-05__num">2.31%</span><span role="cell"><em class="cgl-05__pill cgl-05__pill--down">Failing</em></span>
    </div>
  </div>
</section>
.cgl-05,
.cgl-05 *,
.cgl-05 *::before,
.cgl-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cgl-05 {
  width: 100%;
  min-height: 100vh;
  --bg: oklch(0.15 0.015 160);
  --txt: oklch(0.93 0.02 150);
  --dim: oklch(0.66 0.03 155);
  --grid-line: oklch(0.3 0.04 160);
  --green: oklch(0.8 0.17 150);
  font-family: ui-monospace,'Cascadia Code',Menlo,monospace;
  background: var(--bg);
  padding: 1.4rem;
  color: var(--txt);
}

.cgl-05 .cgl-05__table {
  display: grid;
  grid-template-columns: minmax(150px,1.8fr) repeat(2,minmax(80px,1fr)) minmax(90px,1fr) minmax(84px,max-content);
  border: 1px solid var(--grid-line);
  border-radius: 12px;
  overflow: hidden;
  background: oklch(0.18 0.02 160);
}

.cgl-05 .cgl-05__row {
  grid-column: 1/-1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
  column-gap: 14px;
  padding-block: .7rem;
  padding-inline: 1rem;
  border-block-start: 1px solid var(--grid-line);
  font-size: .82rem;
}

.cgl-05 .cgl-05__row--head {
  border-block-start: none;
  background: oklch(0.22 0.025 160);
  font-size: .68rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
}

.cgl-05 .cgl-05__row:not(.cgl-05__row--head):hover {
  background: oklch(0.21 0.025 160);
}

.cgl-05 .cgl-05__num {
  text-align: end;
  font-variant-numeric: tabular-nums;
  color: var(--dim);
}

.cgl-05 .cgl-05__row--head .cgl-05__num {
  color: var(--dim);
}

.cgl-05 .cgl-05__pill {
  font-style: normal;
  font-size: .68rem;
  padding: .22rem .55rem;
  border-radius: 999px;
  border: 1px solid;
}

.cgl-05 .cgl-05__pill--ok {
  color: var(--green);
  border-color: color-mix(in oklab,var(--green) 45%,transparent);
  background: color-mix(in oklab,var(--green) 12%,transparent);
}

.cgl-05 .cgl-05__pill--warn {
  color: oklch(0.83 0.15 85);
  border-color: oklch(0.83 0.15 85/.45);
  background: oklch(0.83 0.15 85/.12);
}

.cgl-05 .cgl-05__pill--down {
  color: oklch(0.72 0.19 25);
  border-color: oklch(0.72 0.19 25/.45);
  background: oklch(0.72 0.19 25/.12);
}

@media (max-width: 600px) {
  .cgl-05 .cgl-05__table {
    grid-template-columns: minmax(120px,1.6fr) minmax(80px,1fr) minmax(84px,max-content);
  }

  .cgl-05 .cgl-05__row>:nth-child(3),
    .cgl-05 .cgl-05__row>:nth-child(4) {
    display: none;
  }
}

@supports not (grid-template-columns: subgrid) {
  .cgl-05 .cgl-05__row {
    grid-template-columns: minmax(150px,1.8fr) repeat(2,minmax(80px,1fr)) minmax(90px,1fr) minmax(84px,max-content);
  }
}
/* No JavaScript — one master track list; every row inherits it via grid-template-columns: subgrid. */
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 Grid Layout 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: Dynamic Data Grid
Source: https://codefronts.com/layouts/css-grid-layouts/dynamic-data-grid/

A dashboard data table built on grid + subgrid: the header defines the column tracks once, and every row inherits them with grid-template-columns: subgrid — so columns resize gracefully as one unit, numeric columns stay right-aligned, and status pills never wrap the layout. The upgrade path from <table> styling pain.
## HTML
```html
<section class="cgl-05" aria-label="Dynamic data grid demo">
  <div class="cgl-05__table" role="table" aria-label="Deploy activity">
    <div class="cgl-05__row cgl-05__row--head" role="row">
      <span role="columnheader">Service</span><span role="columnheader" class="cgl-05__num">Requests</span><span role="columnheader" class="cgl-05__num">p95 ms</span><span role="columnheader" class="cgl-05__num">Error rate</span><span role="columnheader">Status</span>
    </div>
    <div class="cgl-05__row" role="row">
      <span role="cell">edge-gateway</span><span role="cell" class="cgl-05__num">1,204,118</span><span role="cell" class="cgl-05__num">184</span><span role="cell" class="cgl-05__num">0.02%</span><span role="cell"><em class="cgl-05__pill cgl-05__pill--ok">Healthy</em></span>
    </div>
    <div class="cgl-05__row" role="row">
      <span role="cell">auth-service</span><span role="cell" class="cgl-05__num">386,402</span><span role="cell" class="cgl-05__num">312</span><span role="cell" class="cgl-05__num">0.14%</span><span role="cell"><em class="cgl-05__pill cgl-05__pill--warn">Degraded</em></span>
    </div>
    <div class="cgl-05__row" role="row">
      <span role="cell">billing-worker</span><span role="cell" class="cgl-05__num">92,377</span><span role="cell" class="cgl-05__num">96</span><span role="cell" class="cgl-05__num">0.00%</span><span role="cell"><em class="cgl-05__pill cgl-05__pill--ok">Healthy</em></span>
    </div>
    <div class="cgl-05__row" role="row">
      <span role="cell">search-indexer</span><span role="cell" class="cgl-05__num">14,051</span><span role="cell" class="cgl-05__num">1,240</span><span role="cell" class="cgl-05__num">2.31%</span><span role="cell"><em class="cgl-05__pill cgl-05__pill--down">Failing</em></span>
    </div>
  </div>
</section>
```
## CSS
```css
.cgl-05,
.cgl-05 *,
.cgl-05 *::before,
.cgl-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cgl-05 {
  width: 100%;
  min-height: 100vh;
  --bg: oklch(0.15 0.015 160);
  --txt: oklch(0.93 0.02 150);
  --dim: oklch(0.66 0.03 155);
  --grid-line: oklch(0.3 0.04 160);
  --green: oklch(0.8 0.17 150);
  font-family: ui-monospace,'Cascadia Code',Menlo,monospace;
  background: var(--bg);
  padding: 1.4rem;
  color: var(--txt);
}

.cgl-05 .cgl-05__table {
  display: grid;
  grid-template-columns: minmax(150px,1.8fr) repeat(2,minmax(80px,1fr)) minmax(90px,1fr) minmax(84px,max-content);
  border: 1px solid var(--grid-line);
  border-radius: 12px;
  overflow: hidden;
  background: oklch(0.18 0.02 160);
}

.cgl-05 .cgl-05__row {
  grid-column: 1/-1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
  column-gap: 14px;
  padding-block: .7rem;
  padding-inline: 1rem;
  border-block-start: 1px solid var(--grid-line);
  font-size: .82rem;
}

.cgl-05 .cgl-05__row--head {
  border-block-start: none;
  background: oklch(0.22 0.025 160);
  font-size: .68rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
}

.cgl-05 .cgl-05__row:not(.cgl-05__row--head):hover {
  background: oklch(0.21 0.025 160);
}

.cgl-05 .cgl-05__num {
  text-align: end;
  font-variant-numeric: tabular-nums;
  color: var(--dim);
}

.cgl-05 .cgl-05__row--head .cgl-05__num {
  color: var(--dim);
}

.cgl-05 .cgl-05__pill {
  font-style: normal;
  font-size: .68rem;
  padding: .22rem .55rem;
  border-radius: 999px;
  border: 1px solid;
}

.cgl-05 .cgl-05__pill--ok {
  color: var(--green);
  border-color: color-mix(in oklab,var(--green) 45%,transparent);
  background: color-mix(in oklab,var(--green) 12%,transparent);
}

.cgl-05 .cgl-05__pill--warn {
  color: oklch(0.83 0.15 85);
  border-color: oklch(0.83 0.15 85/.45);
  background: oklch(0.83 0.15 85/.12);
}

.cgl-05 .cgl-05__pill--down {
  color: oklch(0.72 0.19 25);
  border-color: oklch(0.72 0.19 25/.45);
  background: oklch(0.72 0.19 25/.12);
}

@media (max-width: 600px) {
  .cgl-05 .cgl-05__table {
    grid-template-columns: minmax(120px,1.6fr) minmax(80px,1fr) minmax(84px,max-content);
  }

  .cgl-05 .cgl-05__row>:nth-child(3),
    .cgl-05 .cgl-05__row>:nth-child(4) {
    display: none;
  }
}

@supports not (grid-template-columns: subgrid) {
  .cgl-05 .cgl-05__row {
    grid-template-columns: minmax(150px,1.8fr) repeat(2,minmax(80px,1fr)) minmax(90px,1fr) minmax(84px,max-content);
  }
}
```

## JavaScript
```js
/* No JavaScript — one master track list; every row inherits it via grid-template-columns: subgrid. */
```

How this works

The list container declares the master tracks: grid-template-columns: minmax(150px, 1.8fr) repeat(2, minmax(80px, 1fr)) minmax(90px, 1fr) minmax(84px, max-content). Each row then sets grid-column: 1 / -1 and grid-template-columns: subgrid — its cells snap to the parent's tracks with zero per-row duplication. Change one track list, every row follows.

Semantics are preserved with ARIA grid roles (role="table"/"row"/"columnheader"/"cell") since the visual layer is divs. Numeric cells use font-variant-numeric: tabular-nums and text-align: end so figures align by digit — the detail that separates dashboards from spreadsheeted chaos. The last column is max-content: status pills define their own width and never squash.

Make it yours

  • Add a column by extending the ONE master track list — every row updates automatically.
  • Make any column priority-hide on mobile with the included @media display:none pattern (cell + header pair).
  • Zebra-stripe with :nth-child(even) on rows — one declaration.
  • Swap the pill palette by rotating the three oklch hues.

Gotchas — read before shipping

  • Rows must span the full parent (grid-column: 1 / -1) before subgrid can inherit tracks — forgetting the span is the classic silent failure.
  • If real tabular data semantics matter (sorting, screen-reader table nav), keep a native <table> and apply grid to tr instead — the ARIA-role version here is for app-style lists.
  • max-content columns never shrink; reserve them for genuinely fixed content like pills and icons.

Browser support

ChromeSafariFirefoxEdge
117+16+71+117+

subgrid is the gate — universal in evergreens since late 2023. For older targets, repeat the track list on each row (works everywhere, just less DRY).

Techniques used in this demo

Search CodeFronts

Loading…