clamp – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Fri, 07 Oct 2022 13:19:24 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 clamp – CSS-Tricks https://css-tricks.com 32 32 45537868 Adding Fluid Typography Support to WordPress Block Themes https://css-tricks.com/fluid-typography-wordpress-block-themes/ https://css-tricks.com/fluid-typography-wordpress-block-themes/#respond Fri, 07 Oct 2022 13:19:23 +0000 https://css-tricks.com/?p=373905 Fluid typography is a fancy way of “describing font properties, such as size or line height, that scale fluidly according to the size of the viewport”. It’s also known by other names, like responsive typography, flexible type, fluid type, …


Adding Fluid Typography Support to WordPress Block Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Fluid typography is a fancy way of “describing font properties, such as size or line height, that scale fluidly according to the size of the viewport”. It’s also known by other names, like responsive typography, flexible type, fluid type, viewport sized typography, fluid typography, and even responsive display text.

Here is an example of fluid typography that you can play live (courtesy of MDN documentation). CSS-Tricks has covered fluid typography extensively as well. But the point here is not to introduce you to fluid typography, but how to use it. More specifically, I want to show you how to implement fluid typography in WordPress 6.1 which recently introduced a fluid type feature directly in the WordPress Block Editor.

Open up your style.css file, slap in a style rule with fancy clamp()-ing on the font-size property, and good to go, right? Sure, that’ll give you fluid text, but to get Block Editor controls that make it possible to apply fluid type anywhere on your WordPress site? That requires a different approach in these block-ified days.

Fluid typography support in Gutenberg

Some WordPress theme developers have been using the clamp() function to define a fluid font-size, in their WordPress themes, even in newer “block” themes such as Twenty Twenty-Two, Twenty Twenty-Three, and others.

But the Gutenberg plugin — the one that contains experimental development for WordPress Block and Site Editor features — introduced support for fluid typography starting in version 13.8. That opened the door for implementing at a theme level so that fluid type can be applied to specific elements and blocks directly in the Block Editor. CSS-Tricks was even given a shout-out in the Pull Request that merged the feature.

That work became part of WordPress Core in WordPress 6.1. Rich Tabor, one of the earlier advocates of fluid typography in the Block Editor says:

[Fluid typography] is also a part of making WordPress more powerful, while not more complicated (which we all know is quite the challenge). […] Fluid typography just works. Actually, I think it works great.

This Make WordPress post highlights the approach taken to support the feature at the block level so that a fluid font size is applied to blocks dynamically by default. There are some benefits to this, of course:

  • It provides a way for theme authors to activate fluid typography without worrying about implementing it in code.
  • It applies fluid typography to specific typographical entities, such as elements or blocks in a maintainable and reusable way.
  • It allows flexibility in terms of font size units (e.g. px, rem, em, and %).

Now that this new feature is available in the WordPress Block Editor by default, theme authors can apply uniform fluid typography without writing additional code.

Blocks that support typography and spacing settings

Gutenberg 14.1 released on September 16, 2022, and introduced typographic settings on a bunch of blocks. That means the text settings for those blocks were set in CSS before and had to be changed in CSS as well. But those blocks now provide font and spacing controls in the Block Editor interface.

Illustrated list of WordPress blocks that received font and spacing controls in the Gutenberg plugin. There are 31 total blocks.

That work is currently slated to be added to WordPress 6.1, as detailed in this Make WordPress blog post. And with it is an expanded number of blocks that with typography support.

Illustrated list of 60 WordPress blocks gaining typography and font size support in WordPress 6.1.
WordPress blocks that will support typography settings in the upcoming WordPress 6.1 release.

Declaring fluid type in a WordPress block theme

So, how do we put this new fluid typography to use in WordPress? The answer is in theme.json, a new-ish file specific to block themes that contains a bunch of theme configurations in key:value pairs.

Let’s look at a rule for a large font in theme.json where contentSize: 768px and we’re working with a widesize: 1600px layout. This is how we can specify a CSS font-size using the clamp() function:

"settings": {
  "appearanceTools": true,
  "layout": {
    "contentSize": "768px",
    "wideSize": "1600px"
  },
  "typography": {
    "fontSizes": [ 
      {
        "name": "Large",
        "size": "clamp(2.25rem, 6vw, 3rem)",
        "slug": "large"
      }
    ]
  }
}

As of WordPress 6.1, only rem, em and px units are supported.

That’s great and works, but with the new fluid type feature we would actually use a different approach. First, we opt into fluid typography on settings.typography, which has a new fluid property:

"settings": {
  "typography": {
    "fluid": true
  }
}

Then we specify our settings.fontSizes like before, but with a new fluidSize property where we can set the min and max size values for our fluid type range.

"settings": {
  "appearanceTools": true,
  "layout": {
    "contentSize": "768px",
    "wideSize": "1600px"
  },
  "typography": {
    "fontSizes": [ 
      {
        "size": "2.25rem",
        "fluidSize": {
          "min": "2.25rem",
          "max": "3rem"
        },
        "slug": "large",
        "name": "Large"
      }
    ]
  }
}

That’s really it. We just added fluid type to a font size called “Large” with a range that starts at 2.25rem and scales up to 3rem. Now, we can apply the “Large” font to any block with font settings.

How does this works under the hood? Rich Tabor offers a nice explanation, as does this Pull Request in GitHub. In short, WordPress converts the theme.json properties into the following CSS rule:

.has-large-font-size {
  font-size: clamp(36px, calc(2.25rem + ((1vw - 7.68px) * 1.4423)), 48px);
}

…which is applied to the element, say a Paragraph Block:

<p class="has-large-font-size">...</p>

Initially, I found it hard to understand and wrap around in my mind the concept of the CSS clamp() function without also learning about the min(), max(), and calc() functions. This calculator tool helped me quite a bit, especially for determining which values to use in my own theme projects.

For demonstration purposes, let’s use the calculator to define our font-size range so that the size is 36px at a 768px viewport width and 48px at a 1600px viewport width.

Entering values into the online calculator for fluid typography.

The calculator automatically generates the following CSS:

/* 36px @ 768px increasing to 48px @ 1600px */
font-size: clamp(36px, calc(2.25rem + ((1vw - 7.68px) * 1.4423)), 48px);

The calculator provide options to select input units as px, rem, and em. If we select rem unit, the calculator generates the following clamp() value:

/* 2.25rem @ 48rem increasing to 3rem @ 100rem */
font-size: clamp(2.25rem, calc(2.25rem + ((1vw - 0.48rem) * 1.4423)), 3rem);

So, those minimum and maximum values correspond to the the fluidSize.min and fluidSize.max values in theme.json. The min value is applied at viewports that are 768px wide and below. Then the font-size scales up as the viewport width grows. If the viewport is wider than 1600px, the max is applied and the font-size is capped there.

Examples

There are detailed testing instructions in the merged Pull Request that introduced the feature. There are even more testing instructions from Justin Tadlock’s pre-prelease post on Make WordPress.

Example 1: Setting a new fluid font setting

Let’s start with Justin’s set of instructions. I used in a modified version of the default Twenty Twenty-Three theme that is currently under development.

First, let’s make sure we’re running the Gutenberg plugin (13.8 and up) or WordPress 6.1, then opt into fluid type on the settings.typography.fluid property in the theme.json file:

{
  "version": 2,
  "settings": {
    "appearanceTools": true,
    "layout": {
      "contentSize": "768px",
      "wideSize": "1600px"
    },
    "typography": {
      "fluid": true
    }
  }
}

Now, let’s drop the settings.typography.fontSizes examples in there:

{
  "version": 2,
  "settings": {
    "appearanceTools": true,
    "layout": {
      "contentSize": "768px",
      "wideSize": "1600px"
    },
    "typography": {
      "fluid": true
      "fontSizes": [
        {
          "name": "Normal",
          "size": "1.125rem",
          "fluid": {
            "min": "1rem",
            "max": "1.5rem"
          },
          "slug": "normal"
        }
      ]
    }
  }
}

If everything is working correctly, we can now head into the WordPress Block Editor and apply the “Normal” font setting to our block:

The WordPress Block Editor interface showing a paragraph block and the fluid typography settings for it.

Nice! And if we save and inspect that element on the front end, this is the markup:

Inspecting the WordPress Paragraph block in DevTools.

Very good. Now let’s make sure the CSS is actually there:

DevTools showing the font-size custom property for the WordPress Paragraph block's fluid typography.

Good, good. Let’s expose that CSS custom property to see if it’s really clampin’ things:

Revealing the custom property value in DevTools, showing a CSS clamp function.

Looks like everything is working just as we want it! Let’s look at another example…

Example 2: Excluding a font setting from fluid type

This time, let’s follow the instructions from the merged Pull Request with a nod to this example by Carolina Nymark that shows how we can disable fluid type on a specific font setting.

I used the empty theme as advised in the instructions and opened up the theme.json file for testing. First, we opt into fluid type exactly as we did before:

{
  "version": 2,
  "settings": {
    "appearanceTools": true,
    "layout": {
      "contentSize": "768px",
      "wideSize": "1000px"
    },
    "typography": {
      "fluid": true
    }
  }
}

This time, we’re working with smaller wideSize value of 1000px instead of 1600px. This should allow us to see fluid type working in an exact range.

OK, on to defining some custom font sizes under settings.typography.fontSizes:

{
  "version": 2,
  "settings": {
    "typography": {
      "fluid": true,
      "fontSizes": [
        {
          "size": ".875rem",
          "fluid": {
            "min": "0.875rem",
            "max": "1rem"
        },
          "slug": "small",
          "name": "Small"
        },
        {
          "size": "1rem",
          "fluid": {
            "min": "1rem",
            "max": "1.5rem"
          },
          "slug": "normal",
          "name": "Normal"
        },
        {
          "size": "1.5rem",
          "fluid": {
            "min": "1.5rem",
            "max": "2rem"
          },
          "slug": "large",
          "name": "Large"
        },
        {
          "size": "2.25rem",
          "fluid": false,
          "slug": "x-large",
          "name": "Extra large"
        }
      ]
    }
  }
}

Notice that we’ve applied the fluid type feature only on the “Normal”, “Medium”, and “Large” font settings. “Extra Large” is the odd one out where the fluid object is set to false.

the WordPress Block Editor interface with four Paragraph blocks, each at a different font size setting.

What WordPress does from here — via the Gutenberg style engine — is apply the properties we set into CSS clamp() functions for each font size setting that has opted into fluid type and a single size value for the Extra Large setting:

--wp--preset--font-size--small: clamp(0.875rem, 0.875rem + ((1vw - 0.48rem) * 0.24), 1rem);
--wp--preset--font-size--medium: clamp(1rem, 1rem + ((1vw - 0.48rem) * 0.962), 1.5rem);
--wp--preset--font-size--large: clamp(1.5rem, 1.5rem + ((1vw - 0.48rem) * 0.962), 2rem);
--wp--preset--font-size--x-large: 2.25rem;

Let’s check the markup on the front end:

Inspecting the WordPress Paragraph blocks in DevTools.

Good start! Let’s confirm that the .has-x-large-font-size class is excluded from fluid type:

Showing the font-size custom property for the Extra Large font setting in DevTools.

If we expose the --wp--preset--font-size--x-large variable, we’ll see it’s set to 2.25rem.

Revealing the Extra Large font size custom property value, showing 2.25rem.

That’s exactly what we want!

Block themes that support fluid typography

Many WordPress themes already make use of the clamp() function for fluid type in both block and classic themes. A good example of fluid typography use is the recently released Twenty Twenty-Three default theme.

I’ve reviewed all the block themes from WordPress Block Theme directory, examining theme.json file of each theme and to see just how many block themes currently support fluid typography — not the new feature since it’s still in the Gutenberg plugin as of this writing — using the CSS clamp() function. Of the 146 themes I reviewed, the majority of them used a clamp() function to define spacing. A little more than half of them used clamp() to define font sizes. The Alara theme is the only one to use clamp() for defining the layout container sizes.

Understandably, only a few recently released themes contain the new fluid typography feature. But here are the ones I found that define it in theme.json:

And if you read my previous post here on CSS-Tricks, the TT2 Gopher Blocks theme I used for the demo has also been updated to support the fluid typography feature.

Selected reactions to the WordPress fluid typography features

Having fluid typography in WordPress at the settings level is super exciting! I thought I’d share some thoughts from folks in the WordPress developer community who have commented on it.

Matias Ventura, the lead architect of the Gutenberg project:

Rich Tabor:

As one of the bigger efforts towards making publishing beautifully rich pages in WordPress, fluid typography is a pretty big experience win for both the folks building with WordPress — and those consuming the content.

Automattic developer Ramon Dodd commented in the Pull Request:

Contrast that idea with font sizes that respond to specific viewport sizes, such as those defined by media queries, but do nothing in between those sizes. theme.json already allows authors to insert their own fluid font size values. This won’t change, but this PR offers it to folks who don’t want to worry about the implementation details.

Nick Croft, author of GenesisWP:

Brian Garner, designer and principal developer advocate at WPEngine:

A few developers think some features should be an opt-in. Jason Crist of Automattic says:

I love the power of fluid typography, however I also don’t believe that it should just be enabled by default. It’s usage (and the details of it) are important design decisions that should be made carefully.

You can also find a bunch more comments in the official testing instructions for the feature.

Wrapping up

The fluid typography feature in WordPress is still in active development at the time of this writing. So, right now, theme authors should proceed to use it, but with caution and expect some possible changes before it is officially released. Justin cautions theme authors using this feature and suggests to keep eye on the following two GitHub issues:

There is also still lots of ongoing work on typography and other design-related WordPress tools. If you’re interested, watch this typography tracking GitHub ticket and design tools related GitHub issues.

Resources

I used the following articles when researching fluid type and how WordPress is implementing it as a feature.

Tutorials and opinions

CSS-Tricks


Adding Fluid Typography Support to WordPress Block Themes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/fluid-typography-wordpress-block-themes/feed/ 0 373905
Responsive Layouts, Fewer Media Queries https://css-tricks.com/responsive-layouts-fewer-media-queries/ https://css-tricks.com/responsive-layouts-fewer-media-queries/#comments Mon, 22 Nov 2021 15:29:03 +0000 https://css-tricks.com/?p=357080 We cannot talk about web development without talking about Responsive Design. It’s just a given these days and has been for many years. Media queries are a part of Responsive Design and they aren’t going anywhere. Since the introduction of …


Responsive Layouts, Fewer Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
We cannot talk about web development without talking about Responsive Design. It’s just a given these days and has been for many years. Media queries are a part of Responsive Design and they aren’t going anywhere. Since the introduction of media queries (literally decades ago), CSS has evolved to the points that there are a lot of tricks that can help us drastically reduce the usage of media queries we use. In some cases, I will show you how to replace multiple media queries with only one CSS declaration. These approaches can result in less code, be easier to maintain, and be more tied to the content at hand.

Let’s first take a look at some widely used methods to build responsive layouts without media queries. No surprises here — these methods are related to flexbox and grid.

Using flex and flex-wrap

Live Demo

In the above demo, flex: 400px sets a base width for each element in the grid that is equal to 400px. Each element wraps to a new line if there isn’t enough room on the currently line to hold it. Meanwhile, the elements on each line grow/stretch to fill any remaining space in the container that’s leftover if the line cannot fit another 400px element, and they shrink back down as far as 400px if another 400px element can indeed squeeze in there.

Let’s also remember that flex: 400px is a shorthand equivalent to flex: 1 1 400px (flex-grow: 1, flex-shrink: 1, flex-basis: 400px).

What we have so far:

  • ✔️ Only two lines of code
  • ❌ Consistent element widths in the footer
  • ❌ Control the number of items per row
  • ❌ Control when the items wrap

Using auto-fit and minmax

Live Demo

Similar to the previous method, we are setting a base width—thanks to repeat(auto-fit, minmax(400px, 1fr))—and our items wrap if there’s enough space for them. This time, though we’re reaching for CSS Grid. That means the elements on each line also grow to fill any remaining space, but unlike the flexbox configuration, the last row maintains the same width as the rest of the elements.

So, we improved one of requirements and solved another, but also introduced a new issue since our items cannot shrink below 400px which may lead to some overflow.

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ❌ Control the number of items per row
  • ❌ Items grow, but do not shrink
  • ❌ Control when the items wrap

Both of the techniques we just looked at are good, but we also now see they come with a few drawbacks. But we can overcome those with some CSS trickery.

Control the number of items per row

Let’s take our first example and change flex: 400px to flex: max(400px, 100%/3 - 20px).

Resize the screen and notice that each row never has more than three items, even on a super wide screen. We have limited each line to a maximum of three elements, meaning each line only contains between one and three items at any given time.

Let’s dissect the code:

  • When the screen width increases, the width of our container also increases, meaning that 100%/3 gets bigger than 400px at some point.
  • Since we are using the max() function as the width and are dividing 100% by 3 in it, the largest any single element can be is just one-third of the overall container width. So, we get a maximum of three elements per row.
  • When the screen width is small, 400px takes the lead and we get our initial behavior.

You might also be asking: What the heck is that 20px value in the formula?

It’s twice the grid template’s gap value, which is 10px times two. When we have three items on a row, there are two gaps between elements (one on each on the left and right sides of the middle element), so for N items we should use max(400px, 100%/N - (N - 1) * gap). Yes, we need to account for the gap when defining the width, but don’t worry, we can still optimize the formula to remove it!

We can use max(400px, 100%/(N + 1) + 0.1%). The logic is: we tell the browser that each item has a width equal to 100%/(N + 1) so N + 1 items per row, but we add a tiny percentage ( 0.1%)—thus one of the items wraps and we end with only N items per row. Clever, right? No more worrying about the gap!

Now we can control the maximum number of items per row which give us a partial control over the number of items per row.

The same can also be applied to the CSS Grid method as well:

Note that here I have introduced custom properties to control the different values.

We’re getting closer!

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ⚠️ Partial control of the number of items per row
  • ❌ Items grow, but do not shrink
  • ❌ Control when the items wrap

Items grow, but do not shrink

We noted earlier that using the grid method could lead to overflow if the base width is bigger than the container width. To overcome this we change:

max(400px, 100%/(N + 1) + 0.1%)

…to:

clamp(100%/(N + 1) + 0.1%, 400px, 100%)

Breaking this down:

  • When the screen width is big, 400px is clamped to 100%/(N + 1) + 0.1%, maintaining our control of the maximum number of items per row.
  • When the screen width is small, 400px is clamped to 100% so our items never exceed the container width.

We’re getting even closer!

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ⚠️ Partial control of the number of items per row
  • ✔️ Items grow and shrink
  • ❌ Control when the items wrap

Control when the items wrap

So far, we’ve had no control over when elements wrap from one line to another. We don’t really know when it happens because it depends a number of things, like the base width, the gap, the container width, etc. To control this, we are going to change our last clamp() formula, from this:

clamp(100%/(N + 1) + 0.1%, 400px, 100%)

…to:

clamp(100%/(N + 1) + 0.1%, (400px - 100vw)*1000, 100%)

I can hear you screaming about that crazy-looking math, but bear with me. It’s easier than you might think. Here’s what’s happening:

  • When the screen width (100vw) is greater than 400px, (400px - 100vw) results in a negative value, and it gets clamped down to 100%/(N + 1) + 0.1%, which is a positive value. This gives us N items per row.
  • When the screen width (100vw) is less than 400px, (400px - 100vw) is a positive value and multiplied by a big value that’s clamped to the 100%. This results in one full-width element per row.
Live Demo

Hey, we made our first media query without a real media query! We are updating the number of items per row from N to 1 thanks to our clamp() formula. It should be noted that 400px behave as a breakpoint in this case.

What about: from N items per row to M items per row?

We can totally do that by updating our container’s clamped width:

clamp(100%/(N + 1) + 0.1%, (400px - 100vw)*1000, 100%/(M + 1) + 0.1%)

I think you probably get the trick by now. When the screen width is bigger than 400px we fall into the first rule (N items per row). When the screen width is smaller than 400px, we fall into the second rule (M items per row).

Live Demo

There we go! We can now control the number of items per row and when that number should change—using only CSS custom properties and one CSS declaration.

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ✔️ Full control of the number of items per row
  • ✔️ Items grow and shrink
  • ✔️ Control when the items wrap

More examples!

Controlling the number of items between two values is good, but doing it for multiple values is even better! Let’s try going from N items per row to M items per row, down to one item pre row.

Our formula becomes:

clamp(clamp(100%/(N + 1) + 0.1%, (W1 - 100vw)*1000,100%/(M + 1) + 0.1%), (W2 - 100vw)*1000, 100%)

A clamp() within a clamp()? Yes, it starts to get a big lengthy and confusing but still easy to understand. Notice the W1 and W2 variables in there. Since we are changing the number of items per rows between three values, we need two “breakpoints” (from N to M, and from M to 1).

Here’s what’s happening:

  • When the screen width is smaller than W2, we clamp to 100%, or one item per row.
  • When the screen width is larger than W2, we clamp to the first clamp().
  • In that first clamp, when the screen width is smaller than W1, we clamp to 100%/(M + 1) + 0.1%), or M items per row.
  • In that first clamp, when the screen width is bigger than W1, we clamp to 100%/(N + 1) + 0.1%), or N items per row.

We made two media queries using only one CSS declaration! Not only this, but we can adjust that declaration thanks to the CSS custom properties, which means we can have different breakpoints and a different number of columns for different containers.

How many media queries do we have in the above example? Too many to count but we will not stop there. We can have even more by nesting another clamp() to get from N columns to M columns to P columns to one column. (😱)

clamp(
  clamp(
    clamp(100%/(var(--n) + 1) + 0.1%, (var(--w1) - 100vw)*1000,
          100%/(var(--m) + 1) + 0.1%),(var(--w2) - 100vw)*1000,
          100%/(var(--p) + 1) + 0.1%),(var(--w3) - 100vw)*1000,
          100%), 1fr))
from N columns to M columns to P columns to 1 column

As I mentioned at the very beginning of this article, we have a responsive layout without any single media queries while using just one CSS declaration—sure, it’s a lengthy declaration, but still counts as one.

A small summary of what we have:

  • ✔️ Only one line of code
  • ✔️ Consistent element widths in the footer
  • ✔️ Full control of the number of items per row
  • ✔️ Items grow and shrink
  • ✔️ Control when the items wrap
  • ✔️ Easy to update using CSS custom properties

Let’s simulate container queries

Everyone is excited about container queries! What makes them neat is they consider the width of the element instead of the viewport/screen. The idea is that an element can adapt based on the width of its parent container for more fine-grain control over how elements respond to different contexts.

Container queries aren’t officially supported anywhere at the time of this writing, but we can certainly mimic them with our strategy. If we change 100vw with 100% throughout the code, things are based on the .container element’s width instead of the viewport width. As simple as that!

Resize the below containers and see the magic in play:

The number of columns change based on the container width which means we are simulating container queries! We’re basically doing that just by changing viewport units for a relative percentage value.

More tricks!

Now that we can control the number of columns, let’s explore more tricks that allow us to create conditional CSS based on either the screen size (or the element size).

Conditional background color

A while ago someone on StackOverflow asked if it is possible to change the color of an element based on its width or height. Many said that it’s impossible or that it would require a media query.

But I have found a trick to do it without a media query:

div {
  background:
   linear-gradient(green 0 0) 0 / max(0px,100px - 100%) 1px,
   red;
}
  • We have a linear gradient layer with a width equal to max(0px,100px - 100%) and a height equal to 1px. The height doesn’t really matter since the gradient repeats by default. Plus, it’s a one color gradient, so any height will do the job.
  • 100% refers to the element’s width. If 100% computes to a value bigger than 100px, the max() gives us 0px, which means that the gradient does not show, but the comma-separated red background does.
  • If 100% computes to a value smaller than 100px, the gradient does show and we get a green background instead.

In other words, we made a condition based on the width of the element compared to 100px!

This demo supports Chrome, Edge, and Firefox at the time of writing.

The same logic can be based on an element’s height instead by rearranging where that 1px value goes: 1px max(0px,100px - 100%). We can also consider the screen dimension by using vh or vw instead of %. We can even have more than two colors by adding more gradient layers.

div {
  background:
   linear-gradient(purple 0 0) 0 /max(0px,100px - 100%) 1px,
   linear-gradient(blue   0 0) 0 /max(0px,300px - 100%) 1px,
   linear-gradient(green  0 0) 0 /max(0px,500px - 100%) 1px,
   red;
}

Toggling an element’s visibility

To show/hide an element based on the screen size, we generally reach for a media query and plop a classic display: none in there. Here is another idea that simulates the same behavior, only without a media query:

div {
  max-width: clamp(0px, (100vw - 500px) * 1000, 100%);
  max-height: clamp(0px, (100vw - 500px) * 1000, 1000px);
  overflow: hidden;
}

Based on the screen width (100vw), we either get clamped to a 0px value for the max-height and max-width (meaning the element is hidden) or get clamped to 100% (meaning the element is visible and never greater than full width). We’re avoiding using a percentage for the max-height since it fails. That’s why we’re using a big pixel value (1000px).

Notice how the green elements disappear on small screens:

It should be noted that this method is not equivalent to the toggle of the display value. It’s more of a trick to give the element 0×0 dimensions, making it invisible. It may not be suitable for all cases, so use it with caution! It’s more a trick to be used with decorative elements where we won’t have accessibility issues. Chris wrote about how to hide content responsibly.

It’s important to note that I am using 0px and not 0 inside clamp() and max(). The latter makes invalidates property. I won’t dig into this but I have answered a Stack Overflow question related to this quirk if you want more detail.

Changing the position of an element

The following trick is useful when we deal with a fixed or absolutely positioned element. The difference here is that we need to update the position based on the screen width. Like the previous trick, we still rely on clamp() and a formula that looks like this: clamp(X1,(100vw - W)*1000, X2).

Basically, we are going to switch between the X1 and X2 values based on the difference, 100vw - W, where W is the width that simulates our breakpoint.

Let’s take an example. We want a div placed on the left edge (top: 50%; left:0) when the screen size is smaller than 400px, and place it somewhere else (say top: 10%; left: 40%) otherwise.

div {
  --c:(100vw - 400px); /* we define our condition */
  top: clamp(10%, var(--c) * -1000, 50%);
  left: clamp(0px, var(--c) * 1000, 40%);
}
Live Demo

First, I have defined the condition with a CSS custom property to avoid the repetition. Note that I also used it with the background color switching trick we saw earlier—we can either use (100vw - 400px) or (400px - 100vw), but pay attention to the calculation later as both don’t have the same sign.

Then, within each clamp(), we always start with the smallest value for each property. Don’t incorrectly assume that we need to put the small screen value first!

Finally, we define the sign for each condition. I picked (100vw - 400px), which means that this value will be negative when the screen width is smaller than 400px, and positive when the screen width is bigger than 400px. If I need the smallest value of clamp() to be considered below 400px then I do nothing to the sign of the condition (I keep it positive) but if I need the smallest value to be considered above 400px I need to invert the sign of the condition. That’s why you see (100vw - 400px)*-1000 with the top property.

OK, I get it. This isn’t the more straightforward concept, so let’s do the opposite reasoning and trace our steps to get a better idea of what we’re doing.

For top, we have clamp(10%,(100vw - 400px)*-1000,50%) so…

  • if the screen width (100vw) is smaller than 400px, then the difference (100vw - 400px) is a negative value. We multiply it with another big negative value (-1000 in this case) to get a big positive value that gets clamped to 50%: That means we’re left with top: 50% when the screen size is smaller than 400px.
  • if the screen width (100vw) is bigger than 400px, we end with: top: 10% instead.

The same logic applies to what we’re declaring on the left property. The only difference is that we multiply with 1000 instead of -1000 .

Here’s a secret: You don’t really need all that math. You can experiment until you get it perfect values, but for the sake of the article, I need to explain things in a way that leads to consistent behavior.

It should be noted that a trick like this works with any property that accepts length values (padding, margin, border-width, translate, etc.). We are not limited to changing the position, but other properties as well.

Demos!

Most of you are probably wondering if any of these concepts are at all practical to use in a real-world use case. Let me show you a few examples that will (hopefully) convince you that they are.

Progress bar

The background color changing trick makes for a great progress bar or any similar element where we need to show a different color based on progression.

This demo supports Chrome, Edge, and Firefox at the time of writing.

That demo is a pretty simple example where I define three ranges:

  • Red: [0% 30%]
  • Orange: [30% 60%]
  • Green: [60% 100%]

There’s no wild CSS or JavaScript to update the color. A “magic” background property allows us to have a dynamic color that changes based on computed values.

Editable content

It’s common to give users a way to edit content. We can update color based on what’s entered.

In the following example, we get a yellow “warning” when entering more than three lines of text, and a red “warning” if we go above six lines. This can a way to reduce JavaScript that needs to detect the height, then add/remove a particular class.

This demo supports Chrome, Edge, and Firefox at the time of writing.

Timeline layout

Timelines are great patterns for visualizing key moments in time. This implementation uses three tricks to get one without any media queries. One trick is updating the number of columns, another is hiding some elements on small screens, and the last one is updating the background color. Again, no media queries!

When the screen width is below 600px, all of the pseudo elements are removed, changing the layout from two columns to one column. Then the color updates from a blue/green/green/blue pattern to a blue/green/blue/green one.

Responsive card

Here’s a responsive card approach where CSS properties update based on the viewport size. Normally, we might expect the layout to transition from two columns on large screens to one column on small screens, where the card image is stacked either above or below the content. In this example, however, we change the position, width, height, padding, and border radius of the image to get a totally different layout where the image sits beside the card title.

Speech bubbles

Need some nice-looking testimonials for your product or service? These responsive speech bubbles work just about anywhere, even without media queries.

Fixed button

You know those buttons that are sometimes fixed to the left or right edge of the screen, usually for used to link up a contact for or survey? We can have one of those on large screens, then transform it into a persistent circular button fixed to the bottom-right corner on small screens for more convenient taps.

Fixed alert

One more demo, this time for something that could work for those GDPR cookie notices:

Conclusion

Media queries have been a core ingredient for responsive designs since the term responsive design was coined years ago. While they certainly aren’t going anywhere, we covered a bunch of newer CSS features and concepts that allow us to rely less often on media queries for creating responsive layouts.

We looked at flexbox and grid, clamp(), relative units, and combined them together to do all kinds of things, from changing the background of an element based on its container width, moving positions at certain screen sizes, and even mimicking as-of-yet-unreleased container queries. Exciting stuff! And all without one @media in the CSS.

The goal here is not to get rid or replace media queries but it’s more to optimize and reduce the amount of code especially that CSS has evolved a lot and now we have some powerful tool to create conditional styles. In other words, it’s awesome to see the CSS feature set grow in ways that make our lives as front-enders easier while granting us superpowers to control how our designs behave like we have never had before.


Responsive Layouts, Fewer Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/responsive-layouts-fewer-media-queries/feed/ 20 357080
Use CSS Clamp to create a more flexible wrapper utility https://css-tricks.com/use-css-clamp-to-create-a-more-flexible-wrapper-utility/ https://css-tricks.com/use-css-clamp-to-create-a-more-flexible-wrapper-utility/#comments Wed, 17 Feb 2021 15:54:30 +0000 https://css-tricks.com/?p=334393 I like Andy’s idea here:

.wrapper {
  width: clamp(16rem, 90vw, 70rem);
  margin-left: auto;
  margin-right: auto;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

Normally I’d just set a max-width there, but as Andy says:

This becomes a slight issue in mid-sized viewports, such


Use CSS Clamp to create a more flexible wrapper utility originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I like Andy’s idea here:

.wrapper {
  width: clamp(16rem, 90vw, 70rem);
  margin-left: auto;
  margin-right: auto;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

Normally I’d just set a max-width there, but as Andy says:

This becomes a slight issue in mid-sized viewports, such as tablets in portrait mode, in long-form content, such as this article because contextually, the line-lengths feel very long.

So, on super large screens, you’ll get capped at 70rem (or whatever you think a good maximum is), and on small screens you’ll get full width, which is fine. But it’s those in-betweens that aren’t so great. I made a little demo to get a feel for it. This video makes it clear I think:

To Shared LinkPermalink on CSS-Tricks


Use CSS Clamp to create a more flexible wrapper utility originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/use-css-clamp-to-create-a-more-flexible-wrapper-utility/feed/ 9 334393
The Raven Technique: One Step Closer to Container Queries https://css-tricks.com/the-raven-technique-one-step-closer-to-container-queries/ https://css-tricks.com/the-raven-technique-one-step-closer-to-container-queries/#comments Tue, 10 Nov 2020 15:40:00 +0000 https://css-tricks.com/?p=324644 For the millionth time: We need container queries in CSS! And guess what, it looks like we’re heading in that direction.

When building components for a website, you don’t always know how that component will be used. Maybe it …


The Raven Technique: One Step Closer to Container Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
For the millionth time: We need container queries in CSS! And guess what, it looks like we’re heading in that direction.

When building components for a website, you don’t always know how that component will be used. Maybe it will be render as wide as the browser window is. Maybe two of them will sit side by side. Maybe it will be in some narrow column. The width of it doesn’t always correlate with the width of the browser window.

It’s common to reach a point where having container based queries for the CSS of the component would be super handy. If you search around the web for solution to this, you’ll probably find several JavaScript-based solutions. But those come at a price: extra dependencies, styling that requires JavaScript, and polluted application logic and design logic.

I am a strong believer in separation of concerns, and layout is a CSS concern. For example, as nice of an API as IntersectionObserver is, I want things like :in-viewport in CSS! So I continued searching for a CSS-only solution and I came across Heydon Pickering’s The Flexbox Holy Albatross. It is a nice solution for columns, but I wanted more. There are some refinements of the original albatross (like The Unholy Albatross), but still, they are a little hacky and all that is happening is a rows-to-columns switch.

I still want more! I want to get closer to actual container queries! So, what does CSS have offer that I could tap into? I have a mathematical background, so functions like calc(), min(), max() and clamp() are things I like and understand.

Next step: build a container-query-like solution with them.

Want to see what is possible before reading on? Here is a CodePen collection showing off what can be done with the ideas discussed in this article.


Why “Raven”?

This work is inspired by Heydon’s albatross, but the technique can do more tricks, so I picked a raven, since ravens are very clever birds.

Recap: Math functions in CSS

The calc() function allows mathematical operations in CSS. As a bonus, one can combine units, so things like calc(100vw - 300px) are possible.

The min() and max() functions take two or more arguments and return the smallest or biggest argument (respectively).

The clamp() function is like a combination of min() and max() in a very useful way. The function clamp(a, x, b) will return:

  • a if x is smaller than a
  • b if x is bigger than b and
  • x if x is in between a and b

So it’s a bit like clamp(smallest, relative, largest). One may think of it as a shorthand for min(max(a,x),b). Here’s more info on all that if you’d like to read more.

We’re also going to use another CSS tool pretty heavily in this article: CSS custom properties. Those are the things like --color: red; or --distance: 20px. Variables, essentially. We’ll be using them to keep the CSS cleaner, like not repeating ourselves too much.

Let’s get started with this Raven Technique.

Step 1: Create configuration variables

Let’s create some CSS custom properties to set things up.

What is the base size we want our queries to be based on? Since we’re shooting for container query behavior, this would be 100% — using 100vw would make this behave like a media query, because that’s the width of the browser window, not the container!

--base_size: 100%;

Now we think about the breakpoints. Literally container widths where we want a break in order to apply new styles.

--breakpoint_wide: 1500px; 
/* Wider than 1500px will be considered wide */
--breakpoint_medium: 800px;
/* From 801px to 1500px will be considered medium */
/* Smaller than or exact 800px will be small */

In the running example, we will use three intervals, but there is no limit with this technique.

Now let’s define some (CSS length) values we would like to be returned for the intervals defined by the breakpoints. These are literal values:

--length_4_small: calc((100% / 1) - 10px); /* Change to your needs */
--length_4_medium: calc((100% / 2) - 10px); /* Change to your needs */
--length_4_wide: calc((100% / 3) - 10px); /* Change to your needs */

This is the config. Let’s use it!

Step 2: Create indicator variables

We will create some indicator variables for the intervals. They act a bit like boolean values, but with a length unit (0px and 1px). If we clamp those lengths as minimum and maximum values, then they serve as a sort of “true” and “false” indicator.

So, if, and only if --base_size is bigger than --breakpoint_wide, we want a variable that’s 1px. Otherwise, we want 0px. This can be done with clamp():

--is_wide: clamp(0px,
  var(--base_size) - var(--breakpoint_wide),
  1px
);

If var(--base_size) - var(--breakpoint_wide) is negative, then --base_size is smaller than --breakpoint_wide, so clamp() will return 0px in this case.

Conversely, if --base_size is bigger than --breakpoint_wide, the calculation will give a positive length, which is bigger than or equal to 1px. That means clamp() will return 1px.

Bingo! We got an indicator variable for “wide.”

Let’s do this for the “medium” interval:

--is_medium: clamp(0px,
  var(--base_size) - var(--breakpoint_medium),
  1px
); /*  DO NOT USE, SEE BELOW! */

This will give us 0px for the small interval, but 1px for the medium and the wide interval. What we want, however, is 0px for the wide interval and 1px for the medium interval exclusively.

We can solve this by subtracting --is_wide value. In the wide interval, 1px - 1px is 0px; in the medium interval 1px - 0px is 1px; and for the small interval 0px - 0px gives 0px. Perfect.

So we get:

--is_medium: calc(
  clamp(0px, 
  var(--base_size) - var(--breakpoint_medium), 
  1px) 
  - var(--is_wide)
); 

See the idea? To calculate an indicator variable, use clamp() with 0px and 1px as borders and the difference of --base_width and --breakpoint_whatever as the clamped value. Then subtract the sum of all indicators for bigger intervals. This logic produces the following for the smallest interval indicator:

--is_small: calc(
  clamp(0px,
    (var(--base_size) - 0px,
    1px)
  - (var(--is_medium) + var(--is_wide))
); 

We can skip the clamp here because the breakpoint for small is 0px and --base_size is positive, so --base_size - 0px is alway bigger than 1px and clamp() will always return 1px. Therefore, the calculation of --is_small can be simplified to:

--is_small: calc(1px - (var(--is_medium) + var(--is_wide))); 

Step 3: Use indicator variables to select interval values

Now we need to go from these “indicator variables” to something useful. Let’s assume we’re working with a pixel-based layout. Don’t panic, we will handle other units later.

Here’s a question. What does this return?

calc(var(--is_small) * 100);

If --is_small is 1px, it will return 100px and if --is_small is 0px, it will return 0px.

How is this useful? See this:

calc(
  (var(--is_small) * 100) 
  +
  (var(--is_medium) * 200) 
);

This will return 100px + 0px = 100px in the small interval (where --is_small is 1px and --is_medium is 0px). In the medium interval (where --is_medium is 1px and --is_small is 0px), it will return 0px + 200px = 200px.

Do you get the idea? See Roman Komarov’s article for a deeper look at what is going on here because it can be complex to grasp.

You multiply a pixel value (without a unit) by the corresponding indicator variable and sum up all these terms. So, for a pixel based layout, something like this is sufficient:

width: calc(
    (var(--is_small)  * 100) 
  + (var(--is_medium) * 200) 
  + (var(--is_wide)   * 500) 
  );

But most of the time, we don’t want pixel-based values. We want concepts, like “full width” or “third width” or maybe even other units, like 2rem, 65ch, and the like. We’ll have to keep going here for those.

Step 4: Use min() and an absurdly large integer to select arbitrary-length values

In the first step, we defined something like this instead of a static pixel value:

--length_4_medium: calc((100% / 2) - 10px);

How can we use them then? The min() function to the rescue!

Let’s define one helper variable:

--very_big_int: 9999; 
/* Pure, unitless number. Must be bigger than any length appearing elsewhere. */

Multiplying this value by an indicator variable gives either 0px or 9999px. How large this value should be depends on your browser. Chrome will take 999999, but Firefox will not accept that high of a number, so 9999 is a value that will work in both. There are very few viewports larger than 9999px around, so we should be OK.

What happens, then, when we min() this with any value smaller than 9999px but bigger than 0px?

min(
  var(--length_4_small), 
  var(--is_small) * var(--very_big_int) 
);

If, and only if --is_small is 0px, it will return 0px. If --is_small is 1px, the multiplication will return 9999px (which is bigger than --length_4_small), and min will return: --length_4_small.

This is how we can select any length (that is, smaller than 9999px but bigger than 0px) based on indicator variables.

If you deal with viewports larger than 9999px, then you’ll need to adjust the --very_big_int variable. This is a bit ugly, but we can fix this the moment pure CSS can drop the unit on a value in order to get rid of the units at our indicator variables (and directly multiply it with any length). For now, this works.

We will now combine all the parts and make the Raven fly!

Step 5: Bringing it all together

We can now calculate our dynamic container-width-based, breakpoint-driven value like this:

--dyn_length: calc(
    min(var(--is_wide)   * var(--very_big_int), var(--length_4_wide)) 
  + min(var(--is_medium) * var(--very_big_int), var(--length_4_medium))
  + min(var(--is_small)  * var(--very_big_int), var(--length_4_small))
);

Each line is a min() from Step 4. All lines are added up like in Step 3, the indicator variables are from Step 2 and all is based on the configuration we did in Step 1 — they work all together in one big formula!

Want to try it out? Here is a is a Pen to play with (see the notes in the CSS).

This Pen uses no flexbox, no grid, no floats. Just some divs. This is to show that helpers are unnecessary in this kind of layout. But feel free to use the Raven with these layouts too as it will help you do more complex layouts.

Anything else?

So far, we’ve used fixed pixel values as our breakpoints, but maybe we want to change layout if the container is bigger or smaller than half of the viewport, minus 10px? No problem:

--breakpoint_wide: calc(50vw - 10px);

That just works! Other formulas work as well. To avoid strange behavior, we want to use something like:

--breakpoint_medium: min(var(--breakpoint_wide), 500px);

…to set a second breakpoint at 500px width. The calculations in Step 2 depend on the fact that --breakpoint_wide is not smaller than --breakpoint_medium. Just keep your breakpoints in the right order: min() and/or max() are very useful here!

What about heights?

The evaluations of all the calculations are done lazily. That is, when assigning --dyn_length to any property, the calculation will be based on whatever --base_size evaluates to in this place. So setting a height will base the breakpoints on 100% height, if --base_size is 100%.

I have not (yet) found a way to set a height based on the width of a container. So, you can use padding-top since 100% evaluates to the width for padding.

What about showing and hiding things?

The simplest way to show and hide things the Raven way is to set the width to 100px (or any other suitable width) at the appropriate indicator variable:

.show_if_small {
  width: calc(var(--is_small) * 100);
}
.show_if_medium {
  width: calc(var(--is_medium) * 100);
}
.show_if_wide {
  width: calc(var(--is_wide) * 100);
}

You need to set:

overflow: hidden;
display: inline-block; /* to avoid ugly empty lines */

…or some other way to hide things within a box of width: 0px. Completely hiding the box requires setting additional box model properties, including margin, padding and border-width, to 0px . The Raven can do this for some properties, but it’s just as effective to fix them to 0px.

Another alternative is to use position: absolute; and draw the element off-screen via left: calc(var(--is_???) * 9999);.

Takeaways

We might not need JavaScript at all, even for container query behavior! Certainly, we’d hope that if we actually get container queries in the CSS syntax, it will be a lot easier to use and understand — but it’s also very cool that things are possible in CSS today.

While working on this, I developed some opinions about other things CSS could use:

  • Container-based units like conW and conH to set heights based on width. These units could be based on the root element of the current stacking context.
  • Some sort of “evaluate to value” function, to overcome problems with lazy evaluation. This would work great with a “strip unit” function that works at render time.

Note: In an earlier version, I had used cw and ch for the units but it was pointed out to me that those can easily be confused by with CSS units with the same name. Thanks to Mikko Tapionlinna and Gilson Nunes Filho in the comments for the tip!)

If we had that second one, it would allow us to set colors (in a clean way), borders, box-shadow, flex-grow, background-position, z-index, scale(), and other things with the Raven.

Together with component-based units, setting child dimensions to the same aspect-ratio as the parent would even be possible. Dividing by a value with unit is not possible; otherwise --indicator / 1px would work as “strip unit” for the Raven.

Bonus: Boolean logic

Indicator variables look like boolean values, right? The only difference is they have a “px” unit. What about the logical combination of those? Imagine things like “container is wider than half the screen” and “layout is in two-column mode.” CSS functions to the rescue again!

For the OR operator, we can max() over all of the indicators:

--a_OR_b: max( var(--indicator_a) , var(--indicator_b) );

For the NOT operator, we can subtract the indicator from 1px:

--NOT_a: calc(1px - var(--indicator_a));

Logic purists may stop here, since NOR(a,b) = NOT(OR(a,b)) is complete boolean algebra. But, hey, just for fun, here are some more:

AND:

--a_AND_b: min(var(--indicator_a), var(--indicator_b)); 

This evaluates to 1px if and only if both indicators are 1px.

Note that min() and max() take more than two arguments. They still work as an AND and OR for (more than two) indicator variables.

XOR:

--a_XOR_b: max(
  var(--indicator_a) - var(--indicator_b), 
  var(--indicator_b) - var(--indicator_a)
);

If (and only if) both indicators have the same value, both differences are 0px, and max() will return this. If the indicators have different values, one term will give -1px, the other will give 1px. max() returns 1px in this case.

If anyone is interested in the case where two indicators are equal, use this:

--a_EQ_b: calc(1px - 
  max(
    var(--indicator_a) - var(--indicator_b), 
    var(--indicator_b) - var(--indicator_a)
  )
);

And yes, this is NOT(a XOR b). I was unable to find a “nicer” solution to this.

Equality may be interesting for CSS length variables in general, rather than just being used for indicator variables. By using clamp() once again, this might help:

--a_EQUALS_b_general: calc(
  1px -
  clamp(0px,
        max(
          var(--var_a) - var(--var_b),
          var(--var_b) - var(--var_a)
        ),
        1px)
  );

Remove the px units to get general equality for unit-less variables (integers).

I think this is enough boolean logic for most layouts!

Bonus 2: Set the number of columns in a grid layout

Since the Raven is limited to return CSS length values, it is unable to directly choose the number of columns for a grid (since this is a value without a unit). But there is a way to make it work (assuming we declared the indicator variables like above):

--number_of_cols_4_wide: 4;
--number_of_cols_4_medium: 2;
--number_of_cols_4_small: 1;
--grid_gap: 0px;

--grid_columns_width_4_wide: calc(
(100% - (var(--number_of_cols_4_wide) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_wide));
--grid_columns_width_4_medium: calc(
(100% - (var(--number_of_cols_4_medium) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_medium));
--grid_columns_width_4_small: calc(
(100% - (var(--number_of_cols_4_small) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_small));

--raven_grid_columns_width: calc( /*  use the Raven to combine the values  */
  min(var(--is_wide) * var(--very_big_int),var(--grid_columns_width_4_wide)) 
  + min(var(--is_medium) * var(--very_big_int),var(--grid_columns_width_4_medium))
  + min(var(--is_small) * var(--very_big_int),var(--grid_columns_width_4_small))
  );

And set your grid up with:

.grid_container{
  display: grid;
  grid-template-columns: repeat(auto-fit, var(--raven_grid_columns_width));
  gap: var(--grid_gap)
};

How does this work?

  1. Define the number of columns we want for each interval (lines 1, 2, 3)
  2. Calculate the perfect width of the columns for each interval (lines 5, 6, 7).

    What is happening here?

    First, we calculate the available space for our columns. This is 100%, minus the place the gaps will take. For n columns, there are (n-1) gaps. This space is then divided by the number of columns we want.

  3. Use the Raven to calculate the right column’s width for the actual --base_size.

In the grid container, this line:

grid-template-columns: repeat(auto-fit, var(--raven_grid_columns_width));

…then chooses the number of columns to fit the value the Raven provided (which will result in our --number_of_cols_4_??? variables from above).

The Raven may not be able give the number of columns directly, but it can give a length to make repeat and autofit calculate the number we want for us.

But auto-fit with minmax() does the same thing, right? No! The solution above will never give three columns (or five) and the number of columns does not need to increase with the width of the container. Try to set the following values in this Pen to see the Raven take full flight:

--number_of_cols_4_wide: 1;
--number_of_cols_4_medium: 2;
--number_of_cols_4_small: 4;

Bonus 3: Change the background-color with a linear-gradient()

This one is a little more mind-bending. The Raven is all about length values, so how can we get a color out of these? Well, linear gradients deal with both. They define colors in certain areas defined by length values. Let’s go through that concept in more detail before getting to the code.

To work around the actual gradient part, it is a well known technique to double up a color stop, effectively making the gradient part happen within 0px. Look at this code to see how this is done:

background-image:linear-gradient(
  to right,
  red 0%,
  red 50%,
  blue 50%,
  blue 100%
);

This will color your background red on the left half, blue on the right. Note the first argument “to right.” This implies that percentage values are evaluated horizontally, from left to right.

Controlling the values of 50% via Raven variables allows for shifting the color stop at will. And we can add more color stops. In the running example, we need three colors, resulting in two (doubled) inner color stops.

Adding some variables for color and color stops, this is what we get:

background-image: linear-gradient(
  to right,
  var(--color_small) 0px,
  var(--color_small) var(--first_lgbreak_value),
  var(--color_medium) var(--first_lgbreak_value),
  var(--color_medium) var(--second_lgbreak_value),
  var(--color_wide) var(--second_lgbreak_value),
  var(--color_wide) 100%
);

But how do we calculate the values for --first_lgbreak_value and --second_lgbreak_value? Let’s see.

The first value controls where --color_small is visible. On the small interval, it should be 100%, and 0px in the other intervals. We’ve seen how to do this with the raven. The second variable controls the visibility of --color_medium. It should be 100% for the small interval, 100% for the medium interval, but 0px for the wide interval. The corresponding indicator must be 1px if the container width is in the small or the medium interval.

Since we can do boolean logic on indicators, it is:

max(--is_small, --is_medium)

…to get the right indicator. This gives:

--first_lgbreak_value: min(var(--is_small) * var(--very_big_int), 100%);
--second_lgbreak_value: min(
  max(var(--is_small), var(--is_medium)) * var(--very_big_int), 100%);

Putting things together results in this CSS code to change the background-color based on the width (the interval indicators are calculated like shown above):

--first_lgbreak_value: min(
      var(--is_small) * var(--very_big_int), 100%);
--second_lgbreak_value: min(
    max(var(--is_small), var(--is_medium)) * var(--very_big_int), 100%);

--color_wide: red;/* change to your needs*/
--color_medium: green;/* change to your needs*/
--color_small: lightblue;/* change to your needs*/

background-image: linear-gradient(
  to right,
  var(--color_small) 0px,
  var(--color_small) var(--first_lgbreak_value),
  var(--color_medium) var(--first_lgbreak_value),
  var(--color_medium) var(--second_lgbreak_value),
  var(--color_wide) var(--second_lgbreak_value),
  var(--color_wide) 100%
);

Here’s a Pen to see that in action.

Bonus 4: Getting rid of nested variables

While working with the Raven, I came across a strange problem: There is a limit on the number of nested variables that can be used in calc(). This can cause some problems when using too many breakpoints. As far as I understand, this limit is in place to prevent page blocking while calculating the styles and allow for faster circle-reference checks.

In my opinion, something like evaluate to value would be a great way to overcome this. Nevertheless, this limit can give you a headache when pushing the limits of CSS. Hopefully this problem will be tackled in the future.

There is a way to calculate the indicator variables for the Raven without the need of (deeply) nested variables. Let’s look at the original calculation for the --is_medium value:

--is_medium:calc(
  clamp(0px, 
        var(--base_size) - var(--breakpoint_medium), 
        1px) 
        - var(--is_wide)
); 

The problem occurs with the subtraction of --is_wide . This causes the CSS parser to paste in the definition of the complete formula of --is_wide. The calculation of --is_small has even more of these types of references. (The definition for --is_wide will even be pasted twice since it is hidden within the definition of --is_medium and is also used directly.)

Fortunately, there is a way to calculate indicators without referencing indicators for bigger breakpoints.

The indicator is true if, and only if, --base_size is bigger than the lower breakpoint for the interval and smaller or equal than the higher breakpoint for the interval. This definition gives us the following code:

--is_medium: 
  min(
    clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px),
    clamp(0px, 1px + var(--breakpoint_wide) - var(--base_size), 1px)
  );
  • min() is used as a logical AND operator
  • the first clamp() is “--base_size is bigger than --breakpoint_medium
  • the second clamp() means “--base_size is smaller or equal than --breakpoint_wide.”
  • Adding 1px switches from “smaller than” to “smaller or equal than.” This works, because we are dealing with whole (pixel) numbers (a <= b means a < (b+1) for whole numbers).

The complete calculation of the indicator variables can be done this way:

--is_wide: clamp(0px, var(--base_size) - var(--breakpoint_wide), 1px);

--is_medium: min(clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px),
                 clamp(0px, 1px + var(--breakpoint_wide) - var(--base_size), 1px)
             );

--is_small: clamp(0px,1px + var(--breakpoint_medium) - var(--base_size), 1px);

The calculations for --is_wide and --is_small are simpler, because only one given breakpoint needs to be checked for each.

This works with all the things we’ve looked at so far. Here’s a Pen that combines examples.

Final thoughts

The Raven is not capable of all the things that a media query can do. But we don’t need it to do that, as we have media queries in CSS. It is fine to use them for the “big” design changes, like the position of a sidebar or a reconfiguration of a menu. Those things happen within the context of the full viewport (the size of the browser window).

But for components, media queries are kind of wrong, since we never know how components will be sized.

Heydon Pickering demonstrated this problem with this image:

Three boxes representing browsers from left-to-right. The first is a wide viewport with three boxes in a single row. The second is a narrow viewport with the boxes stacked vertically. The third is a wide viewport, but with a dashed vertical line down the middle representing a container and the three boxes are to the right of it in a single row.

I hope that the Raven helps you to overcome the problems of creating responsive layouts for components and pushes the limits of “what can be done with CSS” a little bit further.

By showing what is possible today, maybe “real” container queries can be done by adding some syntax sugar and some very small new functions (like conW, conH, “strip-unit” or “evaluate-to-pixels”). If there was a function in CSS that allows to rewrite “1px” to a whitespace, and “0px” to “initial“, the Raven could be combined with the Custom Property Toggle Trick and change every CSS property, not just length values.

By avoiding JavaScript for this, your layouts will render faster because it’s not dependent on JavaScript downloading or running. It doesn’t even matter if JavaScript is disabled. These calculations will not block your main thread and your application logic isn’t cluttered with design logic.


Thanks to Chris, Andrés Galante, Cathy Dutton, Marko Ilic, and David Atanda for their great CSS-Tricks articles. They really helped me explore what can be done with the Raven.


The Raven Technique: One Step Closer to Container Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-raven-technique-one-step-closer-to-container-queries/feed/ 10 324644
Linearly Scale font-size with CSS clamp() Based on the Viewport https://css-tricks.com/linearly-scale-font-size-with-css-clamp-based-on-the-viewport/ https://css-tricks.com/linearly-scale-font-size-with-css-clamp-based-on-the-viewport/#comments Fri, 25 Sep 2020 14:24:35 +0000 https://css-tricks.com/?p=321421 Responsive typography has been tried in the past with a slew of methods such as media queries and CSS calc().

Here, we’re going to explore a different way to linearly scale text between a set of minimum and maximum …


Linearly Scale font-size with CSS clamp() Based on the Viewport originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Responsive typography has been tried in the past with a slew of methods such as media queries and CSS calc().

Here, we’re going to explore a different way to linearly scale text between a set of minimum and maximum sizes as the viewport’s width increases, with the intent of making its behavior at different screen sizes more predictable — All in a single line of CSS, thanks to clamp().

The CSS function clamp() is a heavy hitter. It’s useful for a variety of things, but it’s especially nice for typography. Here’s how it works. It takes three values: 

clamp(minimum, preferred, maximum);

The value it returns will be the preferred value, until that preferred value is lower than the minimum value (at which point the minimum value will be returned) or higher than the maximum value (at which point the maximum will be returned).

In this example, the preferred value is 50%. On the left 50% of the 400px viewport is 200px, which is less than the 300px minimum value that gets used instead. On the right, 50% of the 1400px viewport equals 700px, which is greater than the minimum value and lower than the 800px maximum value, so it equates to 700px.

Wouldn’t it just always be the preferred value then, assuming you aren’t being weird and set it between the minimum and maximum? Well, you’re rather expected to use a formula for the preferred value, like:

.banner {
  width: clamp(200px, 50% + 20px, 800px); /* Yes, you can do math inside clamp()! */
}

Say you want to set an element’s minimum font-size to 1rem when the viewport width is 360px or below, and set the maximum to 3.5rem when the viewport width is 840px or above. 

In other words:

1rem   = 360px and below
Scaled = 361px - 839px
3.5rem = 840px and above

Any viewport width between 361 and 839 pixels needs a font size linearly scaled between 1 and 3.5rem. That’s actually super easy with clamp()! For example, at a viewport width of 600 pixels, halfway between 360 and 840 pixels, we would get exactly the middle value between 1 and 3.5rem, which is 2.25rem.

Line chart with the vertical axis measured in font size rem unites from 0 to 4, and the horizontal axis measuring viewport width from 0 to 1,060 pixels. There are four blue points on the grid with a blue line connecting them.

What we are trying to achieve with clamp() is called linear interpolation: getting intermediate information between two data points.

Here are the four steps to do this:

Step 1

Pick your minimum and maximum font sizes, and your minimum and maximum viewport widths. In our example, that’s 1rem and 3.5rem for the font sizes, and 360px and 840px for the widths.

Step 2

Convert the widths to rem. Since 1rem on most browsers is 16px by default (more on that later), that’s what we’re going to use. So, now the minimum and maximum viewport widths will be 22.5rem and 52.5rem, respectively.

Step 3

Here, we’re gonna lean a bit to the math side. When paired together, the viewport widths and the font sizes make two points on an X and Y coordinate system, and those points make a line.

A two-dimensional coordinate chart with two points and a red line intersecting them.
(22.5, 1) and (52.5, 3.5)

We kinda need that line — or rather its slope and its intersection with the Y axis to be more specific. Here’s how to calculate that:

slope = (maxFontSize - minFontSize) / (maxWidth - minWidth)
yAxisIntersection = -minWidth * slope + minFontSize

That gives us a value of 0.0833 for the slope and -0.875 for the intersection at the Y axis.

Step 4

Now we build the clamp() function. The formula for the preferred value is:

preferredValue = yAxisIntersection[rem] + (slope * 100)[vw]

So the function ends up like this:

.header {
  font-size: clamp(1rem, -0.875rem + 8.333vw, 3.5rem);
}

You can visualize the result in the following demo:

Go ahead and play with it. As you can see, the font size stops growing when the viewport width is 840px and stops shrinking at 360px. Everything in between changes in linear fashion.

What if the user changes the root’s font size?

You may have noticed a little flaw with this whole approach: it only works as long as the root’s font size is the one you think it is — which is 16px in the previous example — and never changes.

We are converting the widths, 360px and 840px, to rem units by dividing them by 16 because that’s what we assume is the root’s font size. If the user has their preferences set to another root font size, say 18px instead of the default 16px, then that calculation is going to be wrong and the text won’t resize the way we’d expect.

There is only one approach we can use here, and it’s (1) making the necessary calculations in code on page load, (2) listening for changes to the root’s font size, and (3) re-calculating everything if any changes take place.

Here’s a useful JavaScript function to do the calculations:

// Takes the viewport widths in pixels and the font sizes in rem
function clampBuilder( minWidthPx, maxWidthPx, minFontSize, maxFontSize ) {
  const root = document.querySelector( "html" );
  const pixelsPerRem = Number( getComputedStyle( root ).fontSize.slice( 0,-2 ) );

  const minWidth = minWidthPx / pixelsPerRem;
  const maxWidth = maxWidthPx / pixelsPerRem;

  const slope = ( maxFontSize - minFontSize ) / ( maxWidth - minWidth );
  const yAxisIntersection = -minWidth * slope + minFontSize

  return `clamp( ${ minFontSize }rem, ${ yAxisIntersection }rem + ${ slope * 100 }vw, ${ maxFontSize }rem )`;
}

// clampBuilder( 360, 840, 1, 3.5 ) -> "clamp( 1rem, -0.875rem + 8.333vw, 3.5rem )"

I’m deliberately leaving out how to inject the returned string into the CSS because there are a ton of ways to do that depending on your needs and whether your are using vanilla CSS, a CSS-in-JS library, or something else. Also, there is no native event for font size changes, so we would have to manually check for that. We could use setInterval to check every second, but that could come at a performance cost.

This is more of an edge case. Very few people change their browser’s font size and even fewer are going to change it precisely while visiting your site. But if you want your site to be as responsive as possible, then this is the way to go.

For those who don’t mind that edge case

You think you can live without it being perfect? Then I got something for you. I made a small tool to make make the calculations quick and simple.

All you have to do is plug the widths and font sizes into the tool, and the function is calculated for you. Copy and paste the result in your CSS. It’s not fancy and I’m sure a lot of it can be improved but, for the purpose of this article, it’s more than enough. Feel free to fork and modify to your heart’s content.

How to avoid reflowing text

Having such fine-grained control on the dimensions of typography allows us to do other cool stuff — like stopping text from reflowing at different viewport widths.

This is how text normally behaves.

It has a number of lines at a certain viewport width…
…and wraps it’s lines to fit another width

But now, with the control we have, we can make text keep the same number of lines, breaking on the same word always, on whatever viewport width we throw at it.

Viewport width = 400px
Viewport width = 740px

So how do we do this? To start, the ratio between font sizes and viewport widths must stay the same. In this example, we go from 1rem at 320px to 3rem at 960px.

320 / 1 = 320
960 / 3 = 320

If we’re using the clampBuilder() function we made earlier, that becomes:

const text = document.querySelector( "p" );
text.style.fontSize = clampBuilder( 320, 960, 1, 3 );

It keeps the same width-to-font ratio. The reason we do this is because we need to ensure that the text has the right size at every width in order for it to be able to keep the same number of lines. It’ll still reflow at different widths but doing this is necessary for what we are going to do next. 

Now we have to get some help from the CSS character (ch) unit because having the font size just right is not enough. One ch unit is the equivalent to the width of the glyph “0” in an element’s font. We want to make the body of text as wide as the viewport, not by setting width: 100% but with width: Xch, where X is the amount of ch units (or 0s) necessary to fill the viewport horizontally.

To find X, we must divide the minimum viewport width, 320px, by the element’s ch size at whatever font size it is when the viewport is 320px wide. That’s 1rem in this case.

Don’t sweat it, here’s a snippet to calculate an element’s ch size:

// Returns the width, in pixels, of the "0" glyph of an element at a desired font size
function calculateCh( element, fontSize ) {
  const zero = document.createElement( "span" );
  zero.innerText = "0";
  zero.style.position = "absolute";
  zero.style.fontSize = fontSize;

  element.appendChild( zero );
  const chPixels = zero.getBoundingClientRect().width;
  element.removeChild( zero );

  return chPixels;
}

Now we can proceed to set the text’s width:

function calculateCh( element, fontSize ) { ... }

const text = document.querySelector( "p" );
text.style.fontSize = clampBuilder( 320, 960, 1, 3 );
text.style.width = `${ 320 / calculateCh(text, "1rem" ) }ch`;
Umm, who invited you to the party, scrollbar?

Whoa, wait. Something bad happened. There’s a horizontal scrollbar screwing things up!

When we talk about 320px, we are talking about the width of the viewport, including the vertical scrollbar. So, the text’s width is being set to the width of the visible area, plus the width of the scrollbar which makes it overflow horizontally.

Then why not use a metric that doesn’t include the width of the vertical scrollbar? We can’t and it’s because of the CSS vw unit. Remember, we are using vw in clamp() to control font sizes. You see, vw includes the width of the vertical scrollbar which makes the font scale along the viewport width including the scrollbar. If we want to avoid any reflow, then the width must be proportional to whatever width the viewport is, including the scrollbar.

So what do we do? When we do this:

text.style.width = `${ 320 / calculateCh(text, "1rem") }ch`;

…we can scale the result down by multiplying it by a number smaller than 1. 0.9 does the trick. That means the text’s width is going to be 90% of the viewport width, which will more than account for the small amount of space taken up by the scrollbar. We can make it narrower by using an even smaller number, like 0.6.

function calculateCh( element, fontSize ) { ... }

const text = document.querySelector( "p" );
text.style.fontSize = clampBuilder( 20, 960, 1, 3 );
text.style.width = `${ 320 / calculateCh(text, "1rem" ) * 0.9 }ch`;
So long, scrollbar!

You might be tempted to simply subtract a few pixels from 320 to ignore the scrollbar, like this:

text.style.width = `${ ( 320 - 30 ) / calculateCh( text, "1rem" ) }ch`;

The problem with this is that it brings back the reflow issue! That’s because subtracting from 320 breaks the viewport-to-font ratio.

Viewport width = 650px
Viewport width = 670px

The width of text must always be a percentage of the viewport width. Another thing to have in mind is that we need to make sure we’re loading the same font on every device using the site. This sounds obvious doesn’t it? Well, here’s a little detail that could throw your text off. Doing something like font-family: sans-serif won’t guarantee that the same font is used in every browser. sans-serif will set Arial on Chrome for Windows, but Roboto on Chrome for Android. Also, the geometry of some fonts may cause reflow even if you do everything right. Monospaced fonts tend to yield the best results. So always make sure your fonts are on point.

Check out this non-reflowing example in the following demo:

Non-reflowing text inside a container

All we have to do is now is apply the font size and width to the container instead of the text elements directly. The text inside it will just need to be set to width: 100%. This isn’t necessary in the cases of paragraphs and headings since they’re block-level elements anyway and will fill the width of the container automatically.

An advantage of applying this in a parent container is that its children will react and resize automatically without having to set their font sizes and widths one-by-one. Also, if we need to change the font size of a single element without affecting the others, all we’d have to do is change its font size to any em amount and it will be naturally relative to the container’s font size.

Non-reflowing text is finicky, but it’s a subtle effect that can bring a nice touch to a design!

Wrapping up

To cap things off, I put together a little demonstration of how all of this could look in a real life scenario.

In this final example, you can also change the root font size and the clamp() function will be recalculated automatically so the text can have the right size in any situation.

Even though the target of this article is to use clamp() with font sizes, this same technique could be used in any CSS property that receives a length unit. Now, I’m not saying you should use this everywhere. Many times, a good old font-size: 1rem is all you need. I’m just trying to show you how much control you can have when you need it.

Personally, I believe clamp() is one of the best things to arrive in CSS and I can’t wait to see what other usages people come up with as it becomes more and more widespread!


Linearly Scale font-size with CSS clamp() Based on the Viewport originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/linearly-scale-font-size-with-css-clamp-based-on-the-viewport/feed/ 27 321421
Beyond Media Queries: Using Newer HTML & CSS Features for Responsive Designs https://css-tricks.com/beyond-media-queries-using-newer-html-css-features-for-responsive-designs/ https://css-tricks.com/beyond-media-queries-using-newer-html-css-features-for-responsive-designs/#comments Fri, 04 Sep 2020 14:46:17 +0000 https://css-tricks.com/?p=319906 Beyond using media queries and modern CSS layouts, like flexbox and grid, to create responsive websites, there are certain overlooked things we can do well to make responsive sites. In this article, we’ll dig into a number tools (revolving around …


Beyond Media Queries: Using Newer HTML & CSS Features for Responsive Designs originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Beyond using media queries and modern CSS layouts, like flexbox and grid, to create responsive websites, there are certain overlooked things we can do well to make responsive sites. In this article, we’ll dig into a number tools (revolving around HTML and CSS) we have at the ready, from responsive images to relatively new CSS functions that work naturally whether we use media queries or not.

In fact, media queries become more of a complement when used with these features rather than the full approach. Let’s see how that works.

Truly responsive images

Remember when we could just chuck width: 100% on images and call it a day? That still works, of course, and does make images squishy, but there are a number of downsides that come with it, the most notable of which include:

  • The image might squish to the extent that it loses its focal point.
  • Smaller devices still wind up downloading the full size image.

When using images on the web, we have to make sure they’re optimized in terms of their resolution and size. The reason is to ensure that we have the right image resolution to fit the right device, so we don’t end up downloading really large and heavy images for smaller screens which could end up reducing the performance of a site. 

In simple terms, we’re making sure that larger, high-resolution images are sent to larger screens, while smaller, low-resolution variations are sent to smaller screens, improving both performance and user experience.

HTML offers the <picture> element that allows us specify the exact image resource that will be rendered based on the media query we add. As described earlier, instead of having one image (usually a large, high-resolution version) sent to all screen sizes and scaling it to the viewport width, we specify a set of images to serve in specific situations.

<picture>
  <source media="(max-width:1000px)" srcset="picture-lg.png">
  <source media="(max-width:600px)" srcset="picture-mid.png">
  <source media="(max-width:400px)" srcset="picture-sm.png">
  <img src="picture.png" alt="picture"">
</picture>

In this example, picture.png is the full-size image. From there, we define the next-largest version of the image, picture-lg.png, and the size reduces in descending order until the smallest version, picture-sm.png. Note that we’re still using media queries in this approach, but it’s the <picture> element itself that is driving the responsive behavior rather than defining breakpoints in the CSS.

The media queries are added appropriately to scale with the sizes of the picture:

  • Viewports that are 1000px and above get picture.png.
  • Viewports that are between 601px and 999px get picture-lg.png.
  • Viewports that are between 401px and 600px get picture-sm.png.
  • Any thing smaller than 400px gets picture-sm.png.

Interestingly, we can also label each image by image density —  1x, 2x, 3x and so forth — after the URL. This works if we have made the different images in proportion to each other (which we did). This allows the browser to determine which version to download based on the screen’s pixel density in addition to the viewport size. But note how many images we wind up defining:

<picture>
  <source media="(max-width:1000px)" srcset="picture-lg_1x.png 1x, picture-lg_2x.png 2x, picture-lg_3x.png 3x">
  <source media="(max-width:600px)" srcset="picture-mid_1x.png 1x, picture-mid_2x.png 2x, picture-mid_3x.png 3x">
  <source media="(max-width:400px)" srcset="picture-small_1x.png 1x, picture-small_2x.png 2x, picture-small_1x.png 3x">
  <img src="picture.png" alt="picture"">
</picture>

Let’s look specifically at the two tags nested inside the <picture> element: <source> and <img>.

The browser will look for the first <source> element where the media query matches the current viewport width, and then it will display the proper image (specified in the srcset attribute). The <img> element is required as the last child of the <picture> element, as a fallback option if none of the initial source tags matches.

We can also use image density to handle responsive images with just the <img> element using the srcset attribute:

<img
 srcset="
  flower4x.png 4x,
  flower3x.png 3x,
  flower2x.png 2x,
  flower1x.png 1x
 "
 src="flower-fallback.jpg"
>

Another thing we can do is write media queries in the CSS based on the screen resolution (usually measured in dots per inch, or dpi) of the device itself and not just the device viewport. What this means is that instead of:

@media only screen and (max-width: 600px) {
  /* Style stuff */
}

We now have:

@media only screen and (min-resolution: 192dpi) {
  /* Style stuff */
}

This approach lets us dictate what image to render based the screen resolution of the device itself, which could be helpful when dealing with high resolution images. Basically, that means we can display high quality pictures for screens that support higher resolutions and smaller versions at lower resolutions. It’s worth noting that, although mobile devices have small screens, they’re usually high resolution. That means it’s probably not the best idea rely on resolution alone when determining which image to render. It could result in serving large, high-resolution images to really small screens, which may not be the version we really want to display at such a small screen size.

body {
  background-image : picture-md.png; /* the default image */
}


@media only screen and (min-resolution: 192dpi) {
  body {
    background-image : picture-lg.png; /* higher resolution */
  }
}

What <picture> gives us is basically the ability to art direct images. And, in keeping with this idea, we can leverage CSS features, like the object-fit property which, when used with object-position, allows us to crop images for better focal points while maintaining the image’s aspect ratio.

So, to change the focal point of an image:

@media only screen and (min-resolution: 192dpi) {
  body {
    background-image : picture-lg.png;
    object-fit: cover;
    object-position: 100% 150%; /* moves focus toward the middle-right */
  }
}

Setting minimum and maximum values in CSS

The min() function specifies the absolute smallest size that an element can shrink to. This function proves really useful in terms of helping text sizes to properly scale across different screen sizes, like never letting fluid type to drop below a legible font size:

html {
  font-size: min(1rem, 22px); /* Stays between 16px and 22px */
}

min() accepts two values, and they can be relative, percentage, or fixed units. In this example, we’re telling the browser to never let an element with class .box go below 45% width or 600px, whichever is smallest based on the viewport width:

.box {
  width : min(45%, 600px)
}

If 45% computes to a value smaller than 600px, the browser uses 45% as the width. Conversely, if  45% computes to a value greater than 600px, then 600px will be used for the element’s width.

The same sort of thing goes for the max() function. It also accepts two values, but rather than specifying the smallest size for an element, we’re defining the largest it can get.

.box {
  width : max(60%, 600px)
}

If 60% computes to a value greater than 600px, the browser uses 60% as the width. On the flip side, if 60% computes to a value smaller than 600px, then 600px will be used as the element’s width.

Clamping values

Many of us have been clamoring for clamp() for some time now, and we actually have broad support across all modern browsers (sorry, Internet Explorer). clamp() is the combination of the min() and max() functions, accepting three parameters:

  1. the minimum value,
  2. the preferred value, and
  3. the maximum value

For example:

.box {
  font-size : clamp(1rem, 40px, 4rem)
}

The browser will set the font at 1rem until the computed value of 1rem is larger than 40px. And when the computed value is above 40px? Yep, the browser will stop increasing the size after it hits 4rem. You can see how clamp() can be used to make elements fluid without reaching for media queries.

Working with responsive units

Have you ever built a page with a large heading or sub-heading and admired how great it looked on a desktop screen, only to check it on a mobile device and find out that’s it’s too large? I have definitely been in this situation and in this section I’ll be explaining how to handle such problems.

In CSS, you can determine sizes or lengths of elements using various units of measurements, and the most used units of measurements includes: px, em, rem, %, vw, and vh. Although, there are several more units that aren’t used as frequently. What’s of interest to us is that px can be considered an absolute unit, while the rest are considered relative units.

Absolute units

A pixel (px) is considered an absolute unit mainly because it’s fixed and does not change based on the measurement of any other element. It can be considered as the base, or root, unit that some other relative units use. Trying to use pixels for responsive behavior can bump into issues because it’s fixed, but they’re great if you have elements that should not be resized at all.

Relative units

Relative units, like %, em, and rem, are better suited to responsive design mainly because of their ability to scale across different screen sizes.

vw: Relative to the viewport’s width
vh: Relative to the viewport’s height
rem: Relative to the root (<html>) element (default font-size is usually 16px )
em: Relative to the parent element
%: Relative to the parent element

Again, the default font size for most browsers is 16px and and that’s what rem units use to generate their computed values. So, if a user adjusts the font size on the browser, everything on the page scales properly depending on the root size. For example, when dealing a root set at 16px, the number you specify will multiply that number times the default size. For example:

.8rem = 12.8px (.8 * 16)
1rem = 16px (1 * 16)
2rem = 32px (2 * 16)

What if either you or the user changes the default size? As we said already, these are relative units and the final size values will be based off of the new base size. This proves useful within media queries, where you just change the font size and the entire page scales up or down accordingly.

For example, if you changed the font-size to 10px within the CSS, then the calculated sizes would end up being:

html {
  font-size : 10px;
}
1rem = 10px (1 * 10)
2rem = 20px (2 * 10)
.5rem = 5px (.5 * 10)

Note: This also applies to percentage %. For instance:

100% = 16px;
200% = 32px; 
50% = 8px;

And what’s the difference between rem and em units? It’s what the unit uses as its base element. rem calculates values using the font size of the root (<html>) element, whereas an element declaring em values references the font size of the parent element that contains it. If the size of specified parent element is different from the root element (e.g. the parent elements is 18px but the root element is 16px) then em and rem will resolve to different computed values. This gives us more fine-grained control of how our elements respond in different responsive contexts.

vh is an acronym for viewport height, or the viewable screen’s height. 100vh represent 100% of the viewport’s height (depending on the device). In the same vein, vw stands for viewport width, meaning the viewable screen’s width of the device, and 100vw literally represents 100% of the viewport’s width.

Moving beyond media queries

See that? We just looked at a number of really powerful and relatively new HTML and CSS features that give us additional (and possible more effective) ways to build for responsiveness. It’s not that these new-fangled techniques replace what we’ve been doing all along. They are merely more tools in our developer tool belt that give us greater control to determine how elements behave in different contexts. Whether it’s working with font sizes, resolutions, widths, focal points, or any number of things, we have more fine-grain control of the user experience than ever before.

So, next time you find yourself working on a project where you wish you had more control over the exact look and feel of the design on specific devices, check out what native HTML and CSS can do to help — it’s incredible how far things have come along.


Beyond Media Queries: Using Newer HTML & CSS Features for Responsive Designs originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/beyond-media-queries-using-newer-html-css-features-for-responsive-designs/feed/ 27 319906
How to Make a Media Query-less responsive Card Component https://css-tricks.com/how-to-make-a-media-query-less-card-component/ https://css-tricks.com/how-to-make-a-media-query-less-card-component/#comments Tue, 01 Sep 2020 14:29:00 +0000 https://css-tricks.com/?p=319736 Fun fact: it’s possible to create responsive components without any media queries at all. Certainly, if we had container queries, those would be very useful for responsive design at the component level. But we don’t. Still, with or without …


How to Make a Media Query-less responsive Card Component originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Fun fact: it’s possible to create responsive components without any media queries at all. Certainly, if we had container queries, those would be very useful for responsive design at the component level. But we don’t. Still, with or without container queries, we can do things to make our components surprisingly responsive. We’ll use concepts from Intrinsic Web Design, brought to us by Jen Simmons.

Let’s dive together into the use case described below, the solutions regarding the actual state of CSS, and some other tricks I’ll give you.

A responsive “Cooking Recipe” card

I recently tweeted a video and Pen of a responsive card demo I built using a recipe for pizza as an example. (It’s not important to the technology here, but I dropped the recipe at the end because it’s delicious and gluten free.)

The demo here was a first attempt based on a concept from one of Stéphanie Walter’s talks. Here is a video to show you how the card will behave:

And if you want to play with it right now, here’s the Pen.

Let’s define the responsive layout

A key to planning is knowing the actual content you are working, and the importance of those details. Not that we should be hiding content at any point, but for layout and design reasons, it’s good to know what needs to be communicated first and so forth. We’ll be displaying the same content no matter the size or shape of the layout.

Let’s imagine the content with a mobile-first mindset to help us focus on what’s most important. Then when the screen is larger, like on a desktop, we can use the additional space for things like glorious whitespace and larger typography. Usually, a little prioritization like this is enough to be sure of what content is needed for the cards at any and all viewport sizes.

Let’s take the example of a cooking recipe teaser:

In her talk, Stéphanie had already did the job and prioritized the content for our cards. Here’s what she outlined, in order of importance:

  1. Image: because it’s a recipe, you eat with your eyes!
  2. Title: to be sure what you’re going to cook.
  3. Keywords: to catch key info at the first glance.
  4. Rating info: for social proof.
  5. Short description: for the people who read.
  6. Call to action: what you expect the user to do on this card.

This may seem like a lot, but we can get all of that into a single smart card layout!

Non-scalable typography

One of the constraints with the technique I’m going to show you is that you won’t be able to get scalable typography based on container width. Scalable typography (e.g. “fluid type”) is commonly done with the with viewport width (vw) unit, which is based on the viewport, not the parent element.

So, while we might be tempted to reach for fluid type as a non-media query solution for the content in our cards, we won’t be able to use fluid type based on some percentage of the container width nor element width itself, unfortunately. That won’t stop us from our goal, however!

A quick note on “pixel perfection”

Let’s talk to both sides here…

Designers: Pixel perfect is super ideal, and we can certainly be precise at a component level. But there has to be some trade-off at the layout level. Meaning you will have to provide some variations, but allow the in-betweens to be flexible. Things shift in responsive layouts and precision at every possible screen width is a tough ask. We can still make things look great at every scale though!

Developers: You’ll have to be able to fill the gaps between the layouts that have prescribed designs to allow content to be readable and consistent between those states. As a good practice, I also recommend trying to keep as much of a natural flow as possible.

You can also read the Ahmad’s excellent article on the state of pixel perfection.

A recipe for zero media queries

Remember, what we’re striving for is not just a responsive card, but one that doesn’t rely on any media queries. It’s not that media queries should be avoided; it’s more about CSS being powerful and flexible enough for us to have other options available.

To build our responsive card, I was wondering if flexbox would be enough or if I would need to do it with CSS grid instead. Turns out flexbox in indeed enough for us this time, using the behavior and magic of the flex-wrap and flex-basis properties in CSS.

The gist of flex-wrap is that it allows elements to break onto a new line when the space for content gets too tight. You can see the difference between flex with a no-wrap value and with wrapping in this demo:

The flex-basis value of 200px is more of an instruction than a suggestion for the browser, but if the container doesn’t offer enough space for it, the elements move down onto a new line. The margin between columns even force the initial wrapping.

I used this wrapping logic to create the base of my card. Adam Argyle also used it on the following demo features four form layouts with a mere 10 lines of CSS:

In his example, Adam uses flex-basis and flex-grow (used together in flex shorthand property) )to allow the email input to take three times the space occupied by the name input or the button. When the browser estimates there is not enough rooms to display everything on the same row, the layout breaks itself into multiple lines by itself, without us having to manage the changes in media queries.

I also used clamp() function to add even more flexibility. This function is kind of magical. It allows us to resolve a min() and a max() calculation in a single function. The syntax goes like this:

clamp(MIN, VALUE, MAX)

It’s like resolving a combination of the max() and min() functions:

max(MIN, min(VAL, MAX))

You can use it for all kind of properties that cover:  <length>, <frequency>, <angle>, <time>, <percentage>, <number>, or <integer>.

The “No-Media Query Responsive Card” demo

With all of these new-fangled CSS powers, I created a flexible responsive card without any media queries. It might be best to view this demo in a new tab, or with a 0.5x option in the embed below.

Something you want to note right away is that the HTML code for the 2 cards are exactly the same, the only difference is that the first card is within a 65% wide container, and the second one within a 35% wide container. You can also play with the dimension of your window to test its responsiveness.

The important part of the code in that demo is on these selectors:

  • .recipe is the parent flex container.
  • .pizza-box is a flex item that is the container for the card image.
  • .recipe-content is a second flex item and is the container for the card content. 

Now that we know how flex-wrap works, and how flex-basis and flex-grow  influence the element sizing, we just need to quickly explain the clamp() function because I used it for responsive font sizing in place of where we may have normally reached for fluid type.

I wanted to use calc() and custom properties to calculate font sizes based on the width of the parent container, but I couldn’t find a way, as a 100% value has a different interpretation depending on the context. I kept it for the middle value of my clamp() function, but the end result was over-engineered and didn’t wind up working as I’d hoped or expected.

/* No need, really */
font-size: clamp(1.4em, calc(.5em * 2.1vw), 2.1em);

Here’s where I landed instead:

font-size: clamp(1.4em, 2.1vw, 2.1em);

That’s what I did to make the card title’s size adjust against the screen size but, like we discussed much earlier when talking about fluid type, we won’t be able to size the text by the parent container’s width.

Instead, we’re basically saying this with that one line of CSS:

I want the font-size to equal to 2.1vw (2.1% of the viewport width), but please don’t let it go below 1.4em or above 2.1em.

This maintains the title’s prioritized importance by allowing it to stay larger than the rest of the content, while keeping it readable. And, hey, it still makes grows and shrinks on the screen size!

And let’s not forget about responsive images, The content requirements say the image is the most important piece in the bunch, so we definitely need to account for it and make sure it looks great at all screen sizes. Now, you may want to do something like this and call it a day:

max-width: 100%;
height: auto;

But that’s doesnt always result in the best rendering of an image. Instead, we have the object-fit property, which not only responds to the height and width of the image’s content-box, but allows us to crop the image and control how it stretches inside the box when used with the object-position property.

img {
  max-width: 100%;
  min-height: 100%;
  width: auto;
  height: auto;
  object-fit: cover;
  object-position: 50% 50%;
}

As you can see, that is a lot of properties to write down. It’s mandatory because of the explicit width and height properties in the HTML <img> code. If you remove the HTML part (which I don’t recommend for performance reason) you can keep the object-* properties in CSS and remove the others.

An alternative recipe for no media queries

Another technique is to use flex-grow as a unit-based growing value, with an absurdly enormous value for flex-basis. The idea is stolen straight from the Heydon Pickering’s great “Holy Albatross” demo.

The interesting part of the code is this:

/* Container */
.recipe {
  --modifier: calc(70ch - 100%);


  display: flex;
  flex-wrap: wrap;
}


/* Image dimension */
.pizza-box {
  flex-grow: 3;
  flex-shrink: 1;
  flex-basis: calc(var(--modifier) * 999);
}


/* Text content dimension */
.recipe-content {
  flex-grow: 4;
  flex-shrink: 1;
  flex-basis: calc(var(--modifier) * 999);
}

Proportional dimensions are created by flex-grow while the flex-basis dimension can be either invalid or extremely high. The value gets extremely high when calc(70ch - 100%), the value of  --modifier, reaches a positive value. When the values are extremely high each of them fills the space creating a column layout; when the values are invalid, they lay out inline.

The value of 70ch acts like the breakpoint in the recipe component (almost like a container query). Change it depending on your needs.

Let’s break down the ingredients once again

Here are the CSS ingredients we used for a media-query-less card component:

  • The clamp() function helps resolve a “preferred” vs. “minimum” vs. “maximum” value.
  • The flex-basis property with a negative value decides when the layout breaks into multiple lines.
  • The flex-grow property is used as a unit value for proportional growth.
  • The vw unit helps with responsive typography.
  • The  object-fit property provides finer responsiveness for the card image, as it allows us to alter the dimensions of the image without distorting it.

Going further with quantity queries

I’ve got another trick for you: we can adjust the layout depending on the number of items in the container. That’s not really a responsiveness brought by the dimension of a container, but more by the context where the content lays.

There is no actual media query for number of items. It’s a little CSS trick to reverse-count the number of items and apply style modifications accordingly.

The demo uses the following selector:

.container > :nth-last-child(n+3),
.container > :nth-last-child(n+3) ~ * {
  flex-direction: column;
}

Looks tricky, right? This selector allows us to apply styles from the last-child and all it’s siblings. Neat! 

Una Kravets explains this concept really well. We can translate this specific usage like this:

  • .container > :nth-last-child(n+3): The third .container element or greater from the last .container in the group.
  • .container > :nth-last-child(n+3) ~ *: The same exact thing, but selects any .container element after the last one. This helps account for any other cards we add.

Kitty Giraudel’s “Selectors Explained” tool really helps translate complex selectors into plain English, if you’d like another translation of how these selectors work.

Another way to get “quantity” containers in CSS is to use binary conditions. But the syntax is not easy and seems a bit hacky. You can reach me on Twitter if you need to talk about that — or any other tricks and tips about CSS or design. pastedGraphic.png

Is this future proof?

All the techniques I presented you here can be used today in a production environment. They’re well supported and offer opportunities for graceful degradation.

Worst case scenario? Some unsupported browser, say Internet Explorer 9, won’t change the layout based on the conditions we specify, but the content will still be readable. So, it’s supported, but might not be “optimized” for the ideal experience.

Maybe one day we will finally get see the holy grail of container queries in the wild. Hopefully the Intrinsic Web Design patterns we’ve used here resonate with you and help you build flexible and “intrinsicly-responsive” components in the meantime.

Let’s get to the “rea” reason for this post… the pizza! 🍕


Gluten free pan pizza recipe

You can pick the toppings. The important part is the dough, and here is that:

Ingredients

  • 3¼ cups (455g) gluten free flour
  • 1 tablespoon, plus 1 teaspoon (29g) brown sugar
  • 2 teaspoons of kosher salt
  • 1/2 cube of yeast
  • 2½ cups (400 ml) whole almond milk
  • 4 tablespoons of melted margarine
  • 1 tablespoon of maizena

Instructions

  1. Mix all the dry ingredients together.
  2. Add the liquids.
  3. Let it double size for 2 hours. I’d recommend putting a wet dish towel over your bowl where the dough is, and place the dish close to a hot area (but not too hot because we don’t want it to cook right this second).
  4. Put it in the pan with oil. Let it double size for approximately 1 hour.
  5. Cook in the oven at 250 degrees for 20 minutes.

Thanks Stéphanie for the recipe 😁


How to Make a Media Query-less responsive Card Component originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-to-make-a-media-query-less-card-component/feed/ 17 319736
min(), max(), and clamp() are CSS magic! https://css-tricks.com/min-max-and-clamp-are-css-magic/ https://css-tricks.com/min-max-and-clamp-are-css-magic/#comments Mon, 11 May 2020 23:34:03 +0000 https://css-tricks.com/?p=308190 Nice video from Kevin Powell. Here are some notes, thoughts, and stuff I learned while watching it. Right when they came out, I was mostly obsessed with font-size usage, but they are just functions, so they can be used anywhere …


min(), max(), and clamp() are CSS magic! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Nice video from Kevin Powell. Here are some notes, thoughts, and stuff I learned while watching it. Right when they came out, I was mostly obsessed with font-size usage, but they are just functions, so they can be used anywhere you’d use a number, like a length.

Sometimes pretty basic usage allows for tighter code, but the change to get there feels a little mind-bending. Like how to set a “max” here, you really use min().

.el {
  width: 75%;
  max-width: 600px;

  /* tighter, but the change from max to min feels weird */
  width: min(75%, 600px);
}

The min() and max() functions can take more than two values, which is cool, but hard to keep straight what is going on! It would be nice if DevTools could tell you which one it picked at any given time.

.el {
  width: min(100px, 25%, 50vh, 30ch);
}

You don’t need a calc() to do math inside!

.el {
  width: min(10vw + 10%, 100px);
}

It’s reasonable you’d want to be setting a min and max value. You can nest the functions to do this, but it’s less mind-bendy to do with clamp().

.el {
  /* Note we're always using a relative unit somewhere
     so that zooming stays effective. */
  font-size: clamp(0.9rem, 1vw + 1rem, 2.2rem);
}

Here’s the video embedded:

To Shared LinkPermalink on CSS-Tricks


min(), max(), and clamp() are CSS magic! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/min-max-and-clamp-are-css-magic/feed/ 12 308190