16 CSS Two-Column Layouts15 / 16

Pure CSSMIT licensed

CSS Two Column Layout Auto Width

Content-sized first column, fluid second: grid-template-columns: auto 1fr (and its stricter sibling max-content 1fr) let a badge, timestamp or keyboard shortcut set its own width while the text takes the rest — shown as a release changelog and a shortcuts panel where every label column self-aligns to the widest item.

Published

Live Demo
Try it

The code

<section class="tcl-15" aria-label="Auto width two column layout demo">
  <div class="tcl-15__wrap">
    <div class="tcl-15__panel">
      <header class="tcl-15__phead"><h2>Changelog</h2><span class="tcl-15__spec">auto 1fr</span></header>
      <div class="tcl-15__log">
        <span class="tcl-15__ver tcl-15__ver--new">v3.2.0</span><p><b>Container query cards.</b> Card components now respond to their wrapper width — the same card works in feeds, rails and modals.</p>
        <span class="tcl-15__ver">v3.1.4</span><p><b>Fix:</b> sticky sidebars no longer unpin inside overflow-clipped sections.</p>
        <span class="tcl-15__ver">v3.1.0</span><p><b>Scroll-driven progress.</b> Reading indicators moved off JavaScript onto <code>animation-timeline</code>.</p>
        <span class="tcl-15__ver">v3.0.2</span><p><b>Fix:</b> form grids keep 44px hit targets at 200% zoom.</p>
      </div>
    </div>
    <div class="tcl-15__panel tcl-15__panel--dark">
      <header class="tcl-15__phead"><h2>Shortcuts</h2><span class="tcl-15__spec tcl-15__spec--dark">max-content 1fr</span></header>
      <div class="tcl-15__keys">
        <span class="tcl-15__kbd"><kbd>⌘</kbd><kbd>K</kbd></span><p>Open the command palette</p>
        <span class="tcl-15__kbd"><kbd>⌘</kbd><kbd>Shift</kbd><kbd>P</kbd></span><p>Publish the current draft</p>
        <span class="tcl-15__kbd"><kbd>G</kbd><kbd>then</kbd><kbd>I</kbd></span><p>Jump to inbox</p>
        <span class="tcl-15__kbd"><kbd>?</kbd></span><p>Show every shortcut</p>
      </div>
    </div>
  </div>
</section>
.tcl-15,
.tcl-15 *,
.tcl-15 *::before,
.tcl-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tcl-15 {
  width: 100%;
  min-height: 100vh;
  --accent: oklch(0.62 0.17 250);
  --ink: oklch(0.25 0.02 250);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
  background: oklch(0.965 0.008 250);
  padding: clamp(24px,4vw,52px);
}

.tcl-15__wrap {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(320px,1fr));
  gap: 20px;
  max-width: 960px;
  margin-inline: auto;
  align-items: start;
}

.tcl-15__panel {
  background: #fff;
  border: 1px solid oklch(0.89 0.012 250);
  border-radius: 18px;
  padding: clamp(18px,2.5vw,26px);
  display: grid;
  gap: 16px;
}

.tcl-15__panel--dark {
  background: oklch(0.23 0.03 260);
  border-color: oklch(0.32 0.03 260);
  color: oklch(0.92 0.01 250);
}

.tcl-15__phead {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.tcl-15__phead h2 {
  font-size: 19px;
  letter-spacing: -.015em;
}

.tcl-15__spec {
  font: 600 10.5px/1 ui-monospace,monospace;
  color: var(--accent);
  border: 1px dashed color-mix(in oklab,var(--accent) 50%,transparent);
  border-radius: 99px;
  padding: 5px 9px;
}

.tcl-15__spec--dark {
  color: oklch(0.75 0.12 250);
  border-color: oklch(0.5 0.08 250);
}

.tcl-15__log {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 14px 16px;
  align-items: start;
}

.tcl-15__ver {
  font: 700 11.5px/1 ui-monospace,monospace;
  color: color-mix(in oklab,var(--ink) 60%,transparent);
  background: oklch(0.955 0.008 250);
  border: 1px solid oklch(0.9 0.012 250);
  border-radius: 7px;
  padding: 6px 9px;
  justify-self: start;
  white-space: nowrap;
}

.tcl-15__ver--new {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
}

.tcl-15__log p {
  font-size: 13.5px;
  line-height: 1.6;
  color: color-mix(in oklab,var(--ink) 75%,transparent);
  text-wrap: pretty;
}

.tcl-15__log p b {
  color: var(--ink);
}

.tcl-15__log p code {
  font: 600 12px ui-monospace,monospace;
  background: color-mix(in oklab,var(--accent) 10%,transparent);
  border-radius: 4px;
  padding: 1px 5px;
}

.tcl-15__keys {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 14px 18px;
  align-items: center;
}

.tcl-15__kbd {
  display: flex;
  gap: 4px;
}

.tcl-15__kbd kbd {
  font: 600 11.5px/1 ui-monospace,monospace;
  color: oklch(0.9 0.02 250);
  background: oklch(0.3 0.03 260);
  border: 1px solid oklch(0.42 0.03 260);
  border-bottom-width: 2.5px;
  border-radius: 6px;
  padding: 6px 8px;
  white-space: nowrap;
}

.tcl-15__keys p {
  font-size: 13.5px;
  line-height: 1.5;
  color: oklch(0.8 0.02 250);
}

@media (max-width: 420px) {
  .tcl-15__log,
    .tcl-15__keys {
    grid-template-columns: 1fr;
  }

  .tcl-15__log p,
    .tcl-15__keys p {
    margin-top: -8px;
  }
}
/* No JavaScript — grid-template-columns:auto 1fr (changelog) and max-content 1fr (shortcuts); one shared grid per list makes every first-column item align to the widest. */
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 Two-Column 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: CSS Two Column Layout Auto Width
Source: https://codefronts.com/layouts/css-two-column-layout/css-two-column-layout-auto-width/

Content-sized first column, fluid second: grid-template-columns: auto 1fr (and its stricter sibling max-content 1fr) let a badge, timestamp or keyboard shortcut set its own width while the text takes the rest — shown as a release changelog and a shortcuts panel where every label column self-aligns to the widest item.
## HTML
```html
<section class="tcl-15" aria-label="Auto width two column layout demo">
  <div class="tcl-15__wrap">
    <div class="tcl-15__panel">
      <header class="tcl-15__phead"><h2>Changelog</h2><span class="tcl-15__spec">auto 1fr</span></header>
      <div class="tcl-15__log">
        <span class="tcl-15__ver tcl-15__ver--new">v3.2.0</span><p><b>Container query cards.</b> Card components now respond to their wrapper width — the same card works in feeds, rails and modals.</p>
        <span class="tcl-15__ver">v3.1.4</span><p><b>Fix:</b> sticky sidebars no longer unpin inside overflow-clipped sections.</p>
        <span class="tcl-15__ver">v3.1.0</span><p><b>Scroll-driven progress.</b> Reading indicators moved off JavaScript onto <code>animation-timeline</code>.</p>
        <span class="tcl-15__ver">v3.0.2</span><p><b>Fix:</b> form grids keep 44px hit targets at 200% zoom.</p>
      </div>
    </div>
    <div class="tcl-15__panel tcl-15__panel--dark">
      <header class="tcl-15__phead"><h2>Shortcuts</h2><span class="tcl-15__spec tcl-15__spec--dark">max-content 1fr</span></header>
      <div class="tcl-15__keys">
        <span class="tcl-15__kbd"><kbd>⌘</kbd><kbd>K</kbd></span><p>Open the command palette</p>
        <span class="tcl-15__kbd"><kbd>⌘</kbd><kbd>Shift</kbd><kbd>P</kbd></span><p>Publish the current draft</p>
        <span class="tcl-15__kbd"><kbd>G</kbd><kbd>then</kbd><kbd>I</kbd></span><p>Jump to inbox</p>
        <span class="tcl-15__kbd"><kbd>?</kbd></span><p>Show every shortcut</p>
      </div>
    </div>
  </div>
</section>
```
## CSS
```css
.tcl-15,
.tcl-15 *,
.tcl-15 *::before,
.tcl-15 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tcl-15 {
  width: 100%;
  min-height: 100vh;
  --accent: oklch(0.62 0.17 250);
  --ink: oklch(0.25 0.02 250);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
  background: oklch(0.965 0.008 250);
  padding: clamp(24px,4vw,52px);
}

.tcl-15__wrap {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(320px,1fr));
  gap: 20px;
  max-width: 960px;
  margin-inline: auto;
  align-items: start;
}

.tcl-15__panel {
  background: #fff;
  border: 1px solid oklch(0.89 0.012 250);
  border-radius: 18px;
  padding: clamp(18px,2.5vw,26px);
  display: grid;
  gap: 16px;
}

.tcl-15__panel--dark {
  background: oklch(0.23 0.03 260);
  border-color: oklch(0.32 0.03 260);
  color: oklch(0.92 0.01 250);
}

.tcl-15__phead {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.tcl-15__phead h2 {
  font-size: 19px;
  letter-spacing: -.015em;
}

.tcl-15__spec {
  font: 600 10.5px/1 ui-monospace,monospace;
  color: var(--accent);
  border: 1px dashed color-mix(in oklab,var(--accent) 50%,transparent);
  border-radius: 99px;
  padding: 5px 9px;
}

.tcl-15__spec--dark {
  color: oklch(0.75 0.12 250);
  border-color: oklch(0.5 0.08 250);
}

.tcl-15__log {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 14px 16px;
  align-items: start;
}

.tcl-15__ver {
  font: 700 11.5px/1 ui-monospace,monospace;
  color: color-mix(in oklab,var(--ink) 60%,transparent);
  background: oklch(0.955 0.008 250);
  border: 1px solid oklch(0.9 0.012 250);
  border-radius: 7px;
  padding: 6px 9px;
  justify-self: start;
  white-space: nowrap;
}

.tcl-15__ver--new {
  color: #fff;
  background: var(--accent);
  border-color: var(--accent);
}

.tcl-15__log p {
  font-size: 13.5px;
  line-height: 1.6;
  color: color-mix(in oklab,var(--ink) 75%,transparent);
  text-wrap: pretty;
}

.tcl-15__log p b {
  color: var(--ink);
}

.tcl-15__log p code {
  font: 600 12px ui-monospace,monospace;
  background: color-mix(in oklab,var(--accent) 10%,transparent);
  border-radius: 4px;
  padding: 1px 5px;
}

.tcl-15__keys {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 14px 18px;
  align-items: center;
}

.tcl-15__kbd {
  display: flex;
  gap: 4px;
}

.tcl-15__kbd kbd {
  font: 600 11.5px/1 ui-monospace,monospace;
  color: oklch(0.9 0.02 250);
  background: oklch(0.3 0.03 260);
  border: 1px solid oklch(0.42 0.03 260);
  border-bottom-width: 2.5px;
  border-radius: 6px;
  padding: 6px 8px;
  white-space: nowrap;
}

.tcl-15__keys p {
  font-size: 13.5px;
  line-height: 1.5;
  color: oklch(0.8 0.02 250);
}

@media (max-width: 420px) {
  .tcl-15__log,
    .tcl-15__keys {
    grid-template-columns: 1fr;
  }

  .tcl-15__log p,
    .tcl-15__keys p {
    margin-top: -8px;
  }
}
```

## JavaScript
```js
/* No JavaScript — grid-template-columns:auto 1fr (changelog) and max-content 1fr (shortcuts); one shared grid per list makes every first-column item align to the widest. */
```

How this works

grid-template-columns:auto 1fr sizes track one to its content and hands everything else to track two. The subtle superpower: when MANY rows share one grid (via display:grid on the list and two children per row, or subgrid), the auto track resolves to the widest item across ALL rows — every version badge below aligns to one shared column edge without a single hard-coded width.

The demo uses both flavours. The changelog uses auto 1fr (auto can shrink if space runs out). The shortcuts panel uses max-content 1fr, which refuses to wrap the key combos — the right choice when the first column must never break mid-token. Swap in new content and the columns re-negotiate themselves; that’s the point.

Make it yours

  • Clamp a runaway first column with minmax(auto,40%) — auto-width with a ceiling.
  • Flip the pattern for trailing meta: 1fr auto puts the fluid text first and hugs a timestamp to the right edge.
  • Three-part rows (icon · text · action) are just auto 1fr auto.
  • The flexbox equivalent for a single row: no width on item one, flex:1 on item two — but only grid aligns the column ACROSS rows.

Gotchas — read before shipping

  • auto and max-content differ under pressure: auto can shrink and wrap its content; max-content never does and will overflow instead — choose deliberately.
  • One absurdly long unspaced string in an auto track starves the 1fr column; guard user-generated labels with minmax(auto,50%) or overflow-wrap:anywhere.
  • For cross-row alignment put the grid on the LIST (rows as display:contents or two children each), not on each row separately — separate grids each compute their own auto width.

Browser support

ChromeSafariFirefoxEdge
115+26+not yet115+

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

auto and max-content track sizes are original grid spec — universal since 2017, no fallbacks required.

Techniques used in this demo

Search CodeFronts

Loading…