Subgrids has become a hot topic on the past weeks, mostly due to an article by Eric Meyer called “Subgrids Considered Essential”, that shows some examples where subgrids can be really useful. Which is somehow based on a previous post by fantasai on the same topic. Also Rachel Andrew has talked about subgrids on her blog and her last ebook about Grid Layout (which is quite handy to get an introduction to grid syntax and main features).

As you probably know, Igalia has been working on the CSS Grid Layout implementation on Chromium/Blink and Safari/WebKit for a while. As implementors we thought it would be nice to share our feedback on this topic, so this week the team working on Grid Layout (Sergio, Javi and myself) arranged a meeting to review subgrids feature. These are our initial thoughts about it and a first draft proposal about a possible implementation.

State of art

Subgrids feature allows us to create nested grids that share lines with the grid container. We just define the size of the tracks on the main grid container, and the subgrid use those sizes to layout its items.

In a grid, only direct children are grid items and can be positioned on the grid. We can mark one of those items as subgrid, and then the subgrid’s children will be like regular items for the main grid. This is pretty hard to explain with words so we’ll follow up with some examples in the next section.

The feature has been marked as “at-risk” in the specification since January 2014 Which means that it could be moved to the next level (or version) of the spec.

Current situation is that none of the grid implementations (nor any web engine, neither any polyfill) support subgrids yet. Mostly because it’s a pretty complex feature and implementors have been focused on other parts of the spec.

Use cases

In order to understand what subgrids are, let’s explain a few use cases. The canonical example for subgrids feature is a regular form defined using a list:

<form>
    <ul>
        <li><label>Name</label><input></li>
        <li><label>Surname</label><input></li>
        <li><label>Location</label><input></li>
        <li><label>Email</label><input></li>
    </ul>
</form>

Ideally we’d like to define a grid with the labels on the first column and the inputs on the second one. If we define the <ul> element as a grid container, we’ll need to define the <li> as subgrids to be able to place the labels and inputs as desired. Without subgrids we’d need to remove the list (probably replacing <ul> by <div> and removing <li> tags) flattening our HTML and losing the semantic structure, which is really bad from the accessibility point of view.

With subgrids support we could use the following CSS:

ul {
    display: grid;
    grid-template-columns: auto 1fr;
}

li {
    display: grid;
    grid: subgrid;
}

label {
    grid-column: 1;
}

input {
    grid-column: 2;
}

Example of form using subgrid Example of form using subgrid

For this particular purpose, we could be using display: contents; (which is only supported in Firefox so far) instead of subgrids. Just applying display: contents; to the <li> elements we’ll have the very same result.

On the other hand, another of the subgrids examples that we can find out there is a catalog of shop items. Imagine that those products are composed by title, picture and description.

The HTML of each item would be something like this:

<div class="product">
    <h1>Title</h1>
    <img src="product.jpg" />
    <p>Long description...</p>
</div>

And imagine that we use the following CSS:

body {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
}

.product {
    display: grid;
    grid: subgrid;
}

h1 {
    grid-row: 2;
}

img {
    grid-row: 1;
}

p {
    grid-row: 3;
}

The goal is that each product appears in a subgrid of 3 rows, that way the titles and descriptions are horizontally aligned. For that purpose we’re defining a main grid of 3 columns, and a subgrid for each product with the 3 rows.

Example of catalog using subgrid Example of catalog using subgrid

As we can see even if the title has 2 lines, or independently of the lines in the description, the items are properly aligned.

This cannot be achieved with display: contents;, because of it’ll add all the products in a single row (actually 3 rows in the grid). Because we’re setting the row explicitly to the <h1> (row 2), <img> (row 1) and <p> (row 3) elements. Without subgrids, those rows cannot be automatically translated to rows 5, 4 and 6, when the products have filled the 3 columns defined on the main grid.

Main issues

This and next section are very technical and should be discussed with the CSS Working Group (CSS WG), but we thought a blog post would be better to show examples than a simple mail to www-style.

We’ve been reading the spec and found some potential issues thinking about how to implement subgrids. These are some of them in no specific order.

Margin, border and padding

Probably the most important issue is the problem with margin, border and padding on the subgrid. And how they affect to the grid track sizing algorithm.

Let’s try to use a simple example to explain it, imagine a 3x2 grid where we’ve a subgrid taking the first row completely.

<div style="display: grid; grid-template-columns: auto auto auto; grid-template-rows: auto auto;">
    <div style="display: grid; grid: subgrid; grid-column: 1 / 4; grid-row: 1 / 2;">
      <div style="grid-column: 1;">Item 1</div>
      <div style="grid-column: 2;">Item 2</div>
      <div style="grid-column: 3;">Item 3</div>
    </div>
</div>

Imagine that each item’s width is 100px, so the columns will be 100px width each one.

However, if we set for example the padding on the subgrid (e.g. padding: 0px 50px;), we’ll have to do some extra operations to calculate the size of the tracks:

  • First column, as it’s on the left edge of the subgrid we’ll be adding 50px to the size computation. Resulting on a 150px track.
  • Second column is not affected by the padding, so it keeps being 100px.
  • Third column, is in a similar situation than the first one because it’s on the right edge of the subgrid. So the final size is 150px.

Example of subgrid with padding Example of subgrid with padding

The problem here is that the grid track sizing algorithm works directly with the grid items. So it’d need to know if the item is in one of the edges of the subgrid in order to use the margin, border and/or padding to compute the size of the track.

On a first sight this might seem doable, but if we think on items spanning several tracks and nested subgrids things become really complex. For example, imagine that we’ve a nested subgrid that takes 2nd and 3rd columns on the first row with padding: 0px 25px;.

Example of nested subgrids with padding Example of nested subgrids with padding

For each column of the main grid container, we’d need to take into account the different paddings of the subgrids. So for each item we’d need to know not only if it’s on the edge of the subgrid but also check the ancestors.

On a related note, having scrollbars on the subgrids have similar issues related to track sizing.

Implicit tracks on the subgrid

The issue here is what happens when we get rid of explicit tracks on the subgrid. According to the spec, the items in this situation should use implicit tracks of the subgrid, and not take tracks from the main grid. Which means that they’re not aligned with the main grid (they don’t share lines with it).

Let’s use an example to illustrate this issue:

<div style="display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px;">
    <div>i1</div>
    <div>i2</div>
    <div>i3</div>
    <div style="display: grid; grid: subgrid; grid-column: 1 / 3; grid-row: 2 / 3;">
      <div style="grid-column: 1;">si1</div>
      <div style="grid-column: 2;">si2</div>
      <div style="grid-column: 3;">si3</div>
      <div style="grid-column: 4;">si4</div>
    </div>
</div>

Example of subgrid with implicit tracks Example of subgrid with implicit tracks

As we can see the subgrid items si3 and si4 are placed in implicit tracks of the subgrid. Those tracks has no relation with the main grid container, literally from the spec: “they effectively extend in a third dimension”. We cannot think in a use case for this particular topic, and it seems really weird as the main goal of the subgrid is to share the lines with the main grid container.

Besides, imagine a different example of an auto-sized column, and some items from a subgrid in that column that span in and out the explicit subgrid. It’d be really complex from the grid track sizing algorithm point of view, to determine the size of that auto-sized column of the main grid container.

Automatic grid span

In the previous examples we were defining subgrids explicitly setting both the grid position (the initial column and row for the subgrid) and the grid span (how many cells the subgrid takes in each direction).

However, we can define a subgrid just setting the grid position with an automatic grid span. According to the spec, the implicit tracks on the subgrid will determine the span.

Again hopefully one example can help to get the idea:

<div style="display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px;">
    <div style="display: grid; grid: subgrid; grid-column: 2; grid-row: 1;">
      <div style="grid-column: 1;">si1</div>
      <div style="grid-column: 2;">si2</div>
      <div style="grid-column: 3;">si3</div>
    </div>
</div>

The subgrid’s grid position is set to the 2nd column and the 1st row. Then the first 2 items (si1 and si2) are placed in the tracks from the main grid (2nd and 3rd column respectively). But the last item (si3) get rid of tracks, so it’s added on an extra implicit track on the subgrid unrelated to the main grid container. Because the subgrid doesn’t create implicit tracks on the main grid,

Example of subgrid with automatic grid span Example of subgrid with automatic grid span

This seems quite complex to manage, specially if the subgrid cannot create implicit tracks on the main grid container. Also, if we combine it with auto-placement (so we don’t set the grid position), finding the right position and span might be tricky.

On top of that, imagine that one of the items in the subgrid is positioned using grid-column: -1;. As the subgrid doesn’t have a grid span yet, probably it’d be placed on the first column of the subgrid, when we’d be expecting it to be on the last one.

Subgrid only in one axis

The subgrid keyword can be set in both properties defining the grid (grid-template-columns and grid-template-rows) or only in one of them. If we set it only in one of them, we’re creating a subgrid only in one direction.

Let’s see one example:

<div style="display: grid; grid-template-columns: 150px 150px; grid-template-rows: 150px 150px;">
    <div style="display: grid; grid-template-columns: subgrid; grid-column: 1 / 3; grid-row: 1 / 2;">
      <div style="grid-column: 1;">si1</div>
      <div style="grid-column: 2;">si2</div>
      <div style="grid-column: 1;">si3</div>
      <div style="grid-column: 2;">si4</div>
    </div>
</div>

In this case we’re defining a subgrid only for the columns direction, so in order to calculate the size of the items in the 1st and 2nd columns it use the track sizes defined in the main grid container.

However, for the rows we’re not setting anything, so it uses auto-sized rows. This means that the rows will behave like in a regular nested grid, completely independent of the parent grid container.

Example of subgrid only in one axis Example of subgrid only in one axis

Probably this is feasible to implement, but again it seems a weird feature, we might need some good use cases to decide about it.

Descendants navigation

Another issue with subgrids is that we can nest them as much as we want. So, in order to determine the size of a track in the grid container, we need to traverse not only the direct children, but also all the descendants. This can have a huge performance impact if nested subgrids are abused.

Regarding this point, probably we’ve to simply live with it and navigate all the descendants being aware of the performance penalty.

Grid gaps

Probably grid gaps properties should be ignored in the subgrids. We want the items to be aligned with the main grid container, so having different gaps seems really strange.

This is not in the spec, but we guess that the reason is that grid gaps were added after subgrids, so that part of the spec was not updated.

Other

There are other things that we didn’t explore in detail. For example:

  • Named grid lines from the main grid container used in the subgrids.
  • Positioned items in the subgrids.
  • Subgrids with orthogonal flows.

And we’re sure there are more things that we’re missing at this moment.

Draft proposal

As we can see there are a bunch of potential issues, of course they need a deeper analysis and further discussions, but the complexity of the whole feature seems not small at all.

For this reason, we might want to think in a simpler proposal. François Remy has already sent one to the CSS WG mailing list.

We’ve been thinking in another idea, trying to fulfill the use cases described previously in this post, and avoid the issues as much as possible. This is a pre-alpha proposal, so take it with a grain of salt.

The ideas would be to have a limited support of subgrids:

  • Make the subgrid pretty similar to display: contents;, so we cannot set margin, border or padding on it. That will simplify the calculations needed to determine the size of the tracks and the items.

    Main difference with display: contents; is that the subgrid will allow to translate positions depending on the final placement on the main grid container. Like in the use case of the shop catalog.

  • Remove implicit tracks on the subgrid, so we need to always set a grid span (how many columns and rows the subgrid takes) beforehand.

    Items trying to be placed on the implicit grid of the subgrid, would end up on the first cell of the subgrid. For items spanning outside the explicit grid, the span would be truncated at the end edge of the subgrid.

  • It seems clear that items in the subgrids need to be positioned before the items in the main grid container. And once all the positions are resolved, we could try to run the grid track sizing algorithm as usual.

  • Probably on an initial approach only allowing subgrids in both axis might be enough.

With this proposal the use cases explained in the first section would be covered, we’d need to be more specific on the CSS, but they’ll work.

We’d lack the chance to set CSS properties on the subgrid itself, but we could add an extra item on the subgrid that spans the whole subrid and paint a border, background or whatever is needed. Not an ideal solution anyway.

Wrap-up

This post is mostly a braindump from the Igalia team working on CSS Grid Layout, after one day discussing about this part of the spec. We just want to share it with the rest of the web community, the CSS WG and anyone interested on the topic. In order to help to move forward the decision regarding what do to with subgrids in the CSS Grid Layout specification.

Lots of questions about subgrids are still hanging in the air:

  • Defer them to level 2?
  • Simplify them and keep them on level 1?
  • Keep them as they are and wait for subgrids before shipping CSS Grid Layout?

We think there aren’t good answers to these questions right now, hopefully we all together will find the right solution and find a final solution that makes happy all the involved parties. 😄

Last but not least, there’s going to be a CSS Grid Layout Workshop in New York City by the end of the month organized by one of the spec editors (fantasai). My colleague Sergio, who is the maintainer of Grid Layout code in Blink and WebKit will be participating in this event. Hopefully this and other topics will be clarified there, and the specification could move to Last Call Working Draft (LCWD) towards Candidate Recommendation (CR)!

Igalia and Bloomberg working together to build a better web Igalia and Bloomberg working together to build a better web

Finally, we’d like to thank again Bloomberg for supporting Igalia in the development of CSS Grid Layout.

Translations