CSS, at least in Chromium browsers, has a new feature to create lines (rules) between columns and rows — think CSS column rules extended to grid. This will be a popular feature in the future, given all the styling it enables. While we wait for Safari and Firefox, here is a utility for creating a visual grid. It uses a technique Kevin Geary shared a few years ago as a fallback.
.u-ruled-grid {
/* Rule appearance */
--rule-grid-color: currentColor;
--rule-grid-width: 1px;
--rule-grid-style: solid;
/* Grid spacing */
--rule-grid-row-gap: 1.5rem;
--rule-grid-column-gap: 1.5rem;
/*
* Native controls that are not part of the `rule` shorthand.
*
* break: none | normal | intersection
* inset: <length-percentage> | overlap-join
* visibility: all | around | between | normal
* overlap: row-over-column | column-over-row
*/
--rule-grid-break: none;
--rule-grid-inset: 0;
--rule-grid-visibility: all;
--rule-grid-overlap: row-over-column;
/* Fallback controls */
--rule-grid-span: 100vmax;
--rule-grid-row-span: var(--rule-grid-span);
--rule-grid-column-span: var(--rule-grid-span);
--rule-grid-fallback-inset-start: 0px;
--rule-grid-fallback-inset-end: 0px;
--rule-grid-row-z-index: 2;
--rule-grid-column-z-index: 1;
display: grid;
row-gap: var(--rule-grid-row-gap);
column-gap: var(--rule-grid-column-gap);
/* Native gap decorations */
@supports (rule: 1px solid) {
rule: var(--rule-grid-width) var(--rule-grid-style) var(--rule-grid-color);
/* Keep these separate because browsers may ship them independently. */
@supports (rule-break: intersection) {
rule-break: var(--rule-grid-break);
}
@supports (rule-inset: 0) {
rule-inset: var(--rule-grid-inset);
}
@supports (rule-visibility-items: all) {
rule-visibility-items: var(--rule-grid-visibility);
}
@supports (rule-overlap: row-over-column) {
rule-overlap: var(--rule-grid-overlap);
}
&.u-ruled-grid--rows {
column-rule: none;
}
&.u-ruled-grid--columns {
row-rule: none;
}
}
/* Pseudo-element fallback */
@supports not (rule: 1px solid) {
overflow: hidden;
overflow: clip;
isolation: isolate;
& > * {
position: relative;
&::before,
&::after {
content: '';
position: absolute;
pointer-events: none;
}
/* Column rules */
&::before {
inline-size: 0;
block-size: calc(var(--rule-grid-column-span) - var(--rule-grid-fallback-inset-start) - var(--rule-grid-fallback-inset-end));
inset-block-start: var(--rule-grid-fallback-inset-start);
inset-inline-start: calc((var(--rule-grid-column-gap) + var(--rule-grid-width)) / -2);
z-index: var(--rule-grid-column-z-index);
border-inline-start: var(--rule-grid-width) var(--rule-grid-style) var(--rule-grid-color);
}
/* Row rules */
&::after {
inline-size: calc(var(--rule-grid-row-span) - var(--rule-grid-fallback-inset-start) - var(--rule-grid-fallback-inset-end));
block-size: 0;
inset-inline-start: var(--rule-grid-fallback-inset-start);
inset-block-start: calc((var(--rule-grid-row-gap) + var(--rule-grid-width)) / -2);
z-index: var(--rule-grid-row-z-index);
border-block-start: var(--rule-grid-width) var(--rule-grid-style) var(--rule-grid-color);
}
}
&.u-ruled-grid--rows > *::before {
display: none;
}
&.u-ruled-grid--columns > *::after {
display: none;
}
}
/* Break rules at row and column intersections. */
&.u-ruled-grid--break-intersection {
--rule-grid-break: intersection;
--rule-grid-row-span: 100%;
--rule-grid-column-span: 100%;
}
/* Keep rules continuous through intersections. */
&.u-ruled-grid--break-none {
--rule-grid-break: none;
--rule-grid-row-span: var(--rule-grid-span);
--rule-grid-column-span: var(--rule-grid-span);
}
/* Paint column rules above row rules. */
&.u-ruled-grid--column-over-row {
--rule-grid-overlap: column-over-row;
--rule-grid-row-z-index: 1;
--rule-grid-column-z-index: 2;
}
}