AGS Logo AGS Logo

CSS Subgrid
Creating a layout using CSS grid and subgrid

CSS Subgrid

Let's create a layout using subgrid. Still rather new at the time of writing this article and not yet supported in all major browsers, one of the new features currently being implemented with in the CSS Grid Layout Module is subgrid. Figure 1 shows the current browser support for subgrid.

Data on support for the css-subgrid feature across the major browsers from caniuse.com
Current browser support for CSS Subgrid

But first, what functionality does subgrid bring that was not previously available in grid? What subgrid does is allow child elements to be able to use the tracks created on their parent elements. In other words, child elements operate on their parent's rows and columns allowing us to align our elements much more neatly. Let's look at how this works in practice.

Subgrid

We are going to use subgrid to layout a list of 4 projects found in a list that each have an image, name, description, and link in a 4 column layout. Our HTML looks as follows (listing 1):

<section class="projects">
  <h2>My Projects</h2>
  <ul>
     <li>
       <img src="https://ignitioncms.com/img/ignition-light.svg" alt="Ignition Logo" title="Ignition" height="100">
       <h3>Ignition CMS</h3>
       <div class="content">
          <p>Ignition is a headless CMS...</p>
       </div>
       <a href="https://ignitioncms.com" target="_blank" rel="noopener">Check out Ignition CMS</a>
    </li>
    ...
  </ul>
</section>
Starting HTML

And our webpage looks as seen in figure 2.

screen capture of starting point in Firefox browser
Starting point

We start by giving our list a display property value of grid and defining 4 columns (one for each project). Since we want all 4 columns to be the same width we use repeat(), which allows us to define the number of columns to create for a specified dimension. We also remove the list style and default padding from the list. Our CSS therefore looks as follows (Listing 2).

  .projects ul {
    list-style-type: none; /* remove list style (bullet points) */
    padding: 0;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
Screen capture of starting point

Now that we have our columns (figure 3) we can start focusing on the individual elements found within each project (image, name, description, and list).

Screen capture content layed out in 3 columns. The individual project's contents are aligned to the top of their columns using normal content flow.
Four column layout

Notice that the project names and description sections are not all the same length. (Yes the extra characters at the end of the second project - Cirrostyle - are not an oversight, they are there to make the title wrap). What we want to do is have each type of content neatly aligned horizontally. We will use subgrid to achieve this.

The subgrid property value gets assigned to either grid-template-columns, grid-template-rows, or both. In our case, since we are looking for horizontal alignment, we will use it on grid-template-rows. Notice that we still need to give the project (<li>) a display value of grid (listing 3).

  .projects ul li {
    display: grid;
    grid-template-rows: subgrid;
  }
Adding subgrid to the individual projects

At this after applying this property, we will not notice all of our content is now found in the first row as seen in figure 3.

Screen capture content layed out in 3 columns. The individual project's contents are aligned to the top of their columns in one row. The individual project rows are overlapping each other
All project content in one row

Let's go ahead and define our rows. This will be done on the parent (the <ul>). We are going to give each row the following values:

  • First row (image): 100px
  • Second row (project name): max-content
  • Third row (description): auto
  • Fourth row (link): max-content

Our updated list CSS will therefore look as follows (listing 4):

.projects ul {
  list-style-type: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: 100px max-content auto max-content;
}
Adding row definitions to the list

Although we defined our rows, we can see that our content is still found in the first row (figure 5). The row is now smaller, but the project's individual elements have not been distributed across the newly created rows.

Screen capture content layed out in 3 columns. The individual project's contents are aligned to the top of their columns in one row. The individual project rows are overlapping each other.
Project content overlapping in a 100px tall row

Even if we explicitly assign rows to each individual project element (listing 5) we find that our content stays stubbornly stuck in that first row (figure 6).

.projects ul {
  list-style-type: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: 100px max-content auto max-content;
}
Adding row definitions to the list
Screen capture content layed out in 3 columns. The individual project's contents are aligned to the top of their columns in one row. The individual project rows are overlapping each other. The row is now 100px tall.
Content still not aligned in rows

The reason the project content elements are sticking to that first row is because we have not assigned a row value to the project itself (<li>). Unless otherwise defined, by default elements will take up 1 cell's worth of space (both horizontally and vertically). We need our <li> element to span 4 rows. This will render the rows available to the subgrid and the project content will be able to align itself correctly. Once we update our <li>'s (listing 5) we see that our project elements fall into place (figure 7).

.projects ul li {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: 1 / 5;
}
Making the
Screen capture content layed out in 3 columns and each project's content is neatly aligned horizontally with each project element (image, name, description, and link) in their respective rows.
Applied subgrid

With our content in the correct rows, let's do a little bit of clean up. Our images extend past the width of the columns and are causing some overflow. To fix that, we will give our images a max-width of 100%.

We are also going to add some space between our columns using the gap property. Although we want to add space between the columns, we don't want to add any between the rows. We therefore assign 2 values to our declaration. A first one of 0 (row-gap) and a second of 2rem (column-gap). Listing 7 shows our updated CSS and figure 8 our progress thus far. Notice that the gap declaration is added to the list, not the individual projects.

.projects ul li img {
  grid-row: 1;
  max-width: 100%; /* constrains image width */
}

.projects ul {
  list-style-type: none;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: 100px max-content auto max-content;
  gap: 0 2rem; /* gap between the columns but not the rows */
}
Constraining the image widths and adding a column gap
Screen capture of projects with added white space between the columns and image size constrained. The content no longer overflows its columns.
Applied image max-width and gap

Let's put some finishing touches on our layout. We are going to center the images horizontally in the middle of their columns and center the project names vertically in the middle of their rows. Let's start with the images. To center the images horizontally we will use justify-self with a value of center. To center to project names vertically, we use align-self also with a value of center as seen in listing 8.

.projects ul li img {
  grid-row: 1;
  max-width: 100%;
  justify-self: center; /* horizontal alignment */
}

.projects ul li h3 {
  grid-row: 2;
  align-self: center; /* vertical alignment */
}
Aligning individual elements

With these final adjustments, our layout looks pretty good (figure 9) but let's take a look at what we look like in Chrome (figure 10). Spoiler, at the time or writing this article, Chrome does not support subgrid yet.

Screen capture of Firefox with elements correctly vertically and horizontally aligned within their rows and columns.
Completed layout in Firefox
Screen capture of Chrome with elements correctly found in their columns but the vertical alignment of the row alignment is incorrect as subgrid is not supported yet.
Subgrid unsupported in Chrome

Creating a fallback

Although our layout doesn't look awful in Chrome, let's go ahead and make it a little better by adding a fallback. We are going to use the @supports at-rule to provide an alternative solution for browsers that don't support subgrid yet. By specifically adding conditional styles to browsers that don't support subgrid, we create a situation where when the property does gain support, our code will automatically start using our preferred solution. Once the property gains broad support, we can simply delete our at-rule without needing to touch the rest of our code.

Let's focus on aligning our call-to-action (the link) at the bottom of the projects. Our project already has a display value of grid so we will simply provide and alternate value for our grid-template-rows. Our rule therefore looks as follows (listing 10):

@supports not (grid-template-rows: subgrid) {
  .projects > ul li {
    grid-template-rows: 100px min-content auto min-content;
  }
}
Fallback rule

Although not an identical layout as in Firefox, by adding the fallback we now have a working solution in Chrome that will "upgrade" itself to using subgrid once subgrid is supported by the browser (figure 11).

Screen capture of Chrome with fallback applied. The project content is aligned to the top of the column rather than each piece being horizontally with the equivalent of the same kind horizontally except for the links which are correctly aligned at the bottoms of the columns.
Fallback applied in Chrome

You can find a working version of the code on codepen at https://codepen.io/martine-dowden/pen/yLRawVP.

Happy Coding!

Specialties

Overview of our specialties including: accessibility, angular, CSS, design, and Firebase

License: CC BY-NC-ND 4.0 (Creative Commons)