v133 · css

Sticky Column

A horizontally-scrollable data table with a sticky first column. When the column is actually stuck to the left edge — i.e. the table has scrolled — @container scroll-state(stuck: left) fires and adds a drop-shadow and accent border automatically. No JavaScript IntersectionObserver, no scroll listeners.

Feature detection: checking…
Scroll the table right — first column detects stuck: left container-type: scroll-state on wrapper
Country Capital Population Area (km²) GDP (USD bn) Currency Language Time zone
Germany Berlin 83.2 M 357,114 $4,082 Euro German UTC+1/+2
France Paris 67.8 M 551,695 $2,778 Euro French UTC+1/+2
Japan Tokyo 125.7 M 377,975 $4,231 Yen Japanese UTC+9
Brazil Brasília 214.3 M 8,515,767 $1,920 Real Portuguese UTC-3
Australia Canberra 25.9 M 7,692,024 $1,688 AUD English UTC+8/+11
India New Delhi 1,428 M 3,287,263 $3,730 Rupee Hindi/English UTC+5:30
Canada Ottawa 38.2 M 9,984,670 $2,139 CAD English/French UTC-3.5/−8
South Korea Seoul 51.7 M 100,210 $1,709 Won Korean UTC+9
Column state: not stuck stuck: left ← CSS reacts here

Pre-133 vs Chrome 133+

Pre-Chrome 133
To detect a stuck sticky column, authors had to set up an IntersectionObserver watching a 1px sentinel element just before the sticky column, toggle a class when it left the viewport, and remove it on un-stick. Each table needed its own observer, and the sentinel had to be carefully positioned to avoid false positives.
Chrome 133+
Set container-type: scroll-state on the scrollable table wrapper. Write @container scroll-state(stuck: left) for the sticky cells. The browser fires the query automatically when the cell sticks to the left edge — shadow, border, and font-weight all update in CSS, with zero JavaScript.
/* 1. Mark the scroll container */
.data-table {
  container-type: scroll-state;
  overflow-x: auto;
}

/* 2. Sticky first column as usual */
.col-name {
  position: sticky;
  left: 0;
  z-index: 2;
}

/* 3. Style the stuck state — fires when column hits the left edge */
@container scroll-state(stuck: left) {
  .col-name {
    background: var(--bg-stone);
    border-right: 3px solid var(--text-black);
    box-shadow: 3px 0 8px -2px rgba(0, 0, 0, 0.18);
    font-weight: 700;
  }
}

/*
  Other stuck: values:
  stuck: top    — sticky header hits the top of the viewport
  stuck: right  — sticky right column hits the right edge
  stuck: bottom — sticky footer hits the bottom

  All use the same container-type: scroll-state pattern.
*/

see also