Responsive layouts for dynamic content: How to design and implement resilient CSS layouts for dynamic content in web applications

Layout is all about constraints — content flowing into an available space based on constraints. There are two easy steps to designing and implementing resilient web layouts that hold up to the real world, and both come down to identifying and expressing constraints.

1. Actively design constraints

Don’t design with static placeholder content. As a designer, it’s your responsibility to demonstrate the constraints that a layout should adapt to. If you show placeholder content that’s the same width for every title, and the same number of lines for every content block, that doesn’t help a front-end developer translate your intent into code.

Ask yourself: What constrains this content? What happens when it’s long, or when it’s short? Web content isn’t static — ragged, uneven content is the reality of working with a dynamic environment like the web.

2. Implement constraints with respect for content

Some layout constructs are based on grids, and there are fixed points at which the layout should change from beneath the content. This is the framework many developers think with when working with layout. However, we can’t rely on media queries and fixed breakpoints to make a layout responsive and resilient. Let’s define those terms for this purpose of this article:

  • Responsive: Capable of being displayed within arbitrary viewport sizes.
  • Resilient: Capable of holding arbitrary dynamic content without breaking down and showing cracks in the layout system.

When we look at these definitions, it’s pretty clear that drawing fixed lines at which the layout changes won’t address resilience to arbitrary content. To handle that, the constraints of a layout often need to be based on how much content is present — and how much can fit within a row.

The fundamental limitation of CSS layout is it doesn’t react to the size of content within a container. And the size of content is almost always dynamic. Whether dealing with dynamic content populated by a CMS, by user input in an interactive web app, or when dealing with localization of text to another language, content can change size. That means layouts can’t respond to the size of the content unless we carefully express constraints around the content such that the content is pushed and wraps into the appropriate place when it doesn’t fit where it would within a larger container.

Media queries allow us to change a layout based on the size of the viewport. In the near future, we may also be able to use them with respect to parent containers. But that doesn’t help us with content. Here’s the beautiful thing: If we manage to build content-first layouts, container agnosticism comes naturally. It’s like getting container queries for free.

It turns out negative margins on a parent, and equal but opposite positive margins on the children within are the answer. In this example, we have a row of items with the following styles:

.row {
  // The container uses `display: flex;` to prefer laying out children within a row: 
  display: flex;
  // The container allows its children to wrap: 
  flex-wrap: wrap;
  // The container has negative margins to the top and left: 
  margin: -2rem 0 0 -2rem;
}
 
.row--split {
  // A row variant that splits its children to opposite sides of the container: 
  justify-content: space-between;
}
 
.row > * {
  // Each child has positive margins to the top and left reversing the container's: 
  margin: 2rem 0 0 2rem;
}
<div class="row row--split">
  <div>First item</div>
  <div>
    <div class="row">
      <div>Second item</div>
      <div>Third item</div>
    </div>
  </div>
</div>

If there’s enough space for everything, we get the following:

Note that the container will visually fill whatever its container is — those negative margins and positive margins result in aligning the inner items to the edges of whatever contains the container. Our first item is split to the left, and our second item to the right. The second item is a row of its own, and contains two items.

If the content width exceeds the available width, the content will wrap as necessary:

The margin between the items on the left and the items on the right means the text within each set of items will never collide — they wrap as soon there’s not enough space for the content and the margin.

When I build layouts like this, I find they elegantly express the flow of content, handle dynamic content resiliently, and respond cleanly within different viewports and container sizes.

Further reading

  • Breakpoint-based layout patterns: grid and tiles
  • Content-based layout pattern: row