- From: Devi Prasad via GitHub <noreply@w3.org>
- Date: Wed, 25 Mar 2026 21:28:46 +0000
- To: public-css-archive@w3.org
devi-prsd has just created a new issue for https://github.com/w3c/csswg-drafts:
== [selectors] :last-in-column / :nth-last-in-column() pseudo-classes for wrapped flex and grid layouts ==
## Problem Statement
There is currently no CSS-only way to select the last item in each column of a multi-column flex or grid layout. This is a common need for:
- **Decorative fill patterns** in masonry-style layouts (filling remaining column space with hatching, dot patterns, accent borders, etc.)
- **Bottom-border removal** on the last card per column to avoid double borders
- **Spacing adjustments** (removing `margin-bottom` from last items per column)
- **Visual indicators** like column-end dividers, fade-outs, or ornamental elements
Authors are forced to use significant JavaScript to achieve what is fundamentally a presentational concern.
## Real-World Use Case
Consider a horizontally-scrolling editorial/zine-style board with a flexbox column-wrap masonry layout:
```css
.masonry-wrapper {
display: flex;
flex-direction: column;
flex-wrap: wrap;
height: 100%;
column-gap: 2rem;
}
.masonry-item {
width: 320px;
margin-bottom: 2rem;
}
```
The design calls for decorative fill patterns (diagonal hatching, dot matrices, crosshatching, etc.) applied via `::after` pseudo-elements on whichever item happens to land last in each column. These patterns fill the remaining vertical space, creating a polished editorial aesthetic.
**Today's workaround** requires ~80 lines of JavaScript:
```js
// 1. IntersectionObserver to track which items are visible
// 2. ResizeObserver to watch for container size changes
// 3. getBoundingClientRect() to determine column positions
// 4. Manual computation of which items are last per column
// 5. Toggling an `.is-last-in-column` class on matching elements
// 6. Debounced re-computation on scroll, resize, and content changes
```
This approach is:
- **Fragile** — breaks on scroll, resize, dynamic content, and container queries
- **Expensive** — forces layout recalculation and DOM reads on every reflow
- **Non-composable** — cannot be combined with other selectors or used in `@media`/`@container` contexts without additional JS wiring
- **Inaccessible to CSS-only contexts** — server-rendered pages, CSS-only prototypes, and declarative design systems cannot use it at all
## Proposed Solution
### New Pseudo-Classes
#### `:last-in-column`
Matches every element that is the last sibling laid out in its respective column within a multi-column flex container (`flex-direction: column; flex-wrap: wrap`), a CSS `columns` layout, or a grid container with implicit column tracks.
```css
/* Decorative column-end fill */
.masonry-item:last-in-column {
flex-grow: 1;
}
.masonry-item:last-in-column::after {
content: "";
flex-grow: 1;
background: repeating-linear-gradient(
-45deg, currentColor, currentColor 1px,
transparent 1px, transparent 8px
);
opacity: 0.15;
}
```
#### `:first-in-column`
Matches every element that is the first sibling laid out in its respective column. Useful for column-start decorations, drop-cap-like effects, or top-border treatment.
```css
.masonry-item:first-in-column {
border-top: 3px solid accent-color;
}
```
#### `:nth-in-column()` / `:nth-last-in-column()`
Functional pseudo-classes accepting `An+B` notation, scoped to the column the element is placed in.
```css
/* Style every 2nd item within each column */
.masonry-item:nth-in-column(2n) {
background: var(--zebra-stripe);
}
/* Style the second-to-last item in each column */
.masonry-item:nth-last-in-column(2) {
margin-bottom: 0;
}
```
### Which Layouts Apply
| Layout Mode | Applies? | Column Determination |
|---|---|---|
| `flex-direction: column; flex-wrap: wrap` | Yes | Items in the same column share the same inline-axis offset |
| `columns` (multi-column layout) | Yes | Items in each column fragment |
| `grid` with explicit/implicit columns | Yes | Items sharing the same `grid-column` track |
| `flex-direction: row` | No | N/A (items flow in rows, not columns) |
| Block flow | No | N/A (single column by definition) |
### Definition of "Column"
A **column** is defined as a set of sibling elements that share the same column track (grid) or column fragment (multi-column, column-wrap flex). Two flex items are in the same column if they occupy the same column line in the wrapping axis.
The UA already computes this information during layout; the selector simply exposes it.
## Interaction with Existing Specifications
### Selectors Level 4/5
These pseudo-classes extend the structural pseudo-class family (`:first-child`, `:last-child`, `:nth-child()`). The key difference is that structural pseudo-classes operate on DOM order, while `:*-in-column` pseudo-classes operate on **layout position** — making them layout-dependent selectors.
### Precedent: Layout-Dependent Selection
CSS already has layout-aware pseudo-classes and selectors:
- `:empty` — depends on content (though not layout)
- `::column` (proposed in [CSS Pseudo-Elements 4](https://drafts.csswg.org/css-pseudo-4/#column-pseudo)) — selects column boxes in multi-column layout
- The proposed `masonry` layout ([CSS Grid Level 3](https://drafts.csswg.org/css-grid-3/)) will need similar per-column selection
### Circular Dependency Avoidance
To prevent circular dependencies (where selecting `:last-in-column` changes sizing, which changes which item is last), column assignment must be resolved **before** these selectors are evaluated — similar to how `:first-line` resolves after line breaking. The selector matches based on the layout computed in the **previous layout pass**; if matching causes a layout change, the UA performs at most one additional pass (analogous to `fit-content` resolution).
Alternatively, these selectors could be restricted to properties that do not affect column assignment (e.g., `color`, `opacity`, `background`, `border`, `outline`, `box-shadow`, `transform`, `filter`) — similar to how `::first-line` restricts applicable properties.
## Alternatives Considered
| Alternative | Why It Falls Short |
|---|---|
| JavaScript (IntersectionObserver + classList) | Performance cost, fragile, not declarative, SSR-hostile |
| `:nth-child(An+B)` with known column count | Column count changes with viewport; requires JS or `@media` ladder for every breakpoint |
| `break-after: column` | Only works in multi-column layout, not flex or grid |
| Container Queries | Can query dimensions but cannot select based on positional layout role |
| CSS `masonry` layout (Grid L3) | Only addresses one layout mode; flex column-wrap masonry exists today and is widely used |
## Implementation Considerations
- The UA already knows column assignment after layout; exposing it to selectors is a matter of bookkeeping, not new computation.
- Restricting applicable properties (as with `::first-line`) avoids circular dependency without additional layout passes.
- These selectors compose naturally with existing pseudo-classes: `.card:last-in-column:not(:first-in-column)` targets columns with more than one item.
## Syntax Summary
```
:first-in-column /* first item laid out in its column */
:last-in-column /* last item laid out in its column */
:only-in-column /* sole item in its column */
:nth-in-column(An+B) /* nth item in its column */
:nth-last-in-column(An+B) /* nth from end in its column */
```
## Polyfill Path
A polyfill can be implemented today using `ResizeObserver` + `getBoundingClientRect()` to compute column membership and toggle data attributes (`[data-last-in-column]`). This is exactly what production applications are already doing — see the [real-world example above](#real-world-use-case).
## Related Issues / Specs
- [CSS Grid Level 3 (Masonry)](https://drafts.csswg.org/css-grid-3/) — masonry layouts will have the same need
- [CSS Pseudo-Elements 4 `::column`](https://drafts.csswg.org/css-pseudo-4/) — column-level pseudo-elements
- [Selectors Level 4](https://drafts.csswg.org/selectors-4/) — structural pseudo-classes
---
**Labels:** `selectors-5`, `css-flexbox`, `css-grid`, `css-multicol`
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/13729 using your GitHub account
--
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Wednesday, 25 March 2026 21:28:47 UTC