This document is intended as a starting point for engaging the community and standards bodies in developing collaborative solutions fit for standardization. As the solutions to problems described in this document progress along the standards-track, we will retain this document as an archive and use this section to keep the community up-to-date with the most current standards venue and content location of future work and discussions.
CSS multi-column containers allow for rules to be drawn between columns. Applying similar styling to other container layouts such as grid and flex has been widely sought after, as seen in the discussion for CSS Working Group issue 2748 and in several StackOverflow questions ( [1] [2] [3] [4] ). Currently, developers seeking to draw such decorations must resort to non-ergonomic workarounds such as these examples:
Use cases in this explainer were collected from the discussion in CSSWG issue 2748. Additional inspiration was drawn from discussions in CSSWG issues 5080, 6748, and 9482.
Comments received on the feature in MSEdgeExplainers issues 996, 1099, 1100, and 1111 have also been incorporated into the design.
Unless otherwise noted, corresponding row- and column- properties should be
assumed to have identical syntax. All such pairs of properties also have
shorthands that apply the same values in both directions.
For property grammar details, please see the Editor's Draft.
In addition to replicating the existing column-rule properties in the row direction, we expand the syntax of both sets of properties to allow for multiple definitions. If a given property has fewer list entries than the number of gaps, the list is cycled through from the beginning as needed.
Authors may also use familiar syntax from CSS Grid such as repeat()
and auto to create patterns of line definitions. Note that while repeat() and auto
are inspired by CSS Grid, they may also be used to create patterns of decorations
in flex, multi-column, and grid-lanes containers.
Shorthands are also available to combine the width, style, and color properties.
.alternate-red-blue {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-auto-rows: 30px;
gap: 10px;
row-rule: 1px solid;
row-rule-color: red, blue;
}
.alternate-heavy-light {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-auto-rows: 30px;
gap: 10px;
row-rule: 2px solid black, 1px solid lightgray;
}
Like column rules in multi-column layout, gap decorations in other layout
containers do not take up space and do not affect the layout of items in the
container. Conceptually, gap decorations are considered after layout has
completed, and in particular after we already know the full extent of the
implicit grid in grid
layout, or the number of lines in flex layout, or the number of columns in
multi-column layout, or the number of tracks in grid-lanes layout. Thus, the
repeat() grammar, while modeled after the grid-template properties, is
simpler for gap decorations as there are fewer unknowns to consider.
.varying-widths {
dispay: grid;
grid-template-columns: repeat(3, 100px);
grid-auto-rows: 30px;
row-gap: 9px;
row-rule: 5px solid black, repeat(auto, 1px solid black), 3px solid black;
}
.item {
height: 30px;
padding: 5px;
border: 1px dotted lightgray;
}
Authors may change the set of intersections where gap decorations break, from the default behavior to either "all intersections" or "no intersections." Where gap decorations overlap items in the container, the decoration is painted behind the item.
.normal {
rule-break: normal;
}
.all-intersections {
rule-break: intersection;
}
.no-intersections {
rule-break: none;
}
By default, gap decorations are painted as continuous segments that extend as far as possible along the centerline of a given visible gap. The decoration is painted from visible intersection to another, with each endpoint at the innermost edge of the intersection.
.grid-with-spans {
display: grid;
grid-template: repeat(4, 100px) / repeat(4, 100px);
gap: 20px;
row-rule: 6px solid red;
column-rule: 6px solid blue;
}
.flex {
display: flex;
flex-wrap: wrap;
gap: 20px;
width: 500px;
row-rule: 6px solid red;
column-rule: 6px solid blue;
}
Authors may adjust the positions of endpoints relative to gap intersections, either as a fixed distance or as a percentage of the width of the intersection. The "zero point" is the edge of the intersection, with negative values extending into the intersection and positive values receding from it.
.inset-0px {
column-rule-break: intersection;
column-rule-inset: 0px;
}
.inset-5px {
column-rule-break: intersection;
column-rule-inset: 5px;
}
.inset-negative-5px {
column-rule-break: intersection;
column-rule-inset: -5px;
}
Authors may also adjust endpoints more granularly, making a distinction between "edge" endpoints (which fall on the edge of the container), and "interior" endpoints (any endpoint that is not an "edge").
.edge-interior-insets {
column-rule-break: intersection;
column-rule-edge-inset: 0px;
column-rule-interior-inset: -5px;
}
Similarly, authors can have even more granular control to adjust the positions of endpoints, making a distinction between "start" and "end" endpoints, in addition to the "edge" and "interior" distinction.
.start-end-edge-interior-insets {
column-rule-break: intersection;
column-rule-edge-inset-start: 8px;
column-rule-interior-inset-start: 8px;
/* or shorthand: */
column-rule-inset-start: 8px;
}
When row and column gap decorations overlap, authors can control their painting order. By default, row-direction decorations are painted on top of column-direction decorations.
rule-overlap: [ row-over-column | column-over-row ]
.row-over-column {
row-rule: 6px solid red;
column-rule: 6px solid blue;
rule-overlap: row-over-column;
}
.column-over-row {
row-rule: 5px solid red;
column-rule: 5px solid blue;
rule-overlap: column-over-row;
}
By default, gap decoration segments appear throughout a container.
In some cases, authors may not want to paint segments next to empty areas.
The *-rule-visibility-items properties allow control over this.
.container {
display: grid;
grid-template: repeat(3, 100px) / repeat(3, 100px);
gap: 10px;
rule: 1px solid black;
rule-break: intersection;
rule-visibility-items: all; /* initial value */
}
.item {
background: lightgray;
}
<div class="container">
<div class="item" style="grid-area: 1 / 1">Item 1</div>
<div class="item" style="grid-area: 2 / 1">Item 2</div>
<div class="item" style="grid-area: 2 / 2">Item 3</div>
<div class="item" style="grid-area: 3 / 1">Item 4</div>
</div>
.container {
display: grid;
grid-template: repeat(3, 100px) / repeat(3, 100px);
gap: 10px;
rule: 1px solid black;
rule-break: intersection;
rule-visibility-items: around;
}
.item {
background: lightgray;
}
<div class="container">
<div class="item" style="grid-area: 1 / 1">Item 1</div>
<div class="item" style="grid-area: 2 / 1">Item 2</div>
<div class="item" style="grid-area: 2 / 2">Item 3</div>
<div class="item" style="grid-area: 3 / 1">Item 4</div>
</div>
.container {
display: grid;
grid-template: repeat(3, 100px) / repeat(3, 100px);
gap: 10px;
rule: 1px solid black;
rule-break: intersection;
rule-visibility-items: between;
}
.item {
background: lightgray;
}
<div class="container">
<div class="item" style="grid-area: 1 / 1">Item 1</div>
<div class="item" style="grid-area: 2 / 1">Item 2</div>
<div class="item" style="grid-area: 2 / 2">Item 3</div>
<div class="item" style="grid-area: 3 / 1">Item 4</div>
</div>
Note that rule-visibility-items in the examples above is a shorthand
for column-rule-visibility-items and row-rule-visibility-items,
which can also be set independently:
.container {
display: grid;
grid-template: repeat(3, 100px) / repeat(3, 100px);
gap: 10px;
rule: 1px solid black;
rule-break: intersection;
column-rule-visibility-items: around;
row-rule-visibility-items: between;
}
.item {
background: lightgray;
}
<div class="container">
<div class="item" style="grid-area: 1 / 1">Item 1</div>
<div class="item" style="grid-area: 2 / 1">Item 2</div>
<div class="item" style="grid-area: 2 / 2">Item 3</div>
<div class="item" style="grid-area: 3 / 1">Item 4</div>
</div>
https://github.com/w3c/csswg-drafts/issues/2748#issuecomment-446379068, which links to: https://codepen.io/urlyman/pen/yGNOya
The desired effect is a line appearing only between the grid rows, and extending unbroken across the column gaps.
Note that I don't want a line to appear above or beneath all rows, only in the gaps between rows.
.container {
row-rule: 1px solid #ccc;
}
https://github.com/w3c/csswg-drafts/issues/2748#issuecomment-595663212
.container {
rule: thick solid green;
}
https://github.com/w3c/csswg-drafts/issues/2748#issuecomment-446781218 - last example
.container {
rule: 1px solid black;
column-rule-inset: 0px;
}
https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/1099
.layout {
display: grid;
grid-template-areas:
". . content author"
". . content social";
gap: 5px;
rule: 1px solid gray;
rule-visibility-items: around;
border-top: 1px solid gray;
}
https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/1100
.layout {
display: grid;
grid-template-columns: 400px 1000px;
column-gap: 90px;
row-gap: 50px;
column-rule: 1px solid white;
column-rule-visibility-items: between;
}
Much like border-image, support for images in gap decorations would allow for
more decorative separators to be used. These could be purely design choices, or
they could be used to achieve practical effects such as coupon borders.
Examples:
https://github.com/w3c/csswg-drafts/issues/2748#issuecomment-446781218 - third example
https://github.com/w3c/csswg-drafts/issues/2748#issuecomment-621983931
https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/1161
However, unlike border-image, gap decoration images need to cover
significantly more cases, such as T intersections and cross intersections. More
detail and examination of this issue:
In Issue 985, it
was suggested that we apply a border-radius like property to gap decorations
to allow for more flexible styling near intersections. We could also potentially
reuse concepts from corner-shape for even more flexibility. This idea is tracked
in CSSWG Issue 12150.
CSS Grid Level 2 defines the
subgrid feature. A subgrid matches
up its grid lines to lines in the parent grid. Accordingly, gaps will also align
between a subgrid and its parent grid, though the sizes of these gaps may
differ. There may be use cases for propagating gap decorations from the parent
grid into corresponding gaps in the subgrid; we could perhaps do this with a
special keyword on the *-rule-width, *-rule-style, and *-rule-color
properties. See CSSWG Issue
12326 for further discussion.
Design discussions for *-rule-visibility-items also considered companion
*-rule-visibility-self properties which would allow the container-wide value to
be overridden on specific items. For example, an author who wants to draw
decorations only around a specific item in the container might set
rule-visibility-items: none on the container, and
rule-visibility-self: around on the specific item that they want to draw around.
start-side and end-side have also been suggested as additional values for both
*-rule-visibility-items and *-rule-visibility-self, to draw decorations only
on one side or the other of items.
An author may want to apply different sets of gap decorations to different regions of a given container layout. We refer to such regions as a gap decoration areas. The examples below illustrate how these might work on a grid container; gap decoration areas on other container types have not yet been explored.
The author defines these areas using the rule-areas property. Each area is
defined by giving it first a name, then a tuple of numbers or line names which
work exactly as they would in the grid-area property:
rule-areas: --first-row 1 / 1 / 2 / -1, --first-column 1 / 1 / -1 / 2;
On a grid container, the above value of rule-areas would define an area named
--first-row that includes the grid lines within and around the first row (row
line 1, column line 1 to row line 2, column line -1) and an area named
--first-column that includes the grid lines within and around the first column
(row line 1, column line 1 to row line -1, column line 2). These areas are
inclusive of the grid lines on their edges.
Then, on other gap decoration properties such as *-rule-width, *-rule-style,
and *-rule-color, the author can then specify first a "default" set of values
for the container, then a named area, then a set of values that applies to that
area, and so on:
rule: 1px solid black, 1px solid gray [--first-row] 3px solid black, 5px solid black [--first-column] 1px solid blue;
Cycling behavior applies in named areas the same as it does elsewhere, and where multiple values would cover the same segment of a gap, the last one that applies will "win". Thus, the value above would apply alternating 1px solid black and 1px solid gray rules to the grid in general, then override gaps in the first row with alternating 3px solid black and 5px solid black rules, then on top of that override gaps in the first column with 1px solid blue rules.
.grid-multiple-decoration-areas {
display: grid;
grid-template-rows: [top] 30px [main-top] repeat(6, 30px) [bottom];
grid-template-columns: [left] 100px [main-left] repeat(3, 100px) [right];
gap: 10px;
rule-areas: --month-column left / top / main-left / bottom;
row-rule: 1px solid black [--month-column] 1px solid lightblue;
column-rule: [--month-column] 1px solid lightblue;
}
https://github.com/w3c/csswg-drafts/issues/2748#issuecomment-595889781
.container {
rule-style: solid:
rule-color: lightgray;
rule-areas: --main 2 / 2 / -1 / -1;
column-rule-width: [--main] 1px repeat(auto, 2px) 1px;
row-rule-width: [--main] 0px repeat(auto, 2px 1px);
}
https://github.com/w3c/csswg-drafts/issues/12024#issuecomment-3086244002
.container {
display: grid;
grid-template: repeat(4, 280px) / repeat(8, auto);
gap: 20px;
rule-areas: --top-center 1 / 4 / 2 / 6, --bottom-left -2 / 1 / -1 / 2, --bottom-right -2 / -1 / -1 / -1;
column-rule: 18px solid red [--top-center] none [--bottom-left] none [--bottom-right] none;
}
This idea was dropped based on feedback raised in the initial proposal discussion.
These are designed to enable scenarios where authors wish to switch, for example,
flex-direction based on space constraints or other factors.
| Property | row or row-reverse direction | column or column-reverse direction |
|---|---|---|
| main-rule-width | row-rule-width | column-rule-width |
| main-rule-style | row-rule-style | column-rule-style |
| main-rule-color | row-rule-color | column-rule-color |
| main-rule | row-rule | column-rule |
| cross-rule-width | column-rule-width | row-rule-width |
| cross-rule-style | column-rule-style | row-rule-style |
| cross-rule-color | column-rule-color | row-rule-color |
| cross-rule | column-rule | row-rule |
And so on for other properties.
For flex containers, the logical properties map based on
flex-direction following the convention above.
For grid containers, main maps to row, and cross maps to column.
For multi-column containers, main maps to column, and cross maps to row.
In 2021, Mats Palmgren from Mozilla posted a draft specification for gap decorations. We believe the proposal in this explainer improves on developer ergonomics by (a) reusing concepts from grid layout such as repeat and grid lines, and (b) simplifying the model for fine-tuning segment placement. We also believe the proposal in this explainer offers developers more flexibility even absent support for gap decoration images; see Scenario 3 for one example.
An alternative approach to column-rule-* and row-rule-* properties would be
to introduce pseudo-elements representing gaps, for example:
.container {
display: grid;
grid-template: auto / auto;
gap: 5px;
}
.container::column-gaps {
background: red;
width: 1px;
}
.container::row-gaps {
background: blue;
width: 1px;
}
In some ways, this would be more powerful, as it would allow for more flexibility for what can be placed in the gaps.
However, this approach also comes with drawbacks. Varying gap decorations over a
container becomes much harder. One might imagine a ::row-gaps::nth(even)
pseudo selector to style every other row gap. However, certain container types
such as grid can automatically generate rows and columns depending on their
contents. That means we don't know until layout time how many such pseudo styles
we need to produce, which creates a wrong-way dependency between layout and
style. It would also mean that, for large containers, we would incur the costs
of calculating and storing styles for every single gap. That would be a large
overhead to absorb, especially considering that the more common case is to have
at most a single decoration style for a given container.
Many thanks for valuable feedback and advice from: