2/25/2022
Creating a flexible, fixed-grid layout
Use relative units in combination with the
aspect-ratio property to create responsive, smoothly
scaling fixed-grid layouts.
I ran into an issue where I needed to create a CSS grid layout with a fixed number of columns and rows (in this case, an 8 x 8 grid), with the following requirements:
- The grid container must shrink and grow responsively.
- The grid container must maintain it's aspect ratio.
- The grid items must also shrink and grow responsively.
- The grid items must also maintain their own aspect ratios.
- The grid items' children (in this case, SVGs) must also shrink and grow responsively.
The specific use case for this layout is to create a responsive chessboard that sizes based on the user's viewport. Here is the final result:
See the Pen Responsive Chessboard by John Mayes (@JohnMayes) on CodePen.
Here is the basic CSS:
.grid-container {
display: grid;
grid-template-columns: repeat(8, 1fr);
width: 80vmin;
max-width: 480px;
}
.grid-item {
display: flex;
align-items: center;
justify-content: center;
width: auto;
height: auto;
aspect-ratio: 1 / 1;
}
Here is the TLDR:
We start with a grid container with 8 columns and 8 rows, each taking up1frof space. The width of the container is responsively calculated based on the width of the user's viewport. The width of each grid item is set toauto, so it will expand to fill as much space as is available to it, in this case1fr. By usingaspect-ratio, we calculate the height of the grid items based on the item's own width. Whenever the viewport changes size, the width and height of both the grid items and the grid container will expand and shrink responsively, while maintaining it's aspect ratio. By usingvminto calculate the width of the grid container, we ensure that the container will never grow larger than the viewport's smaller dimension.
And here is the long-form write up for how I got there:
The HTML
The actual code I am working with uses React with functional components to dynamically generate each square of the chessboard, along with is classes, which are then displayed as a grid. For the purposes of this demo, however, we can just use some hand-jammed HTML:
<h1>Chessboard</h1>
<div class="board">
<div class="square white" id="1"></div>
<div class="square black" id="2"></div>
<div class="square white" id="3"></div>
...
<div class="square white" id="64"></div>
We now have one "board" div with 64 squares as children.
The Grid Container
We are going to use grid-template-columns: repeat to
create a grid layout of 8 columns (the width of a chessboard), with
each column taking up 1fr of available space. The 64 divs
we created earlier will then fill in our gird from left to right, top
to bottom, to from a nice 8 x 8 grid. However, the grid items are
currently empty, so nothing will display! Let's set the height and
width of the grid items to something arbitrary, say 40 pixels, and
then inspect with dev tools to see our grid in action.
Getting somewhere. However, nothing is yet responsive. Let's change that.
The Grid Items
First, we are going to change the grid item's height and
width to auto. This will keep each element
the same height and width as its parent container (the grid track) —
which is currently nothing, so again, nothing will display. In order
to see anything rendered,
the grid container must have an explicit width.
In order to make the grid responsive, we can set the container's width
to a relative unit of measurement. In this case I chose
60vw. Now the width of the grid container will grow and
shrink responsively, relative to the width of the user's viewport.
Almost there. However, we are still lacking an explicit height, so,
again, nothing is being displayed (remember that so far we are only
working with empty divs). This is where
aspect-ratio comes into play. The
aspect-ratio CSS property directly specifies a fixed
width to height aspect ratio for an element. It also does not rely on
parent size or viewport size: it relies on the element's own width or
height. For a perfect square (what we need in this case), use
aspect-ratio: 1 / 1.
So now we have a grid container with 8 columns, each taking up
1fr of space. The width of the container is responsively
calculated based on the width of the user's viewport. The width of
each grid item is set to auto, so it will expand to fill
as much space as is available to it, in this case 1fr,
which is again responsively calculated. Finally, by using
aspect-ratio, we calculate the height of the grid items
based on it's own width. Now the grid items are displayed.
Further, whenever the viewport changes size, the width and height of
both the grid items the the grid container will expand and shrink
responsively, while maintaining it's aspect ratio.
Finally, setting the grid items to display: flex will set
us up for the final "piece" of this puzzle. The chess pieces, to be
exact.
The SVG elements
The project I am working on uses SVGs to represent chess pieces on a
board. I need those SVGs to scale along with the chessboard! However,
explaining SVG scaling in detail is beyond the scope of this article.
The TLDR is that in order for an SVG element to scale proportionally
with the size of it's parent element, while maintaining it's aspect
ratio, the width and height values of the
SVG's viewBox attribute need to equal the width and
height of the coordinate system used in the SVG element itself (i.e,
viewBox="0 0 [original svg width] [original svg height]"). Next, remove any height and width properties on the SVG element
itself — the SVG will then scale to fill the available space in it's
parent element, while automatically preserving it's aspect ratio.
Nice!
Bonus: a little more mobile responsiveness
Let's change the unit of measurement used to calculate our grid width
from vw to vmin. This ensure that the grid
container will never grow larger than the viewport's smaller
dimension. This means that on a mobile device the chessboard will
always be fully visible, wether or not the device is in portrait or
landscape mode. No media queries required.
Again, here is the final result:
See the Pen Responsive Chessboard by John Mayes (@JohnMayes) on CodePen.