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.
- This document status: Active
- Expected venue: CSS Working Group
- Current version: this document
- CSS Gap Decorations Level 1
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:
- https://www.smashingmagazine.com/2017/09/css-grid-gotchas-stumbling-blocks/#how-do-i-add-backgrounds-and-borders-to-grid-areas
- https://x.com/geddski/status/1004731709764534274
- Extend CSS column rule properties to apply to grid, flex, and masonry in addition to multi-column containers.
- Introduce row-direction gap decorations on CSS grid, flex, and masonry containers.
- Allow gap decorations to vary over a given container to handle cases such as alternating row separators.
- Gap decorations on CSS Tables. The CSS Tables specification is currently Not Ready for Implementation, and there are interoperability differences among engines. Additionally, authors can achieve many of the scenarios covered by this explainer in a table already using cell borders.
- Row-direction gap decorations on multi-column containers. This is theoretically feasible for cases where an element spans across multiple columns, but currently row gaps do not apply to multi-column containers, so there is nowhere to put such a decoration. Support for row-gap on multi-column containers was proposed in issue 6746; discussion in that issue also notes the potential for multi-column to gain block-direction overflow with a corresponding gap. This non-goal could become a goal if either of these ideas are adopted.
- Images in gap decorations. Compared to, say, border-image, gap decoration images need to cover significantly more cases such as T intersections. See this comment for more detail. Further exploration is needed into the best way to handle these, so this scenario is left to a future level of the feature.
Use cases in this explainer were collected from the discussion in issue 2748. Additional inspiration was drawn from discussions in issues 5080, 6748, and 9482.
Unless otherwise noted, corresponding row-
and column-
properties should be
assumed to have identical syntax. Maintaining symmetry between row-
and
column-
properties is important for responsive flex and masonry scenarios.
For property grammar details, please see the draft specification.
These properties allow authors to specify where gap decorations start and end within a container.
An author may specify more than one such region and apply a different set of gap decorations to each. Within this document, we refer to such a region as a gap decoration area. Much like CSS Transitions and Animations, all gap decoration properties may take a comma-delimited list of values. Each entry in such a list is applied to the corresponding entry in the list of gap decoration areas. If a given property's list length is shorter than the gap decoration area list length, the shorter list cycles back to the beginning as needed.
Gap decoration area properties are defined per container type.
In grid containers, the author may specify any grid line based placement, as in
the 'grid-row-start', 'grid-row-end', 'grid-column-start', and
'grid-column-end'
properties. The
corresponding width-style-color gap decoration tuples in the row and column
directions will apply in that area. The initial value is 1 / 1 / -1 / -1
to
cover the entire grid.
.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];
grid-gap: 10px;
grid-row-rule-area: left / top / main-left / bottom,
main-left / main-top / right / bottom;
row-rule: 1px solid lightblue,
1px solid black;
grid-column-rule-area: main-left / top / main-left / bottom;
column-rule: 1px solid lightblue;
}
Gap decoration area properties for these container types are not yet defined.
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. Authors may use familiar syntax from CSS Grid such as repeat()
and auto
to create patterns of line definitions that apply within a given gap
decoration area. 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 masonry containers.
If the number of specified values (after expanding any repeats) in a given list
is less than the number of gaps in the corresponding direction in the gap
decoration area, the list cycles back to the beginning. This cycling applies
within a given gap decoration area, in contrast to the cycling described
previously which applies different sets of values to multiple gap decoration
areas. Having a second level of cycling preserves backward compatibility with
existing column-rule
declarations that only specify single values, while also
enabling authors to specify simple alternating patterns.
Shorthands are also available to combine the width, style, and color properties.
.alternate-red-blue {
display: grid;
grid-template: repeat(auto-fill, 30px) / repeat(3, 100px);
grid-gap: 10px;
row-rule: 1px solid;
row-rule-color: red blue;
}
.alternate-heavy-light {
display: grid;
grid-template: repeat(auto-fill, 30px) / repeat(3, 100px);
grid-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 masonry 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: repeat(auto-fill, 30px) / repeat(3, 100px);
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;
}
By default, gap decorations are painted as continuous segments that extend as far as possible along the centerline of a given gap. The decoration is painted from one gap T intersection to another, with both endpoints at the centers of the T crossings and the decoration proceeding along the stems of both Ts. In grid layout, row decorations are painted on top of column decorations by default; changing this behavior is covered in a later section of this document.
.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 specify that decorations are instead segmented between all gap intersections. In such cases, each segment starts at the edge of a given gap intersection and proceeds to the edge of the next gap intersection. The endpoints may be offset from those endpoints using the segment outset properties.
row-rule-outset: [ <length-percentage> | join ]#
column-rule-outset: [ <length-percentage> | join ]#
The initial value is join
which "joins together" segments to give the default
"T intersections" behavior. Any other value gives the "all intersections"
behavior. Positive values extend into the neighboring intersection; negative
values recede from it. The percentage basis is the width of the gap in the
perpendicular direction. These offsets also apply at the edges of the container,
where negative values may extend into the padding area.
To facilitate animation of segment outset values, join
computes to 50%
.
.segment-outset-0px {
row-rule: 3px solid purple;
column-rule: 3px solid green;
column-rule-outset: 0px;
}
.segment-outset-plus-5px {
row-rule: 3px solid purple;
column-rule: 3px solid green;
column-rule-outset: 5px;
}
.segment-outset-minus-5px {
row-rule: 3px solid purple;
column-rule: 3px solid green;
column-rule-outset: -5px;
}
For grid containers, authors may control how gap decorations interact with spanning items within a given gap decoration area.
grid-row-rule-break: [ none | span ]#
grid-column-rule-break: [ none | span ]#
grid-rule-break: [ none | span ]#
The initial value is span
which can give either the "T intersections" or "all
intersections" behavior described previously, depending on the value of the
corresponding rule-outset
property. A value of none
causes gap decorations
to cross behind spanning items and receive the "T intersection" behavior at the
edges of the gap decoration area.
In any case, as with existing column rules in multi-column, gap decorations are painted just above the border of the container, and specifically below any items placed within the container.
.rule-break-none {
grid-rule-break: none;
}
These are designed to enable scenarios where authors wish to switch
flex-direction
or masonry-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 and masonry containers, the logical properties map based on
flex-direction
or masonry-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
.
When row and column gap decorations overlap, authors can control their painting order.
gap-rule-paint-order: [ row-over-column | column-over-row | main-over-cross | cross-over-main ]#
The main-over-cross
and cross-over-main
values are logical alternates for
row-over-column
and column-over-row
. They map similarly to properties
described in the previous section.
The initial value for gap-rule-paint-order is main-over-cross
.
.row-over-column {
row-rule: 6px solid red;
column-rule: 6px solid blue;
gap-rule-paint-order: row-over-column;
}
.column-over-row {
row-rule: 5px solid red;
column-rule: 5px solid blue;
gap-rule-paint-order: column-over-row;
}
These shorthands apply the same values in both the main
and cross
directions.
gap-rule-width: [ <'main-rule-width'> ]#
gap-rule-style: [ <'main-rule-style'> ]#
gap-rule-color: [ <'main-rule-color'> ]#
gap-rule: [ <'main-rule'> ]#
grid-gap-rule-area: [ <'grid-row-rule-area'> ]#
w3c/csswg-drafts#2748 (comment), 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;
}
w3c/csswg-drafts#2748 (comment)
.container {
gap-rule: thick solid green;
}
w3c/csswg-drafts#2748 (comment)
.container {
gap-rule-style: solid:
gap-rule-color: lightgray;
column-rule-width: 1px repeat(auto, 2px) 1px;
row-rule-width: 0px repeat(auto, 2px 1px);
grid-gap-rule-area: 2 / 2 / -1 / -1;
}
w3c/csswg-drafts#2748 (comment)
My use case for this is a left-and-right view in landscape that becomes a top-and-bottom view in portrait – with a divider in the middle (think split screen on a phone). Currently I have to use border-right in landscape, and switch to border-bottom in portrait. I would prefer setting a gap rule so the line automatically adjusts depending on the flex-direction.
.container {
display: flex;
cross-gap-rule: 1px solid black;
}
w3c/csswg-drafts#2748 (comment) - last example
.container {
gap-rule: 1px solid black;
column-rule-outset: 0px;
}
- How do gap decorations apply to subgrids?
- Can we construct an all-encompassing
gap-rule
shorthand? The challenge here is that/
is already heavily loaded in the longhands.
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.
Many thanks for valuable feedback and advice from:
- Alison Maher
- Ian Kilpatrick
- Kurt Catti-Schmidt