Contextual Callouts with CSS Grid

At long last, contextual callouts are possible with CSS grid. Contextual callouts are small paragraphs that sit beside primary text and offer secondary information to a reader. They have long been a feature of books, magazines and other printed materials. I particularly enjoy coming across these small asides when reading, as they add texture and interest to the content.

I have been searching for a while now for a way to bring this to the web with pure CSS. The solutions in the past have typically been fairly messy, requiring annoying floating and clearing, or manual absolute positioning. That is changing now, thanks to CSS grid.

I haven’t found a complete solution to these callouts, but the example here will hopefully work in several situations.

The Grid Markup

Say I’m building a blog post template. I’ll need a header to contain the title and date, and a section for share links. Then I’ll need the primary blog content, consisting of headings, paragraphs, images, and, yes, callouts. First let’s write some semantic markup:

<article class="blog-post">
  <header class="blog-post-header">
    <h1 class="blog-post-title">...</h1>
    <time datetime="..." class="blog-post-date">...</time>
  </header>
  <aside class="blog-post-share">...</aside>
  <p>...</p>
  <h2>...</h2>
  <p>...</p>
  <aside>
    <p>...</p>
  </aside>
  <p>...</p>
  <p>...</p>
  <aside>
    <p>...</p>
  </aside>
  <figure>
    <img src="..." alt="...">
    <figcaption>...</figcaption>
  </figure>
</article>

The article element contains the header, share links aside, and all post content as direct children. This will be important as we apply CSS grid styles to the post. Callouts are written as aside elements, perfect for content that is connected tangentially to the rest of the document, and appear in the document directly after the paragraph they are connected to.

Fallback Styling (if no CSS Grid support)

First, we’ll apply some basic styles for browsers that don’t yet support CSS grid. We’ll add some margin, padding and set a max-width of 70 characters using the ch unit:

.blog-post {
  max-width: 70ch;
  margin: 3rem auto;
  padding: 1.5rem;
}

The Fun Part: Using CSS Grid!

Now, progressively enhance for browsers that do support CSS grid using an @supports query:

@supports(display: grid) {
  .blog-post {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    grid-column-gap: 2rem;
  }
}

Here I’m setting setting up the .blog-post article as a 12-column grid, each with a width of 1 fraction unit and a gap between each column of 2rem. Now it’s time to start placing content on the grid:

.blog-post-header {
  grid-column-start: 2;
  grid-column-end: span 3;
}

.blog-post p,
.blog-post h2 {
  grid-column-start: 5;
  grid-column-end: span 6;
}

.blog-post-share {
  grid-column-start: span 1;
  grid-column-end: -2;
}

Now, the post header, post paragraphs and headings, and share links all sit next to each other in a row. Pretty cool. The callouts are up next:

.blog-post aside {
  grid-column-start: 3;
  grid-column-end: 5;
}

The asides are now pulled to the left of the paragraph immediately preceding them, looking exactly like callouts. If you wanted more control over the positioning of these callouts (say you wanted one at the bottom of the row, rather than at the top), you could apply some targeted styling with the align-self property, but for this exercise, I’m assuming a more generic approach for content generated from a CMS.

And that’s pretty much all it takes to setup contextual callouts with CSS grid. No sweat.

Bonus: Full-Width Figures

Here’s my styling for figures within a post, causing them to stretch the full width of the grid container. The rest of the content is still fit to a narrower width.

.blog-post figure {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-column-gap: 2rem;
  grid-column-start: 1;
  grid-column-end: -1;
}

.blog-post figure img {
  grid-column-start: 1;
  grid-column-end: -1;
}

.blog-post figure figcaption {
  grid-column-start: 5;
  grid-column-end: span 6;
}

I set the figure up to span across all 12 columns of its parent grid. I set up a nested grid within the figure with the same number of columns and spacing as the parent. This allows me to have the img element to span the full width, while the figcaption is aligned with the rest of the primary text in the blog post.

Caveats

There is one primary caveat to the approach outlined here. Because row height is determined by the content within the row, it is possible that a caption that is longer than the paragraph to the right of it could elongate that row and create some odd blank space to the right.

Essentially, it is required that all callouts be of equal length or shorter than their preceding paragraphs.

Conclusion

That wraps up this one. Check out the live experiment here. I’d love to see what other people do with callouts!