The as-of-yet-unsupported CSS display: contents is one of those things you probably won’t consider until it becomes necessary.

Here’s my example: Our designers had some great ideas about presenting consistent field layout in forms. To start, they wanted to have all fields align in rows and columns, with labels for each input. Each input area must be a minimum of 53 pixels in height; if the content doesn’t fit that area, it should center vertically.

Easy. For the next requirement, we needed to have inputs align even if labels were omitted:

Ok, that’s still pretty straightforward, as long as we assume labels are a fixed height. But there’s one last thing: if there are no labels on a row, the label space should collapse.

Funny how something so basic can introduce complexity. We want to semantically group labels and inputs together, but from a layout perspective, labels are grouped to horizontally adjacent labels, and inputs are grouped to horizontally adjacent inputs.

That means we can’t get the code we want, which might look like this:

<Fields>

<FieldRow>

<Field label="label for input 1">

<input value="foo" />

</Field>

<Field label="label for input 2">

<input value="bar" />

</Field>

</FieldRow>

</Fields>

… without doing a lot of ‘component introspection’ in React to determine whether the labels have been included, whether to render a ‘filler’ space where the label would be in order to accommodate the presence of a label on an adjacent field, etc. That’s a lot of complexity (and risk of bugs) for such a simple layout!

Using CSS-Grid

This seems like an obvious win for CSS-Grid. We’ve got a two-dimensional layout which needs specific layout rules for certain sections (think: grid-template-areas).

However, what we really need is sub-grids in CSS-Grid. For more information on the missing sub-grid spec, see this post (the example problem provided there is directly analogous to the layout problem we’re dealing with here).

That article discusses how display: contents actually doesn’t replace sub-grids for all use cases. Because it more or less ‘pretends’ that a certain semantic layout is applied, without actually applying it, there are quirks to how styling gets computed in relation to other rules.

Not to mention, display: contents isn’t really available on many users’ browsers at the time of writing.

Good thing we’re using React, so we can get a little more advanced with how we generate styling. It turns out the React version of display: contents may actually provide us with the pseudo-sub-grid we need.

Semantic Association with Fragments

If you’re not familiar with React Fragments, you may have missed their debut in React 16. Fragments are a solution to the age-old React problem of not being able to return an array of components from a render call. With React.Fragment , you can encapsulate multiple component nodes as one ‘logical’ node.

const MultiComponent = () => (

<React.Fragment>

<div>Part 1</div>

<div>Part 2</div>

</React.Fragment>

);

React.Fragment looks and acts like a regular component, but it disappears from the markdown during rendering. If you rendered the component above inside a <div> , you’d get the following HTML:

<div>

<MultiComponent />

</div> Produces: <div>

<div>Part 1</div>

<div>Part 2</div>

</div>

When you see it in action, it’s clear: Fragments are kind of like the display: contents of React — but they work today, and don’t produce any CSS quirks.

So let’s revisit the original problem: we want to semantically associate Inputs and Labels into Fields, but we want horizontally aligned Inputs and Labels to be associated in layout.

We can accomplish this by making our Field component export a fragment containing a label and an input. Now our label and input are semantically grouped within Field, but within the layout they are assimilated into their direct parent’s CSS-Grid layout.

Check it out in the pen below:

Things to dig into: FieldRow has a lot going on inside to help exploit CSS-Grid in our favor. When provided with a number of columns, it generates new grid-template-area s for labels and content. It also clones its children inline and provides each one with an index prop to inform it of which grid-area its children should be targeting. So there’s a little magic, but it’s manageable magic. There may be a way to remove the need to generate grid-area names; I’m still exploring the possibilities of CSS-Grid and learning new things every time I use it.

There are a few things which we could extend from here. Better support for custom field content, using grid-column-span with fields to let them span multiple columns, etc. Because we get the logical advantage of rendering all the children of Field directly into the containing FieldRow, we can achieve our layout goals using the power of CSS-Grid instead of custom Javascript calculations. No unsupported CSS required!

Update: over at the shared-components library for Bandwidth, we’re introducing a version of this system with even more bells and whistles: there’s no need to manually specify rows, and fields support the ability to span multiple columns. Check out the pull request if you’re interested.