#btconf Düsseldorf, Germany 02 - 03 May 2022

Stephanie Eckles

Stephanie Eckles is a front-end-focused SWE at Microsoft. She's also the author of ModernCSS.dev which provides modern solutions to old CSS problems as in-depth tutorials, and is the creator of StyleStage.dev, and author of SmolCSS.dev and 11ty.Rocks. Steph has well over a decade of webdev experience that she enjoys sharing as an author, egghead instructor, and conference speaker. She's an advocate for accessibility, scalable CSS, and the Jamstack (especially Eleventy). Offline, she's mom to two girls and a cowboy corgi, and enjoys baking.

Want to watch this video on YouTube directly? This way, please.

Scaling CSS Layout Beyond Pixels

Responsive designs being created today have to serve more users on more devices and with more varied abilities and preferences than ever before. And size and spacing of elements can quite literally make or break your layout. In this new world, strict pixel values are so Web 2.0. Let’s review modern CSS techniques for building future-forward flexibility into our layouts and components.

(Addition: (link: https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/ text: read the companion article to this talk here))



[Audience applause]

Stephanie Eckles: Thank you. Thank you so much, everyone. Good morning. Thank you for showing up bright-eyed and ready to start the day. I hope you’re excited to start day two with the best programming language.

[Audience cheers]

Stephanie: Yeah. [Laughter] All right, so in my talk description, I made this somewhat bold claim. I kind of feel like I should apologize for using this kind of loaded term, Web 2.0. Right? But at the same time, it’s true that moving away from the pixel as the standard Web unit is long overdue.

My dearest colleagues, the users’ browsing environment is not predictable. Tell other developers and, for goodness sake, tell your designers. Now, today we’re going to learn how to coexist with that unpredictability.

I started dabbling in Web development approximately 11 or 12 years after this photo was taken. That’s me on my family farm in Nebraska, which is smack dab in the middle of the U.S. As you can see, I’ve been using purple as a brand color long before I knew what a brand color was.

This little cowgirl grew up to get a degree in advertising, work across in-house marketing environments, advertising agencies, and lead the development of a multi-platform design system and is now kind of doing a combination of those skills as a front-end-focused software engineer who likes to teach people CSS on the side.

What I am not is a designer, but I sure gave it a try. My Web development career started when I was giving a copy of Macromedia Flash. Now, in flash--

[Audience cheers]

Stephanie: Yes. [Laughter] In Flash, you would have a stage for your elements to live on. Those elements would be absolutely positioned until you moved them with keyframe animations. You could have interactivity with buttons, but you would program them with the proprietary language action script.

Now, what you just watched was my loving recreation of one of my original Flash splash screens ported over to modern CSS. Now, in keeping faithful to the original, I had my stage set at 800x600 pixels, but then upgraded to use other modern CSS techniques like Grid and, of course, CSS animations.

If we track the evolution of Web design on the absolute most simple timeline, back in those days of Flash, but even before those days, we had a small, finite, absolute canvas. Now, what I mean by that is we knew the end resolution that our designs were going to end up on because we simply didn’t have all the devices that we have today.

Like I mentioned, that Flash animation was 800x600 pixels. That’s the first resolution I remember. I also distinctly remember working in an ad agency and finally feeling comfortable with our analytics proving out that we could bump our designs to 1024x768. Now that might sound like a lie to some of you who are newer to the industry, but that’s what we were working with.

Now, we have this infinite, flexible, adaptive canvas, or at least that’s the potential of the Web. But we’ve kind of stopped short of realizing that full potential.

At An Event Apart in 2016, so six years ago, Jen Simmons gave a more detailed overview of the evolution of Web design. It was in her talk, “Getting Out of Our Ruts.” In this talk, she posited that we would move beyond what had become a plethora of very templated layouts given that we were having more layout methods available in CSS.

At the time of this talk, Flexbox was gaining traction and Grid was a few months away from browser support. This talk was six years after Ethan Marcotte introduced us all to the idea of responsive Web design. Jen had observed that the prevailing layout we were working out for responsive design was simply taking our sidebars and stacking them under the main content.

Now, raise your hand if you have developed this layout. [Laughter] Yes, me too - many, many times.

Two years later, in 2018, Jen presented “Everything You Know About Web Design Just Changed,” and offered this view of the progression of Web layout capabilities. Grid had reached cross-browser support, and we were finally starting to adopt Flexbox and move away from floats.

Jen dropped this new term on us, “Intrinsic Web Design,” which she proposed as a way to describe our current era of responsive Web design. As a starting point, she gave us these three principles to consider for beginning to create intrinsic Web designs.

Number one: Contracting and expanding, which is the way we consider how our designs will adapt to a change in available space.

Number two: Flexibility, which is about primarily using Grid, Flexbox, and newer functions and units as a way to enable our layout to adapt at variable rates to the change in available space.

Number three: The viewport, which is about considering all sides of the viewport and taking advantage of viewport units.

In Rachel Andrew’s talk from An Event Apart in 2019, she stated that what we see as good Web design is rooted in technical limitations of CSS2. I’m going to let that sink in for just a moment.

Now, I’m sorry to attack some of you, but we are still doing these designs that Jim Gold joked about in 2016. But, these are okay, actually. Right? We have a clear hierarchy of content. A user can come in, scan for the information they need, and move on with the task that they are trying to accomplish.

To me at least, that is a hallmark of good Web design. But we can build these better using the principles of intrinsic design, which is what we’re going to spend the rest of the time talking about.

The following techniques are guaranteed to surprise and delight. This is not a legally binding guarantee, your mileage may vary, et cetera, et cetera.

[Laughter] I’d like to recognize the techniques might also make you feel a little uncomfortable, and that’s because using adaptive layout techniques is a trust exercise between designers, devs, and (critically) the browser.

To try to help bridge this divide, I’d like to introduce the Slinky. A Slinky can help us learn to find flexibility within constraints. A Slinky has a natural state of equilibrium. It’s bound by a maximum and a minimum length, and their physical state is affected by pressure on either end.

A Slinky teaches us to feel comfortable with variability and adapt to our environment. Sometimes, a Slinky really needs to stretch, and a Slinky will adapt to a change in its orientation.

The CSS function clamp is the Slinky of Web design, and we’ve had full support for this CSS math function for about the last two years. Clamp accepts three values: a minimum, an ideal, and a maximum.

Now, the trick with clamp is that that ideal value needs to use a dynamic unit (like viewport units) in order to transition between the minimum and the maximum. You may have encountered clamp under the umbrella of fluid typography where viewport units are often used. Here’s a set of example values.

Now, based on the computed value of that four view widths, the font size will change and it will never be smaller than our minimum of one rem or larger than our maximum of three rem.

Beyond fluid typography, let’s look at a couple of other quick examples of using clamp. First up is padding. Here we’ll use a percentage because a percent used for padding is going to be based on the computed element’s width. We can also use clamp to define width.

This time, we’re using the ch unit. This is going to be approximately the size of the zero character in the inherited font size and it can be used to approximate line length. In this example, percent is going to be a percent of the available inline space.

How do we go about using clamp for intrinsic design? My proposal is that designers can provide that minimum and maximum value and devs can determine dynamic dimensions.

Now, I’d like to call out that the min and max values can definitely come from design tokens, which may be a familiar concept if you come from design systems or are using a framework that includes a sizing ramp. And I’ll continue to call out opportunities for design tokens because they can be a great method to map our constraints back to designs and wireframes.

Next up we have the min and max functions. These allow us to provide context-dependent options. They’re going to have support on par with clamp. Both min and max accept two or more values where the use of min means the browser will select the smallest computed value and, inversely, the use of max, the browser will accept the largest computed value.

Another feature of min, max, as well as clamp is that you can do additional calculations without the need for a nested calc, as seen here. For this definition, we’re asking the browser to choose the smallest value between 100 view widths minus 3rem and 80ch. This results in 100 view widths minus 3rem being selected when the viewport is smaller than that 80ch, and 80ch being selected when the viewport is larger than 80ch.

If we take that rule and add the logical property (margin inline set to auto) then we have the basis for a really modern container class. We can take our container class a step further and add in an optional custom property of container max. We still have our fallback here set to that 80ch, and now we have a modern, ultra-flexible rule. I use this all the time.

It can be a bit tricky to understand when to use min and max. Let’s provide some values for max.

In this case, we essentially have a static and dynamic value. When that 4vh (view heights) compresses down and would compute to smaller than the 2rem, the browser will select 2rem. Effectively, this means that the selected choice is actually the minimum allowed when you use a static and dynamic value. For min, if we consider 100% and 60ch, this means that 60ch is effectively the maximum allowed between these two values.

Now, if you’re wondering why we use min and max instead of their complementary dimension properties, it’s because we can use their value wherever a numeric value is accepted. For example, background size.

The way to interpret this rule is that our background image would not be able to grow larger than 600 pixels but it would be able to compress down in spaces narrower than that. Now, alternatives for this type of effect could get quite complicated.

Next up, we have fit, min, and max-content, which allow for intrinsic sizing. A little caveat on the support here. This is specifically for use (in terms of the support listed) for the width property, and you are likely to going to still want to include a prefix for Firefox - a small price to pay.

These are considered sizing keywords. Let’s take a look at their behavior when we apply them to the width property for text content.

Fit content grows just large enough to contain its contents. Min-content grows just large enough (in terms of text) to contain the longest word. You can see that it will apply soft wrapping. Max-content will continue growing as large as its contents require.

Now, in that very micro example, it’s probably hard to tell the difference between fit content and max-content. To remember the difference, fit content will grow but not overflow. You see that once it hits that boundary of that available inline space, the text content will begin to wrap. On the other hand, max-content will continue to grow unless it is constrained by an additional max-width rule.

To be honest, I haven’t found a lot of use cases for min-content and max-content - mostly due to their overflow potential. But fit content, that is a top-shelf keyword.

Here we have some block-level content. Let’s add in an alert.

This alert has width set to fit content. As you can see, it is growing up until the equivalent of its max-content property. But it happens to be shorter than the available inline space.

Now, the magic of fit content is achieving content relative width without changing the display property. This means we can save the display property for other uses. Maybe you want this to be a flex or grid item. It also continues, in this instance, to use block behavior so its margins will also continue to apply.

Next up, we have grid units and functions, which is pretty much just Slinky potential everywhere. Folks, we have had Grid support since 2017. That’s five whole years. It is time to use and learn Grid.

CSS Grid is the perfect toolset for achieving flexibility within constraints. Now, choosing which methods to show you for Grid was quite literally like choosing my favorite children, so I narrowed it down to two that I think you absolutely must know about.

This is the most magical CSS definition because it creates an intrinsically laid out and sized layout grid. With the exception of the ch unit, everything here is currently grid-specific. Repeat allows you to define a recurring pattern and sizing technique to use for your grid tracks. Auto-fit creates as many tracks that fit. Sometimes you’ll use an absolute unit with repeat like three, but this allows us to have auto-behavior of the sizing.

Those will be sized according to the next part of the definition and, in this case, we’re using the function minmax, which defines a range of values that will be used to compute the grid track size. The first value is the minimum allowed size and the second value is the largest allowed size.

One more reminder about Grid is that the fr unit is also grid-specific. It’s the fractional unit. If we were applying (as in this case) 1fr, that’s asking the browser to share the available space equitably among our elements.

Here’s the rule in action. At this larger size (in this example), three tracks have been created. But as the available inline space reduces, the track space also reduces. Once it can no longer contain multiple elements of our minimum width, they’re dropped down to be added to or to create new rows.

We can enhance this rule to reduce overflow potential by nesting this additional min function where one of the values is 100%. This means that if we get even narrower then that minimum provided value, we can still allow our elements to reduce in space.

We’ve also enabled this rule to scale by adding another optional custom property. This time grid min, which serves as a breakpoint, if you will.

Here’s a new rule where, instead of repeat, we’re using the grid-specific version of fit content. Now for Grid (and this function version), we’re able to provide a value, our own value that says this is how large we’ll allow this content to grow. And so, if we don’t have enough content to actually hit that max value, the fit content behavior still kicks in and it will be smaller than that.

Altogether, the behavior produced by this rule is that this main element is going to be beholden to minmax, shrink to its 50%, and finally, at that point, begin to compress the fit content element. The fit content element will reduce up until the value of its min-content size. That’s why it’s been important to first explore those intrinsic keywords.

Here we have improved the flexibility of this rule with another custom property, our sidebar max. What I love about this rule is that it can accommodate all kinds of content without really any changes.

In this case, I have swapped in an image. Now the important thing to remember about an image versus text content is it doesn’t have an intrinsic concept of min-content size, so it’s going to continue to shrink as long as there is available space.

Okay, I promise to talk about spacing. But so far, we’ve really just been talking about how to affect how elements take up space and how they size themselves.

Now it’s finally time to talk about spacing, spacing around and between elements, and how we can get the most out of intrinsic Web design.

This is my favorite reading experience on my mobile. How about you? We’ve got three layers of margin and padding just squeezing that content.

Now, what if we could provide the browser a better rubric of how to define and choose spacing for this context? What if we could do that without providing an overbearing amount of media queries?

Gap, padding, and margin are our primary properties responsible for spacing. Are you ready for a knowledge bomb about gap, padding, and margin?

Gap, padding, and margin have different purposes. Oh, yes.

Let’s learn to use more appropriate units and create adaptive methods for handling spacing between and around elements. Our goal is to make spacing contextual so that it flexes beyond the happy path.

First up is padding, which handles individual box spacing. For padding, we’re going to bring back our friend clamp. We’re going to use it with percent and rem. As a reminder, percent is relative to the inline size, which makes it a great, dynamic element-relative unit.

Here’s a comparison of that not-so-great mobile experience next to one that’s been upgraded to use clamp. Notice the improvement in the available line length that we have from gaining back some of that inline space.

Now here’s that same desktop version where those pixels were probably designed to work best. But I still feel we could do better. Here’s the clamp version enlarged up to that desktop size.

Now, our outer container is also using that min function, and that’s going to allow that total collapse of that extra outer space. Overall, the spacing for clamp, I think, feels more contextually appropriate.

Here is the actual full rules that are in place for clamp. We’re using the same padding rules on both the main element and our card elements. For our main, there’s that min function again. And since we’re using simply 100% for one of the values, as it compresses down to that narrow inline space, that’s how we’re able to lose those gutters.

The side-by-side of both the wide view and small view with not a media query in sight. My CSS nerd heart loves this.

Another context that is positively impacted by these spacing upgrades is the Web Content Accessibility Guidelines success criterion for reflow, which defines expectations for browsers zooming up to 400% at which point the assumed CSS pixel width is 320 pixels. Now, we don’t have a dedicated zoom media query, but any rules that affect a viewport approaching 320 pixels will affect this context.

Here’s our clamp layout stepping up to 400% zoom. You can kind of see when that mobile context comes into effect.

Here’s a direct comparison of that behavior of clamp versus pixels at 400% zoom. You can again see how our pixels are very inflexible, and we get the same result as we saw for that poor mobile reading experience. We’ll continue to measure the effect of our techniques on browser zoom, as it’s an often overlooked criteria and, as we’ve already seen, can be positively impacted by our spacing upgrades.

I recommend trying out creating some padding custom properties. Now, this is something to be considered a starting point. They can be improved upon, and you may have already noticed that our middle value seems a big magic, and it is. It’s simply a doubling up of our max value applied as a percent. Hey, there are some more design token opportunities.

Next up is margin. For our purposes, we’ll be explicitly using it for block layout spacing by which I mean vertical margin. For margin, we’ll be using the min function with viewport units and rem where viewport units will allow creating contextual spacing.

Here is our baseline rule. I’m borrowing the term “flow” from Andy Bell’s flow content rule. Block comes from our usage of margin-block-start, which is a logical property. If you’re not yet familiar with logical properties, margin-block-start is the logical companion to margin-top.

With min, we’ve provided the values for rem and 8 view heights. You can think of the 4rem as our static value and the 8 view heights as our dynamic value.

Here’s the rule in effect on a large versus a small context. Technically, the computed margin for our smaller context is only saving about 13 pixels for each application, and that might not seem too impactful. But here again, let’s look at our zoom context. We can see that the difference made by enabling that 8 view heights as an option, which is what’s kicking in here, really makes a positive difference.

Now, in this context, it’s desirable to reduce any unnecessary space. I also think that this demo kind of further shows the difference between a zoom context and a mobile context, which is a landscape orientation. Typically, if we were designing for a mobile context, we’re considering a portrait orientation.

Here is a starting point for some block flow custom properties. Once again, we’re simply doubling the static rem value as a starting point for our view height dynamic value. It tends to work out pretty well. I’ve used this technique in several projects at this point.

Now as we’ve explored, both true mobile and browser zoom up to 400% are the key context to test to help you determine the appropriate view height value. Once again, our static value is another design token opportunity.

Now, I’ve got to stay on brand, so here’s your ultra-modern block flow rule. We’re using the is selector to say that direct children of both body and a block flow class will be having a margin-block-start defaulting to our medium custom property. Now, if you’re uncomfortable with or are already feeling like that body is a little too overarching for your context, most certainly strip this rule back to just the block flow class.

Our final spacing property is gap. Now, we’re going to specifically consider gap in the context of layout component spacing. For example, that intrinsic grid that we reviewed earlier. We’ll once again be using clamp but switch it up to use vmax and rem.

As a reminder, gap is applied between elements in both row and column orientations. By the way, we have had full cross-browser gap support for Flexbox and Grid for over a year.

Here’s a baseline rule for gap. The reason we’re using vmax and not percent for this context is that if we evenly apply gap to row and column and use percent, gap is going to calculate percent in the orientation in which it’s applied. In our example right here, we would have a smaller value for our row gap and a larger value for our column gap.

On the other hand, we’re using vmax to have dynamic, contextual, and evenly applied gap spacing in all contexts. As you probably guessed, more design tokens for our min and max values.

Here are how the rules applied for a large versus small environment. I actually have the calculated values here again. Once again, not gaining too much space on the mobile necessarily, at least with these precise values. But here is our zoom context once again, which is really benefiting.

I use gap with Flexbox and Grid for more atomic components like form fields. I want to specifically call out that these custom properties are intended to be used for layout gap. Again, like our larger layout grid.

Since they are also relying on vmax, that’s not going to be quite appropriate either for a smaller atomic component. You can again see the doubling strategy in effect to determine our vmax values.

Remember this design? Using the techniques we learned, plus a tiny reset and a little bit of color, here are the foundational styles for achieving that base layout. This is 40 lines of CSS.

Now, certainly, lines of code is not a measure of quality. But I think this emphasizes the power of modern CSS. The only code not shown here is for those image placeholders and increasing the header font size. Have I mentioned just how much I love modern CSS?

Now, some of you may be wondering, “Steph, why are you so anti-media queries?” The truth is I’m just pro-selective use of media queries. Right?

We’ve just reviewed a whole lot of more appropriate techniques to create context-dependent, intrinsic sizing and spacing of elements. For these spacing context, viewport relative media queries just aren’t scalable in that context and they’re just no longer the best tool for the job.

Now even though we covered a lot, there were still some things I couldn’t fit in this talk. Notably, container queries, container units, and has. These are not dreams. These are really coming to your browsers and, when we have container queries, we’ll have a new paradigm.

We’ll have a new context where we’re no longer thinking in viewport. When we have container units as well, I’ll be switching all those places I used viewport units to container relative units to get even more contextually appropriate sized spacing.

Has, a.k.a. the parent selector, I just can’t even fathom all of the opportunities we’re going to have to even further improve these techniques.

Now, here’s the question. “Why haven’t we already embraced intrinsic Web design?”

I was thinking about my experiences on teams across all sorts of project contexts, client contexts, team skillsets, and designer-developer dynamics. I think that some of the reasons why we haven’t collectively adopted the principles of intrinsic Web design are that it’s hard to relinquish control both from designer to developers and developers to the browser.

We also can feel trapped by processes. Whether that’s the tech stack you have, stakeholder requests, or simply the fact that you have to get done a particular work item in front of you and you don’t have time to experiment. At the end of the day, we may not feel empowered to create a request change.

Spicy take incoming...

The necessary rigidity of design systems and frameworks is at odds with intrinsic Web design. Spoiler alert... I don’t have a magic way past this other than to ask each of you to make adjustments where you can.

Teach others, educate others about the possibility, and carry the torch, if you will. Write articles. Do talks. Give demos. Maybe take a Slinky to your next meeting with designers and spread the good word.

Here are kind of the highlights of what we talked about today. Again, I’d just ask you to take time to be a little more thoughtful. Experiment and push beyond your current comfort zone. Truly, experimentation is the best way to understand these properties.

Get into your browser tools. I make heavy use of the computed tab to really understand what’s happening, why certain values are being selected, using a bit of JavaScript to demo out (like we saw with the font size). Whatever tools you need to understand it yourself and also, like I said, help share it and the possibilities to others.

Now, if you want to dig in a little more on these techniques, during the break I’m going to publish a companion article that will have some demos and further resources on this topic. I’d also love to see you at my workshop this July.

Before I close, I want to acknowledge that identifying the need to respect the Web as a flexible medium is certainly not a new idea. In the year 2000, John Allsopp wrote “A Dao o Web Design,” which was also cited by Ethan Marcotte when he introduced us to responsive design. It concluded with this passage.

“The Web’s greatest strength, I believe, is often seen as a limitation, a defect. It is the nature of the Web to be flexible and it should be our role as designers and developers to embrace this flexibility, and produce pages which, by being flexible, are accessible for all.”

Thank you.

[Audience applauds and cheers]