30 CSS Grid Layouts18 / 30

Pure CSSMIT licensed

Kanban Board

A Trello-style board built on the column-flow trick: grid-auto-flow: column + grid-auto-columns sizes every new list identically without declaring counts, the board scroll-snaps horizontally on mobile, and each column's inner card stack scrolls independently while headers stay pinned.

Published Updated

Live Demo
Try it

The code

<section class="cgl-18" aria-label="Kanban board demo">
  <div class="cgl-18__board" role="list" aria-label="Board columns">
    <section class="cgl-18__col" role="listitem"><header><h4>Backlog</h4><span>4</span></header>
      <div class="cgl-18__stack">
        <article class="cgl-18__card" style="--p:oklch(0.7 0.14 250)">Refactor auth flow<em>API</em></article>
        <article class="cgl-18__card" style="--p:oklch(0.75 0.13 150)">Empty-state illustrations<em>Design</em></article>
        <article class="cgl-18__card" style="--p:oklch(0.7 0.14 250)">Rate-limit webhooks<em>API</em></article>
        <article class="cgl-18__card" style="--p:oklch(0.78 0.14 85)">Docs: quickstart rewrite<em>Docs</em></article>
      </div>
    </section>
    <section class="cgl-18__col" role="listitem"><header><h4>In progress</h4><span>2</span></header>
      <div class="cgl-18__stack">
        <article class="cgl-18__card" style="--p:oklch(0.68 0.19 25)">Billing dunning emails<em>Urgent</em></article>
        <article class="cgl-18__card" style="--p:oklch(0.75 0.13 150)">Grid layout collection<em>Design</em></article>
      </div>
    </section>
    <section class="cgl-18__col" role="listitem"><header><h4>Review</h4><span>1</span></header>
      <div class="cgl-18__stack">
        <article class="cgl-18__card" style="--p:oklch(0.7 0.14 250)">Subgrid pricing table<em>API</em></article>
      </div>
    </section>
    <section class="cgl-18__col" role="listitem"><header><h4>Done</h4><span>3</span></header>
      <div class="cgl-18__stack">
        <article class="cgl-18__card is-done" style="--p:oklch(0.6 0.02 250)">Dark mode tokens<em>Design</em></article>
        <article class="cgl-18__card is-done" style="--p:oklch(0.6 0.02 250)">Onboarding checklist<em>Growth</em></article>
        <article class="cgl-18__card is-done" style="--p:oklch(0.6 0.02 250)">CDN migration<em>Infra</em></article>
      </div>
    </section>
  </div>
</section>
.cgl-18,
.cgl-18 *,
.cgl-18 *::before,
.cgl-18 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cgl-18 {
  width: 100%;
  min-height: 100vh;
  --bg: oklch(0.19 0.02 270);
  --col: oklch(0.23 0.025 270);
  --line: oklch(0.32 0.03 270);
  --txt: oklch(0.93 0.01 270);
  --dim: oklch(0.66 0.02 270);
  font-family: system-ui,sans-serif;
  background: var(--bg);
  padding: 1.2rem;
}

.cgl-18 .cgl-18__board {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: clamp(230px,28vw,290px);
  gap: 12px;
  block-size: 340px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: 4px;
  padding-block-end: 8px;
}

.cgl-18 .cgl-18__col {
  scroll-snap-align: start;
  display: grid;
  grid-template-rows: auto minmax(0,1fr);
  background: var(--col);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
}

.cgl-18 .cgl-18__col header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: .75rem .9rem;
  border-block-end: 1px solid var(--line);
}

.cgl-18 h4 {
  font-size: .82rem;
  color: var(--txt);
}

.cgl-18 .cgl-18__col header span {
  font-size: .68rem;
  font-variant-numeric: tabular-nums;
  color: var(--dim);
  background: var(--bg);
  border-radius: 999px;
  padding: .15rem .5rem;
}

.cgl-18 .cgl-18__stack {
  overflow-y: auto;
  padding: .7rem;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cgl-18 .cgl-18__card {
  background: oklch(0.28 0.025 270);
  border: 1px solid var(--line);
  border-inline-start: 3px solid var(--p);
  border-radius: 9px;
  padding: .65rem .75rem;
  font-size: .78rem;
  line-height: 1.4;
  color: var(--txt);
  display: flex;
  flex-direction: column;
  gap: .4rem;
  cursor: grab;
}

.cgl-18 .cgl-18__card:hover {
  border-color: color-mix(in oklab,var(--p) 60%,var(--line));
}

.cgl-18 .cgl-18__card em {
  font-style: normal;
  font-size: .62rem;
  letter-spacing: .06em;
  inline-size: fit-content;
  color: var(--p);
  background: color-mix(in oklab,var(--p) 14%,transparent);
  border-radius: 5px;
  padding: .12rem .4rem;
}

.cgl-18 .cgl-18__card.is-done {
  opacity: .55;
  text-decoration: line-through;
}

.cgl-18 .cgl-18__card.is-done em {
  text-decoration: none;
}
/* No JavaScript — grid-auto-flow: column mints identical columns on demand; scroll-snap makes the board swipeable. */
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: Kanban Board
Source: https://codefronts.com/layouts/css-grid-layouts/kanban-board/

A Trello-style board built on the column-flow trick: grid-auto-flow: column + grid-auto-columns sizes every new list identically without declaring counts, the board scroll-snaps horizontally on mobile, and each column's inner card stack scrolls independently while headers stay pinned.
## HTML
```html
<section class="cgl-18" aria-label="Kanban board demo">
  <div class="cgl-18__board" role="list" aria-label="Board columns">
    <section class="cgl-18__col" role="listitem"><header><h4>Backlog</h4><span>4</span></header>
      <div class="cgl-18__stack">
        <article class="cgl-18__card" style="--p:oklch(0.7 0.14 250)">Refactor auth flow<em>API</em></article>
        <article class="cgl-18__card" style="--p:oklch(0.75 0.13 150)">Empty-state illustrations<em>Design</em></article>
        <article class="cgl-18__card" style="--p:oklch(0.7 0.14 250)">Rate-limit webhooks<em>API</em></article>
        <article class="cgl-18__card" style="--p:oklch(0.78 0.14 85)">Docs: quickstart rewrite<em>Docs</em></article>
      </div>
    </section>
    <section class="cgl-18__col" role="listitem"><header><h4>In progress</h4><span>2</span></header>
      <div class="cgl-18__stack">
        <article class="cgl-18__card" style="--p:oklch(0.68 0.19 25)">Billing dunning emails<em>Urgent</em></article>
        <article class="cgl-18__card" style="--p:oklch(0.75 0.13 150)">Grid layout collection<em>Design</em></article>
      </div>
    </section>
    <section class="cgl-18__col" role="listitem"><header><h4>Review</h4><span>1</span></header>
      <div class="cgl-18__stack">
        <article class="cgl-18__card" style="--p:oklch(0.7 0.14 250)">Subgrid pricing table<em>API</em></article>
      </div>
    </section>
    <section class="cgl-18__col" role="listitem"><header><h4>Done</h4><span>3</span></header>
      <div class="cgl-18__stack">
        <article class="cgl-18__card is-done" style="--p:oklch(0.6 0.02 250)">Dark mode tokens<em>Design</em></article>
        <article class="cgl-18__card is-done" style="--p:oklch(0.6 0.02 250)">Onboarding checklist<em>Growth</em></article>
        <article class="cgl-18__card is-done" style="--p:oklch(0.6 0.02 250)">CDN migration<em>Infra</em></article>
      </div>
    </section>
  </div>
</section>
```
## CSS
```css
.cgl-18,
.cgl-18 *,
.cgl-18 *::before,
.cgl-18 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cgl-18 {
  width: 100%;
  min-height: 100vh;
  --bg: oklch(0.19 0.02 270);
  --col: oklch(0.23 0.025 270);
  --line: oklch(0.32 0.03 270);
  --txt: oklch(0.93 0.01 270);
  --dim: oklch(0.66 0.02 270);
  font-family: system-ui,sans-serif;
  background: var(--bg);
  padding: 1.2rem;
}

.cgl-18 .cgl-18__board {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: clamp(230px,28vw,290px);
  gap: 12px;
  block-size: 340px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: 4px;
  padding-block-end: 8px;
}

.cgl-18 .cgl-18__col {
  scroll-snap-align: start;
  display: grid;
  grid-template-rows: auto minmax(0,1fr);
  background: var(--col);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
}

.cgl-18 .cgl-18__col header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: .75rem .9rem;
  border-block-end: 1px solid var(--line);
}

.cgl-18 h4 {
  font-size: .82rem;
  color: var(--txt);
}

.cgl-18 .cgl-18__col header span {
  font-size: .68rem;
  font-variant-numeric: tabular-nums;
  color: var(--dim);
  background: var(--bg);
  border-radius: 999px;
  padding: .15rem .5rem;
}

.cgl-18 .cgl-18__stack {
  overflow-y: auto;
  padding: .7rem;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cgl-18 .cgl-18__card {
  background: oklch(0.28 0.025 270);
  border: 1px solid var(--line);
  border-inline-start: 3px solid var(--p);
  border-radius: 9px;
  padding: .65rem .75rem;
  font-size: .78rem;
  line-height: 1.4;
  color: var(--txt);
  display: flex;
  flex-direction: column;
  gap: .4rem;
  cursor: grab;
}

.cgl-18 .cgl-18__card:hover {
  border-color: color-mix(in oklab,var(--p) 60%,var(--line));
}

.cgl-18 .cgl-18__card em {
  font-style: normal;
  font-size: .62rem;
  letter-spacing: .06em;
  inline-size: fit-content;
  color: var(--p);
  background: color-mix(in oklab,var(--p) 14%,transparent);
  border-radius: 5px;
  padding: .12rem .4rem;
}

.cgl-18 .cgl-18__card.is-done {
  opacity: .55;
  text-decoration: line-through;
}

.cgl-18 .cgl-18__card.is-done em {
  text-decoration: none;
}
```

## JavaScript
```js
/* No JavaScript — grid-auto-flow: column mints identical columns on demand; scroll-snap makes the board swipeable. */
```

How this works

The board declares NO column count: grid-auto-flow: column; grid-auto-columns: clamp(230px, 28vw, 290px). Every <section> you append becomes a new, identical column — the CMS-proof way to handle boards with unknown list counts. The board itself is the scroll container: overflow-x: auto; scroll-snap-type: x proximity with scroll-padding-inline so snapped columns clear the board's padding.

Each column is its own mini app-frame: grid-template-rows: auto minmax(0,1fr) pins the header while the card stack scrolls under it (the same minmax(0) discipline as the dashboard demo, one level down). Cards carry priority hues via left border + color-mix chips.

Make it yours

  • Column width lives in one clamp() — tune floor/ceiling for your card content.
  • Switch snap to mandatory for paging-style boards (test with trackpads first).
  • Cap board height with any block-size; columns inherit and scroll internally.
  • Add a ghost 'new list' column that's just a button — auto-flow places it like any other.

Gotchas — read before shipping

  • Snap proximity beats mandatory for boards — mandatory fights users who want to rest between columns.
  • The inner stack needs minmax(0,1fr), not 1fr, or long stacks stretch the whole board vertically.
  • overscroll-behavior-x: contain on the board stops horizontal swipes from triggering browser back-navigation.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

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

Scroll snap + auto-flow column are long-universal; clamp() sizing needs 2020+. One of the safest demos here.

Techniques used in this demo

Search CodeFronts

Loading…