iTranslated by AI
Rethinking CSS Grid for Programmatic Layout Control
This article is about how to cleanly control and generate "programmatically controlled layouts," rather than the context of existing CSS layouts.
It's a discussion on how to leverage Grid to handle layouts primarily from a JavaScript perspective in complex SPAs or some kind of authoring environment.
Visual Correspondence of grid-template-areas
In a world without IE, you can use the full features of CSS grid.
When using grid, I have personally enjoyed using grid-template-areas. This is because the visual information in the CSS matches the final display.
For example, here is what layout configuration code using grid-template-areas written in Svelte looks like.
<div class="grid">
<div style="grid-area: text1">
<slot name="text1" />
</div>
<div style="grid-area: text2">
<slot name="text2" />
</div>
<div style="grid-area: img1">
<slot name="img1" />
</div>
</div>
<style>
.grid {
width: 500px;
height: 500px;
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(12, 1fr);
grid-template-areas:
". text1 text1 . . . . . "
". text2 . img1 img1 . . . "
". . . img1 img1 . . . "
". . . img1 img1 . . . "
". . . img1 img1 . . . "
". . . img1 img1 . . . "
". . . img1 img1 . . . "
". . . . . . . . "
". . . . . . . . "
". . . . . . . . "
". . . . . . . . "
". . . . . . . . ";
}
</style>
Usage side:
<script>
import Layout from "./Layout.svelte";
</script>
<Layout>
<div slot="text1">xxx</div>
<div slot="text2">yyy</div>
<img src="..." slot="img1" />
</Layout>
grid-template-areas is a kind of ASCII art where you enter the names of grid-area separated by spaces. The . means an empty cell.
While this is a Svelte example, the slot semantics are the same as Web Components or Vue, so it should be easy to understand. Content is fitted into the corresponding slot.
The actual width of a single cell in the above example is repeat(8, 1fr), which divides the space into 8 equal parts using the fr unit (the ratio equivalent to flex: n in flexbox). You can also specify fine-grained widths like 16px 1fr 15px 2rem 3fr ....
The concept of grid-template-areas is to name areas on a two-dimensional matrix and place them by name correspondence without the user side knowing the actual physical position. This means the user side only needs to know the expected slot names.
grid-template-areas - CSS: Cascading Style Sheets | MDN
Compared to flexbox, when doing two-dimensional placement, there's no need to nest another div to switch the flex-direction. I think this is a clear advantage of grid over flexbox.
Limitations of grid-template-areas: overlap
As far as I know, grid-template-areas has two problems:
- The CSS output size increases as the unit cells become finer.
- Overlapping
grid-areas cannot be represented.
Point 1 is a performance issue, and it really only becomes a problem at a small granularity like a third-party script (I encountered this while creating a third-party script with Svelte). However, point 2 is an expressive limitation, which left me stuck when I needed it. grid-template-areas assumes that a cell is occupied by a single area, so it breaks down when trying to overlap or intersect areas.
Specifically, you cannot express layouts like placing text over a background image.
While you could set an image as the background for a text element, placing multiple text items over a single large image is still impossible.
When I looked into how to achieve this, I found an article titled Overlapping Grid Items that explained you can specify the grid-area from the child element side.
.item.one {
grid-row-start: 1;
grid-row-end: 2;
grid-column-start: 1;
grid-column-end: 4;
}
The shorthand for this is grid-area, which allows you to specify all four values at once.
.item.two {
grid-area: 1 / 1 / 4 / 2;
}
So, I thought, "Wouldn't it be great if I could specify this using CSS variables?" and decided to try it out.
Layout Specification combining CSS Vars and Grid
Using this grid-area, we can specify layout variables from the parent side. Since grid-row-start or grid-column-end were confusing to me, I decided to think in terms of a rectangle defined by x1, y1, x2, y2.
Here is the raw HTML and CSS.
<div class="grid">
<div class="grid-item" style="--x1: 5; --y1: 2;--x2: 8; --y2: 7; --color: red; --z: 1;"></div>
<div class="grid-item" style="--x1: 2; --y1: 4;--x2: 11; --y2: 5; --color: white; --z: 2"></div>
</div>
.grid {
width: 400px;
height: 400px;
grid-gap: 10px;
background-color: wheat;
display: grid;
grid-template-rows: repeat(16, 1fr);
grid-template-columns: repeat(16, 1fr);
}
.grid-item {
grid-area: var(--y1) / var(--x1) / var(--y2) / var(--x2);
background: var(--color);
z-index: var(--z);
}
In this setup, x1, y1, x2, y2 are not 0-indexed... they are 1-indexed. Setting --x1: 0; --y1: 0; won't point to the top-left; it becomes an invalid grid-area specification.
Therefore, if you want to use 0-indexing, you can use calc as follows.
.grid-item {
/* y1, x1, y2, x2 */
grid-area: calc(var(--y1) + 1) / calc(var(--x1) + 1) / calc(var(--y2) + 1) / calc(var(--x2) + 1);
background: var(--color);
z-index: var(--z);
}
I have posted this on CodePen.
Grid Elements in Svelte
Using the code above as is would be a bit disappointing because writing styles inline lacks editor support. I felt it would be easier to handle if adapted into framework-specific expressions.
This time, I will try it with Svelte. I'll define two components: Grid and GridItem.
<!-- Grid.svelte -->
<script>
export let rows;
export let columns;
export let gap = "8px";
export let background = "transparent";
$: gridRoot = `
grid-template-columns: ${typeof columns === "number" ? `repeat(${columns}, 1fr)` : columns };
grid-template-rows: ${typeof rows === "number" ? `repeat(${rows}, 1fr)` : rows };
grid-gap: ${gap};
background: ${background};`;
</script>
<div class="grid" style={gridRoot}>
<slot />
</div>
<style>
.grid {
width: 500px;
height: 500px;
display: grid;
}
</style>
<!-- GridItem.svelte -->
<script>
export let x1;
export let x2;
export let y1;
export let y2;
export let background = 'black';
$: gridAreaStyle = `grid-area: ${y1 + 1} / ${x1 + 1} / ${y2 + 1} / ${x2 + 1}; background: ${background}`;
</script>
<div style={gridAreaStyle}>
<slot />
</div>
Then, the code to use these looks like this:
<script>
import Grid from "./Grid.svelte";
import GridItem from "./GridItem.svelte";
</script>
<Grid
rows={16}
columns={16}
gap="8px"
background="wheat"
>
<GridItem x1={3} y1={3} x2={9} y2={5} background="rgba(255,0,0,0.5)">
xxx
</GridItem>
<GridItem x1={5} y1={1} x2={6} y2={7} background="rgba(255,0,0,0.5)">
yyy
</GridItem>
</Grid>
Compared to grid-template-areas, you lose the visual intuition, but in return, you can think in terms of a two-dimensional matrix with explicit coordinate specification.
While it doesn't allow for the separation of layout and actual position that was possible with grid-template-areas, it could be achieved by wrapping it in another layout component. Since it makes visual control easier, I think it's a matter of trade-offs.
I have placed this in the Svelte REPL.
Bonus: Other Grid Uses
The simplest implementation for centering.
.center {
width: 100%;
height: 100%;
display: grid;
place-items: center;
}
You can do this with flexbox too, but this way is easier.
Bonus: grid-template-areas and Media Queries
A technique to switch grid-template-areas using media queries. It makes it easy to re-output the same content for responsive support.
.grid {
display: grid;
width: 500px;
height: 500px;
grid-template-rows: 1fr 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
}
.grid-item {
display: grid;
place-items: center;
background: wheat;
}
.grid {
grid-template-areas:
'a b c'
'a b c'
'a b c'
}
/* For mobile */
@media (max-width: 480px) {
.grid {
grid-template-areas:
'a a a'
'b b b'
'c c c'
}
}
Disadvantages of Grid
- It's not that it doesn't work in IE, but since it uses an old specification, you need to fix it with Autoprefixer or similar tools.
- It cannot be used in React Native. Since you can't really apply what you've learned about Grid, you might feel a bit disappointed the more you learn CSS Grid.
- I feel that the current mainstream for CSS that requires IE support is flexbox, and if that's the case, there may be few people who can read CSS Grid.
- It is not suitable for inserting variable content. Anything that exceeds the area will overflow.
- Using grid to align things in a single direction feels like overkill compared to flexbox.
Conclusion
For those who want to learn about Grid, I recommend Grid Garden.
Grid Garden - A game for learning CSS Grid
Some of the techniques written in this article, especially the method of handling overlaps with CSS variables, are insights gained by observing the CSS output by a service called Grid-based website builder | Grid.studio.
Grid makes it easy to create programmable expressions, so it seems suitable for variably controlling layout structures in correspondence with data structures. This has been a discussion about how many things seem possible with it.
Discussion