Tag: Media

The New CSS Media Query Range Syntax

We rely on CSS Media Queries for selecting and styling elements based on a targeted condition. That condition can be all kinds of things but typically fall into two camps: (1) the type of media that’s being used, and (2) a specific feature of the browser, device, or even the user’s environment.

So, say we want to apply certain CSS styling to a printed document:

@media print {   .element {     /* Style away! */   } }

The fact that we can apply styles at a certain viewport width has made CSS Media Queries a core ingredient of responsive web design since Ethan Marcotte coined the term. If the browser’s viewport width is a certain size, then apply a set of style rules, which allows us to design elements that respond to the size of the browser.

/* When the viewport width is at least 30em... */ @media screen and (min-width: 30em) {   .element {     /* Style away! */   } }

Notice the and in there? That’s an operator that allows us to combine statements. In that example, we combined a condition that the media type is a screen and that it’s min-width feature is set to 30em (or above). We can do the same thing to target a range of viewport sizes:

/* When the viewport width is between 30em - 80em */ @media screen and (min-width: 30em) and (max-width: 80em) {   .element {     /* Style away! */   } }

Now those styles apply to an explicit range of viewport widths rather than a single width!

But the Media Queries Level 4 specification has introduced a new syntax for targeting a range of viewport widths using common mathematical comparison operators — things like <, >, and = — that make more sense syntactically while writing less code.

Let’s dig into how that works.

New comparison operators

That last example is a good illustration of how we’ve sort of “faked” ranges by combining conditions using the and operator. The big change in the Media Queries Level 4 specification is that we have new operators that compare values rather than combining them:

  • < evaluates if a value is less than another value
  • > evaluates if a value is greater than another value
  • = evaluates if a value is equal to another value
  • <= evaluates if a value is less than or equal to another value
  • >= evaluates if a value is greater than or equal to another value

Here’s how we might’ve written a media query that applies styles if the browser is 600px wide or greater:

@media (min-width: 600px) {   .element {     /* Style away! */   } }

Here’s how it looks to write the same thing using a comparison operator:

@media (width >= 600px) {   .element {     /* Style away! */   } }

Targeting a range of viewport widths

Often when we write CSS Media Queries, we’re creating what’s called a breakpoint — a condition where the design “breaks” and a set of styles are applied to fix it. A design can have a bunch of breakpoints! And they’re usually based on the viewport being between two widths: where the breakpoint starts and where the breakpoint ends.

Here’s how we’ve done that using the and operator to combine the two breakpoint values:

/* When the browser is between 400px - 1000px */ @media (min-width: 400px) and (max-width: 1000px) {   /* etc. */ }

You start to get a good sense of how much shorter and easier it is to write a media query when we ditch the Boolean and operator in favor of the new range comparison syntax:

@media (400px <= width <= 1000px) {   /* etc. */ }

Much easier, right? And it’s clear exactly what this media query is doing.

Browser support

This improved media query syntax is still in its early days at the time of this writing and not as widely supported at the moment as the approach that combines min-width and max-width. We’re getting close, though! Safari is the only major holdout at this point, but there is an open ticket for it that you can follow.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.


Chrome Firefox IE Edge Safari
104 63 No 104 No

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
106 106 106 No

Let’s look at an example

Here’s a layout for that’s nicely suited for larger screens, like a desktop:

A desktop layout with a logo and menu up top, a large heading in white, and an image of a silhouetted person underneath the heading, followed by a footer.

This layout has base styles that are common to all breakpoints. But as the screen gets narrower, we start to apply styles that are conditionally applied at different smaller breakpoints that are ideally suited for tablets all the way down to mobile phones:

Side-by-side screenshots of the mobile and tablet layouts with their CSS Grid tracks overlaid.

To see what’s happening, here’s a how the layout responds between the two smaller breakpoints. The hidden nav list getting displayed as well as title in the main gets increased in font-size.

That change is triggered when the viewport’s changes go from matching one media’s conditions to another:

/* Base styles (any screen size) */ header {   display: flex;   justify-content: center; }  header ul {   display: none; }  .title p {   font-size: 3.75rem; }  /* When the media type is a screen with a width greater or equal to 768px */ @media screen and (width >= 768px) {   header {     justify-content: space-between;   }    header ul {     display: flex;     justify-content: space-between;     gap: 3rem;   }    .title p {     font-size: 5.75rem;   } }

We’ve combined a few of the concepts we’ve covered! We’re targeting devices with a screen media type, evaluating whether the viewport width is greater than or equal to a specific value using the new media feature range syntax, and combining the two conditions with the and operator.

Diagram of the media query syntax, detailing the media type, operator, and range media feature.

OK, so that’s great for mobile devices below 768px and for other devices equal to or greater than 768px. But what about that desktop layout… how do we get there?

As far as the layout goes:

  • The main element becomes a 12-column grid.
  • A button is displayed on the image.
  • The size of the .title element’s font increases and overlaps the image.

Assuming we’ve done our homework and determined exactly where those changes should take place, we can apply those styles when the viewport matches the width condition for that breakpoint. We’re going to say that breakpoint is at 1000px:

/* When the media type is a screen with a width greater or equal to 1000px  */ @media screen and (width >= 1000px) {   /* Becomes a 12-column grid */   main {     display: grid;     grid-template-columns: repeat(12, 1fr);     grid-template-rows: auto 250px;   }    /* Places the .title on the grid */   .title {     grid-row: 1;   }    /* Bumps up the font-size */   .title p {     font-size: 7.75rem;   }    /* Places .images on the grid */   .images {     grid-row: 1 / span 2;     align-self: end;     position: relative;   }    /* Displays the button */   .images .button {     display: block;     position: absolute;     inset-block-end: 5rem;     inset-inline-end: -1rem;   } }
Showing the CSS grid tracks for a desktop layout using a CSS media query with the new range syntax.

Have a play with it:

Why the new syntax is easier to understand

The bottom line: it’s easier to distinguish a comparison operator (e.g. width >= 320px) than it is to tell the difference between min-width and max-width using the and operator. By removing the nuance between min- and max-, we have one single width parameter to work with and the operators tell us the rest.

Beyond the visual differences of those syntaxes, they are also doing slightly different things. Using min- and max- is equivalent to using mathematical comparison operators:

  • max-width is equivalent to the <= operator (e.g. (max-width: 320px) is the same as (width <= 320px)).
  • min-width is equivalent to the >= operator (e.g. (min-width: 320px) is the same as (width >= 320px)).

Notice that neither is the equivalent of the > or < operators.

Let’s pull an example straight from the Media Queries Level 4 specification where we define different styles based on a breakpoint at 320px in the viewport width using min-width and max-width:

@media (max-width: 320px) { /* styles for viewports <= 320px */ } @media (min-width: 320px) { /* styles for viewports >= 320px */ }

Both media queries match a condition when the viewport width is equal to 320px. That’s not exactly what we want. We want either one of those conditions rather than both at the same time. To avoid that implicit changes, we might add a pixel to the query based on min-width:

@media (max-width: 320px){ /* styles for viewports <= 320px */ } @media (min-width: 321px){ /* styles for viewports >= 321px */ }

While this ensures that the two sets of styles don’t apply simultaneously when the viewport width is 320px, any viewport width that fall between 320px and 321px will result in a super small zone where none of the styles in either query are applied — a weird “flash of unstyled content” situation.

One solution is to increase the second comparison scale value (numbers after the decimal point) to 320.01px:

@media (max-width: 320px) { /* styles for viewports <= 320px */ } @media (min-width: 320.01px) { /* styles for viewports >= 320.01px */ }

But that’s getting silly and overly complicated. That’s why the new media feature range syntax is a more appropriate approach:

@media (width <= 320px) { /* styles for viewports <= 320px */ } @media (width > 320px) { /* styles for viewports > 320px */ }

Wrapping up

Phew, we covered a lot of ground on the new syntax for targeting viewport width ranges in CSS Media Queries. Now that the Media Queries Level 4 specification has introduced the syntax and it’s been adopted in Firefox and Chromium browsers, we’re getting close to being able to use the new comparison operators and combining them with other range media features besides width, like height and aspect-ratio

And that’s just one of the newer features that the Level 4 specification introduced, alongside a bunch of queries we can make based on user preferences. It doesn’t end there! Check out the Complete Guide to CSS Media Queries for a sneak peek of what might be included in Media Queries Level 5.

The New CSS Media Query Range Syntax originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.


, , ,

A Chrome Extension for Cloudinary That Helps You Pluck Out Useful Media URLs From Your Library Quickly

(This is a sponsored post.)

Cloudinary is a host for your digital assets like images and video. If you don’t already know them, you should, because you can build it into the asset management you almost certainly need to do if you run any size of website. Cloudinary helps you serve the assets as efficiently as technologically possible, meaning optimization, resizing, CDN hosting, and goes further in allowing interesting transforms on those assets.

If you already use it, unless you use it entirely through the APIs, you’ll know Cloudinary has a Media Library that gives you a UI dashboard for everything you’ve ever uploaded to Cloudinary. This is where you find your assets and open them up to play with the settings and transformations and such (e.g. blur it — then serve in the best possible format with automatic quality adjustments). You can always pop over to cloudinary.com to use this. But wouldn’t it be nice if this process was made a bit easier?

That clutch moment where you get the URL of the image you need.

There are all sorts of moments while bopping the web around doing our jobs as developers where you might need to get your fingers on an asset URL.

Gimme that URL!

Here’s a personal example: we have a little custom CMS thing for building our weekly email The CodePen Spark. It expects a URL to an image.

This is the exact kind of moment that the brand new Chrome Media Library Extension could help. Essentially it gives you a context menu you can use right in the browser to snag a URL to an asset. Right click, Insert and Asset URL.

It pops up a UI right inline (where you are on the web) of your Media Library, and you pick an image from there. Find the one you want, open it up, and you can either “edit” it to customize it to your liking, or just Insert it straight away.

Then it plops the URL right onto the site (probably an input) where you need it.

You can set up defaults to your liking, but I really like how the defaults are f_auto and q_auto which are Cloudinary classics that you’ll almost surely want. They mean “serve in the best possible format” and “optimize it intelligently”.

Sharon Yelenik introduced it on the Cloudinary blog:

Say your team creates social posts on a browser tab on an automated marketing application. To locate a media asset, you must open another tab to search for the asset within the Media Library, copy the related URL, and paste it into the app. In some cases, you even have to download an asset and then upload it into the app.

Talk about a classic example of menial, mundane, and repetitive chores!

Exactly. I like the idea of having tools to optimize workflows that should be easy. I’d also call Cloudinary a bit of a technical/developer tool, and there is an aspect to this that could be set up on anyone’s machine that would allow them to pick assets from your Media Library easily, without any access control worries.

If all this appeals to you:

Or see more at Cloudinary Labsdocumentation, and blog post.

A Chrome Extension for Cloudinary That Helps You Pluck Out Useful Media URLs From Your Library Quickly originally published on CSS-Tricks. You should get the newsletter and become a supporter.


, , , , , , , , , ,

New Age DAM APIs to Simplify Your Media Workflows

(This is a sponsored post.)

High-velocity, online businesses produce multiple digital assets like banners, images, videos, PDFs, etc., to promote their businesses online. For such businesses, Digital Asset Management (DAM) solutions are essential. These solutions help centrally store, manage, organize, search and track digital assets. Having a central repository of assets helps in the faster execution of campaigns and improves cross-functional collaboration.

But, for an organization operating at scale and dealing with millions of digital assets flowing in from multiple sources, certain parts of your asset management workflow cannot be done manually using a UI. For example, how do you upload thousands of images in the correct folders every day? Or integrate an internal CMS to add product SKU IDs as a tag on the product image in the DAM?

This is why leading DAM solutions come with APIs to allow you to integrate them into your existing workflows and get the benefits of a DAM system at scale. Let’s first understand what an API is before getting to some common examples and use cases you can solve with them.

What is an API?

API stands for Application Programming Interface. It allows two software pieces or applications to communicate using a common definition.

An analogy in the physical world is when you order a dish in a restaurant, the chef understands what you ordered and prepares it. Here, the menu with the dish’s name serves as the common language for you (one of the parties) to communicate with the chef (the other party).

Let’s look at an example of an API in an e-commerce application. To check the delivery time to your location, you enter your pin code, and in a second or two, the time appears on your mobile screen. Here, your app (one of the software) is talking to the server (the other software), asking to give delivery times for a pin code (the definition or the common language between the two software). The delivery time that is returned by the server is called an API’s “response.”

What is a DAM API?

Continuing our explanation above, DAM APIs allow you to communicate with the Digital Asset Management system using a defined language. These APIs allow you to use all or most of the features of a DAM system, but instead of doing it via the user interface in a browser, you would be able to use them from a software program.

For example, a DAM’s user interface lets you drag and drop an image to upload it. However, the same DAM system could offer an API to upload images from your user’s Android app. Here the Android app is one of the software, the DAM system itself is the other software, and the upload API communicates what and how to upload to the DAM system. Once completed, the API responds with information about the uploaded image.

What’s ImageKit? What’s its DAM offering?

ImageKit is a leading Digital Asset Management solution. It comes with standard DAM features like storage, management, AI tagging, custom metadata, and advanced search. It also has optimized asset delivery integrated into the system.

While ImageKit’s DAM system comes with a user-friendly UI, like all leading players in this space, it also offers media APIs to use all of its features programmatically.

Use cases you can solve with DAM APIs

Before jumping to the APIs, here are some ways to use a DAM system’s APIs.

  1. If you have an app or website where users can upload images or videos, or other content,you can use the DAM API to upload them directly to the DAM system.
  2. Suppose you build a product that offers integrated media storage to its users. Instead of exposing your users to the DAM system directly, you would want to integrate it into your product natively (or white-label it). You can use a combination of DAM upload APIs, list and search APIs, and get image detail APIs to build this asset library for the users of your product.
  3. Suppose your team uses an existing CMS or any other system to manage internal data. You can use the DAM as the underlying file storage and use its advanced management and search features via its APIs. Your team never has to leave their existing CMS while still leveraging all the features of the DAM system.
  4. If you require and your DAM solution supports it, you can use the real-time image and video optimization APIs to deliver the assets to your users or on different platforms. ImageKit is one such DAM that supports file delivery for any asset upload to its media library.

Common Digital Asset Management APIs

Let’s look at some of the standard APIs that most DAM systems offer. For demonstration and examples, we would be using ImageKit’s DAM APIs.

1. API for uploading a file

This is the most basic API of all — before you use the DAM system, you need to upload files to it.

ImageKit’s Upload API allows you to upload an actual file from your file system or a web URL. You can use this API on a front-end application, like a mobile app, or a back-end application, like your application server. Here is an example of uploading the image from a back-end application.

curl -X POST "https://upload.imagekit.io/api/v1/files/upload"   -u your_private_api_key:   -F 'file=@/Users/username/Desktop/my_file_name.jpg;type=image/jpg'   -F 'fileName=my_file_name.jpg'

You would get some information about the uploaded file in the API response. For example, you would usually get a unique ID for your file, which would be super valuable for subsequent APIs, along with other information like the file’s format, size, upload time, etc.

{   "fileId": "598821f949c0a938d57563bd",   "name": "my_file_name.jpg",   "url": "https://ik.imagekit.io/your_imagekit_id/images/products/file1.jpg",   "height": 300,   "width": 200,   "size": 83622,   // other information... }

2. Moving, copying, and deleting a file

After uploading a file to the DAM system, you might want to remove it or move it around to different folders. This also can be done programmatically via APIs.

For example, in ImageKit, to move a file from one folder to the other, you need to give the file’s path (sourceFilePath) and the destination folder path (destinationPath) in the API.

curl -X POST "https://api.imagekit.io/v1/files/move"  -H 'Content-Type: application/json'      -u your_private_key: -d '     {   "sourceFilePath" : "/path/to/file.jpg",   "destinationPath" : "/folder/to/move/into/" }'

3. Updating a file with manual and AI Tags

File nomenclature and creating the correct folder structure are often insufficient to organize and find content in a growing repository of digital assets.

Associating custom metadata or tags with an asset helps build another layer of organization for your content. For example, you could assign values to fields such as “Product Category” (Shoe, Shirt, Jeans, etc.), “Platform” (Facebook, Instagram, etc.), “Sale Name” (Thanksgiving, Black Friday, etc.) to the files in your DAM system, to build a more business-specific organization.

Through services like Google Cloud Vision, taking advantage of AI can help speed up asset tagging workflows and reduce errors. In addition, good DAM systems do provide APIs to associate tags with your assets.

For example, ImageKit allows you to add AI-inferred tags, using Google Cloud Vision, to your asset in the code below.

curl -X PATCH "https://api.imagekit.io/v1/files//details"     -H 'Content-Type: application/json'    -u your_private_key: -d'    {     "extensions": [     {       "name": "google-auto-tagging",       "maxTags": 5,       "minConfidence": 95     }   ] }

While the above API adds tags to an existing file, you can also do this when the file is first uploaded.

4. Searching for a file using search APIs

The most significant advantage of using a DAM is searching for the exact asset amongst thousands of them. Therefore, a good search API is necessary for any DAM system. It should allow searching on all the possible parameters associated with an asset, including custom tags and metadata that we add to create a business-specific organization for ourselves.

ImageKit provides a very flexible search API that lets you construct complex search queries to pinpoint the exact resource you need. The example below finds out all assets you created more than seven days ago with a size of more than 2MB.

curl -X GET "https://api.imagekit.io/v1/files"   -G --data-urlencode "searchQuery=createdAt >= "7d" AND size > "2mb""   -u your_private_api_key:

5. The image and video delivery API

Once your team starts managing and collaborating on the assets on the DAM, the next obvious step would be to be able to use these assets on the web, share them via their URLs, use them on your website, apps, emails, and so on.

Leading DAM solutions like ImageKit provide ready-to-use URLs for any file stored with them. ImageKit API also has in-built automatic optimizations and real-time manipulations for images and videos that ensure optimized asset delivery every time.


The above example resizes the original image to a 200×200 square thumbnail while compressing it and optimizing its format. But, of course, you can do the same using a similar URL-based API for videos too.Read more about ImageKit’s media APIs


Apart from the basic APIs explained above, all DAM solutions offer several other APIs that allow you to manage folders, get file details, control the shareability of assets, and more. The possibilities are endless for integrating these APIs to simplify and automate your existing workflows. Using a DAM solution like ImageKit, with its extensive media management APIs given here, will bring your marketing, creative, and technology teams on the same page and help them execute campaigns faster. Sign up today on ImageKit’s forever free DAM plan and start optimizing your media workflows.


, , ,

Responsive Layouts, Fewer Media Queries

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%)


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%)


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!

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.


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.

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.

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:


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.

The post Responsive Layouts, Fewer Media Queries appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , , ,

Can we have custom media queries, please?

Stefan Judis notes that there is a spec for custom media queries, but seemingly no momentum on it at the moment. That lack of movement is unfortunate, as it’s almost guaranteed front-end developers all over would start using it as soon as it’s ready. I know I would, as I liberally use custom properties now, and the DRYness of custom properties is one of the exact same benefits we’d get with custom media queries.

The syntax is:

@custom-media --narrow-window (max-width: 30em);  @media (--narrow-window) {   /* narrow window styles */ } @media (--narrow-window) and (script) {   /* special styles for when script is allowed */ }

..which I lifted from the Chrome Bug (star it to signal interest).

I’ve come around on the idea of postcss-preset-env. It used to think it was too theoretical which made me nervous. — so much CSS isn’t particularly polyfill-able without JavaScript. Plus, if anything changed with the specs, you’ve basically locked yourself into a CSS processor rather than getting future features, as that processing eventually stops processing. But all the CSS transformations in here generally seem pretty chill, deliver a lot of value (like custom media queries!), and you can pick which stage you feel most comfortable with.

Direct Link to ArticlePermalink

The post Can we have custom media queries, please? appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , ,

Detecting Media Query Support in CSS and JavaScript

You can’t just do @media (prefers-reduced-data: no-preference) alone because, as Kilian Valkhof says:

[…] that would be false if either there was no support (since the browser wouldn’t understand the media query) or if it was supported but the user wanted to preserve data.

Usually @supports is the tool for this in CSS, but that doesn’t work with @media queries. Turns out there is a solution though:

@media not all and (prefers-reduced-data), (prefers-reduced-data) {   /* ... */ }

This is a somewhat complex logic puzzle involving media query syntax and how browsers evaluate these things. It’s nice you can ultimately handle a no-support fallback situation in one expression.

Direct Link to ArticlePermalink

The post Detecting Media Query Support in CSS and JavaScript appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.


, , , ,

Media Queries in Times of @container

Max Böck took me up on my challenge to look through a codebase and see how many of the @media queries could ultimately become @container queries.

I took the bait and had a look at some of my projects – and yes, most of what I use @media for today can probably be accomplished by @container at some point. Nevertheless, I came up with a few scenarios where I think media queries will still be necessary.

Max didn’t say exactly how many would be replaced, but I got the impression it was 50/50ish.

A combination of both techniques will probably be the best way forward. @media can handle the big picture stuff, user preferences and global styles; @container will take care of all the micro-adjustments in the components themselves.

A perfect team!

I also think there will be a big difference between what we do when refactoring an existing CSS codebase to what we do when we are building from scratch. And it will be different what we do when we first get container queries to what we do years from now when new patterns have settled in. I’ve long been bullish on components being the right abstraction for front-end development. It feels like everything lately pushes us in that direction, from JavaScript frameworks and to native components, to container queries and style scoping.

Direct Link to ArticlePermalink

The post Media Queries in Times of @container appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , ,

Hexagons and Beyond: Flexible, Responsive Grid Patterns, Sans Media Queries

A little while back, Chris shared this nice hexagonal grid. And true to its name, it’s using —wait for it — CSS Grid to form that layout. It’s a neat trick! Combining grid columns, grid gaps, and creative clipping churns out the final result.

A similar thing could be accomplished with flexbox, too. But I’m here to resurrect our old friend float to create the same sort of complex and responsive layout — but with less complexity and without a single media query.

I know, it’s hard to believe. So let’s start with a working demo:

This is a fully responsive hexagon grid made without media queries, JavaScript, or a ton of hacky CSS. Resize the demo screen and see the magic. In addition to being responsive, the grid also scales. For example, we can chuck more hexagons in there by adding more divs, and control both the sizing and spacing using CSS variables.

Cool, right? And this is only one example among many grids we will build in the same manner.

Making a grid of hexagons

First, we create our hexagon shape. This task is fairly easy using clip-path. We will consider a variable S that will define the dimension of our element. Bennett Feely’s Clippy is a great online generator for clip paths.

Creating a hexagonal shape using clip-path

Each hexagon is an inline-block element. The markup can go something like this:

<div class="main">   <div class="container">     <div></div>     <div></div>     <div></div>     <!--etc. -->   </div> </div>

…and the CSS:

.main {   display: flex; /* we will talk about this later ... */   --s: 100px;  /* size  */   --m: 4px;   /* margin */ }  .container {   font-size: 0; /* disable white space between inline block element */ }  .container div {   width: var(--s);   margin: var(--m);   height: calc(var(--s) * 1.1547);   display: inline-block;   font-size: initial; /* we reset the font-size if we want to add some content */   clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%); }

Nothing complex so far. We have a main element that holds a container which, in turn, holds the hexagons. Since we are dealing with inline-block, we need to fight the common white space issue (using the font-size trick) and we consider some margin (defined with the variable M) to control the space.

Toggling the font-size of the first demo to illustrate the white space issue

Here’s the result so far:

Every other row needs some negative offset so the rows overlap rather than stack directly on top of each other. That offset will be equal to 25% of the element height (see Figure 1). We apply that offset to margin-bottom to get the following:

.container div {   width: var(--s);   margin: var(--m);   height: calc(var(--s) * 1.1547);   display: inline-block;   font-size: initial;   clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);   margin-bottom: calc(var(--m) - var(--s) * 0.2886); /* some negative margin to create overlap */ }

…and the result becomes:

Now the real trick is how we can shift the second row to get a perfect hexagon grid. We’ve already scrunched things to the point where the rows overlap each other vertically, but what we need is to push every other row toward the right so the hexagons stagger rather than overlap. Here’s where float and shape-outside come into play.

Did you wonder why we have a .main element wrapping our container and having display: flex ? That div is also a part of the trick. In a previous article, I used float and I needed that flexbox container in order to be able to use height: 100%. I will be doing the same thing here.

.container::before {   content: "";   width: calc(var(--s)/2 + var(--m));   float: left;   height: 100%; }

I am using the container::before pseudo-element to create a float element that take up all the height at the left of the grid, and that has a width equal to half a hexagon (plus its margin). We get the following result:

The yellow area is our.container::before pseudo-element.

Now, we can reach for shape-outside. Let’s take a quick refresher on what it does. Robin defines it nicely in the CSS-Tricks Almanac. MDN describes it nicely as well:

The shape-outside CSS property defines a shape—which may be non-rectangular—around which adjacent inline content should wrap. By default, inline content wraps around its margin box; shape-outside provides a way to customize this wrapping, making it possible to wrap text around complex objects rather than simple boxes.

Emphasis mine

Notice “inline content” in the definition. This explains exactly why the hexagons need to be inline-block elements. But to understand what kind of shape we need, let’s zoom into the pattern.

What’s cool about shape-inside is that it actually works with gradients. But what kind of gradient fits our situation?

If, for example, we have 10 rows of hexagons, we only need to shift means every even row. Seen differently, we need to shift every second row so we need a kind of repetition — perfect for a repeating gradient!

We’ll create a gradient with two colors:

  • A transparent one to create the “free space” while allowing the first row to stay in place (illustrated by the blue arrow above).
  • An opaque color to shift the second row to the right so the hexagons aren’t directly stacked on top of one another (illustrated by the green arrow).

Our shape-outside value will look like this:

shape-outside: repeating-linear-gradient(#0000 0 A, #000 0 B); /* #0000 = transparent */

Now, let’s find the value of A and B. B will simply be equal to the height of two rows since our logic need to repeat each two rows.

The height of two rows is equal to the height of two hexagons (including their margins), minus twice the overlap (2*Height + 4*M - 2*Height*25% = 1.5*Height + 4*M ). Or, expressed in CSS with calc():

calc(1.732 * var(--s) + 4 * var(--m))

That’s a lot! So, let’s hold all of this in a CSS custom property, F.

The value of A (defined by the blue arrow in the previous figure) needs to be at least equal to the size of one hexagon, but it can also be bigger. In order to push the second row over to the right, we need few pixel of opaque color so A can simply be equal to B - Xpx, where X is a small value.

We end up with something like this:

shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px),#000 0 var(--f));

And the following result:

shape-outside is applied to the floated element, creating a floated area with a predating linear gradient.

See that? Our repeating linear gradient’s shape is pushing every other row to the right by one half the width of a hexagon to offset the pattern.

Let’s put that all together:

.main {   display:flex;   --s: 100px;  /* size  */   --m: 4px;    /* margin */   --f: calc(var(--s) * 1.732 + 4 * var(--m) - 1px);  }  .container {   font-size: 0; /* disable white space between inline block element */ }  .container div {   width: var(--s);   margin: var(--m);   height: calc(var(--s) * 1.1547);   display: inline-block;   font-size:initial;   clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);   margin-bottom: calc(var(--m) - var(--s) * 0.2885); }  .container::before {   content: "";   width: calc(var(--s) / 2 + var(--m));   float: left;   height: 120%;    shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px), #000 0 var(--f)); }

That’s it! With no more than 15 CSS declarations, we have a responsive grid that fit nicely into all the screen sizes and we can easily adjust things by simply controling two variables.

You may have noticed that I am adding -1px to the variable F. Since we are dealing with calculation that involve decimals, the rounding may give us bad results. To avoid this we add or remove few pixels. I am also using 120% instead of 100% for the height of the floated element for similar reasons. There is no particular logic with theses values; we simply adjust them to make sure to cover most of the cases without any misaligning our shapes.

Want more shapes?

We can do more than hexagons with this approach! Let’s create a “rhombus” grid instead. Again, we start with our clip-path to create the shape:

Rhombus shape using clip-path

The code is basically the same. What’s changing are the calculations and values. Find below a table that will illustrate the changes.

Hexagon grid Rhombus grid
height calc(var(--s)*1.1547) var(--s)
clip-path polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%) polygon(50% 0, 100% 50%, 50% 100%, 0 50%)
margin-bottom calc(var(--m) - var(--s)*0.2885) calc(var(--m) - var(--s)*0.5)
--f calc(var(--s)*1.7324 + 4*var(--m)) calc(var(--s) + 4*var(--m))

And we’re done! A mere four changes to our code gets us a completely new grid but with a different shape.

Just how flexible is this?

We saw how we were able to make the hexagon and rhombus grids using the exact same code structure, but different variables. Let me blow your mind with another idea: What about making that calculation a variable so that we can easily switch between different grids without changing the code? We can certainly do that!

We’ll use an octagonal shape because it’s more of a generic shape from that we can use to create other shapes (a hexagon, a rhombus, a rectangle, etc.) simply by changing a few values.

The points on this octagon shape are defined in the clip-path property.

Our octagon is defined with four variables:

  • S: the width.
  • R: the ratio that will help us defines the height based on the width.
  • hc and vc : both of these will control our clip-path values and the shape we want to get. hc will be based on the width while vc on the height

I know it looks hefty, but the clip-path is defined using eight points (like shown in the figure). Adding some CSS variables, we get this:

clip-path: polygon(    var(--hc) 0, calc(100% - var(--hc)) 0, /* 2 points at the top */    100% var(--vc),100% calc(100% - var(--vc)), /* 2 points at the right */    calc(100% - var(--hc)) 100%, var(--hc) 100%, /* 2 points at the bottom */    0 calc(100% - var(--vc)),0 var(--vc) /* 2 points at the left */ );

This is what we’re aiming for:

Let’s zoom in to identify the different values:

The overlap between each row (illustrated by the red arrow) can be expressed using the vc variable which gives us a margin-bottom equal to M - vc (where M is our margin).

In addition to the margin we applied between our element, we also need an additional horizontal margin (illustrated by the yellow arrow) equal to S - 2*hc. Let’s define another variable for the horizontal margin (MH) that is equal to M + (S - 2*hc)/2.

The height of two rows is equal to twice the size of a shape (plus the margin), minus twice the overlap, or 2*(S + 2*M) - 2*vc.

Let’s update our table of values to see how we’re calculating things between the different grids:

Hexagon grid Rhombus grid Octagon grid
height calc(var(--s)*1.1547) var(--s) calc(var(--s)*var(--r)))
clip-path polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%) polygon(50% 0, 100% 50%, 50% 100%, 0 50%) polygon(var(--hc) 0, calc(100% - var(--hc)) 0,100% var(--vc),100% calc(100% - var(--vc)), calc(100% - var(--hc)) 100%,var(--hc) 100%,0 calc(100% - var(--vc)),0 var(--vc))
--mh calc(var(--m) + (var(--s) - 2*var(--hc))/2)
margin var(--m) var(--m) var(--m) var(--mh)
margin-bottom calc(var(--m) - var(--s)*0.2885) calc(var(--m) - var(--s)*0.5) calc(var(--m) - var(--vc))
--f calc(var(--s)*1.7324 + 4*var(--m)) calc(var(--s) + 4*var(--m)) calc(2*var(--s) + 4*var(--m) - 2*var(--vc))

Alright, let’s update our CSS with those adjustments:

.main {   display: flex;   --s: 100px;  /* size  */   --r: 1; /* ratio */    /* clip-path parameter */   --hc: 20px;    --vc: 30px;    --m: 4px; /* vertical margin */   --mh: calc(var(--m) + (var(--s) - 2*var(--hc))/2); /* horizontal margin */   --f: calc(2*var(--s) + 4*var(--m) - 2*var(--vc) - 2px); }  .container {   font-size: 0; /* disable white space between inline block element */ }  .container div {   width: var(--s);   margin: var(--m) var(--mh);   height: calc(var(--s)*var(--r));   display: inline-block;   font-size: initial;   clip-path: polygon( ... );   margin-bottom: calc(var(--m) - var(--vc)); }  .container::before {   content: "";   width: calc(var(--s)/2 + var(--mh));   float: left;   height: 120%;    shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px),#000 0 var(--f)); }

As we can see, the code structure is the same. We simply added more variable to control the shape and extend the margin property.

And below a working example. Adjust the different variables to control the shape while having a fully responsive grid:

An interactive demo, you say? You bet!

To make things easier, I am expressing the vc and hc as percetange of the width and height so we can easily scale our elements without breaking the clip-path

From the above we can easily get the initial hexagonal grid:

The rhombus grid:

And yet another hexagon grid:

A masonry-like grid:

And a checkerboard while we are at it:

A lot of possibilities to create a responsive grid with any kind of shape! All we have to do is adjust few variables.

Fixing the alignment

Let’s try to control the alignment of our shapes. Since we are dealing with inline-block elements, we’re dealing with default left alignment and some empty space at the end, depending on viewport width.

Notice that we alternate between two kind of grids based on the screen width:

Grid #1: A different number of items per row (NN-1,NN-1, etc.)
Grid #2: The same number of items per row (NNNN, etc.)

It would be good to always have one of the grid all the time (either #1 or #2) and center everything so that the free space is equally divided on both sides.

In order to get the first grid in the figure above, the container width needs to be a multiplier of the size of one shape, plus its margin, or N*(S + 2*MH), where N is an integer value.

This may sound impossible with CSS, but it’s indeed possible. I made it using CSS grid:

.main {   display: grid;   grid-template-columns: repeat(auto-fit, calc(var(--s) + 2*var(--mh)));   justify-content: center; }  .container {   grid-column: 1/-1; }

.main is now a grid container. Using grid-template-columns, I define the column width (as previously explained) and use the auto-fit value to get as many columns as possible into the available space. Then, the .container spans all of the grid columns using 1/-1 — which means that the width of our container will be a mutiplier of one column size.

All it takes to center things is justify-content: center.

Yes, CSS is magic!

Resize the demo and notice that not only do we have the first grid from the figure, but everything is perfectly centered as well.

But wait, we removed display: flex and swapped in display: grid… so how is the percentage-based height of the float still working? I had said that using a flex container was the key for that, no?

Well, turns out CSS grid sports that feature too. From the specification:

Once the size of each grid area is thus established, the grid items are laid out into their respective containing blocks. The grid area’s width and height are considered definite for this purpose.

Note: Since formulas calculated using only definite sizes, such as the stretch fit formula, are also definite, the size of a grid item which is stretched is also considered definite.

A grid item has a stretch alignment by default, so its height is definite, meaning using a percentage as a height inside it is perfectly valid.

Let’s say we instead want the second grid in the figure — we simply add an extra column with a width equal to half the width of the other columns:

.main {   display: grid;   grid-template-columns: repeat(auto-fit,calc(var(--s) + 2*var(--mh))) calc(var(--s)/2 + var(--mh));   justify-content :center; }

Now, in addition to a fully responsive grid that is flexible enough to take custom shapes, everything is perfectly centred!

Fighting the overflow

The use of negative margin-bottom on the last items and the float element pushing our items will create some unwanted overflow that may affect the content placed after our grid.

If you resize the demo, you will notice an overflow equal to the negative offset and sometimes it’s bigger. The fix is to add some padding-bottom to our container. I will make the padding equal to the height of one shape:

I have to admit that there isn’t a perfect solution to fight that overflow and to control the space below our grid. That space depends on a lot of factors and we may have to use a different padding value for each case. The safest solution is to consider a big value that covers most of the cases.

Wait, one more: a pyramidal grid

Let’s take everything we’ve learned and build another amazing grid. This time, we’ll transform the grid we just made into a pyramidal one.

Consider that, unlike the grid we’ve made so far, the number of elements is important especially for the responsive part. It’s required to know the number of elements and more precesily the number of rows.

Different pyramidal grid based on the number of items

It doesn’t mean we need a bunch of hardcoded values; rather we use an extra variable to adjust things based on the number of rows.

The logic is based on the number of rows because different numbers of elements may give us the same number of rows. For example, there are five rows when we have between 11 and 15 elements, even if the last row is not fully occupied. Having between 16 and 21 elements gives us six rows, and so on. The number of rows is our new variable.

Before digging into the geometry and the math here is a working demo:

Notice that most of the code is the same as what we’ve done in the previous examples. So let’s focus on the new properties that we’ve added:

.main {   --nr: 5;  /* number of rows */ }  .container {   max-width: calc(var(--nr)*(var(--s) + 2*var(--mh)));   margin: 0 auto; }  .container::before , .container i {   content: "";   width: calc(50% - var(--mh) - var(--s)/2);   float: left;   height: calc(var(--f)*(var(--nr) - 1)/2);   shape-outside: linear-gradient(to bottom right, #000 50%, #0000 0); }  .container i {   float:right;   shape-outside: linear-gradient(to bottom left, #000 50%, #0000 0); }

NR is our variable for the number of rows. The width of the container needs to be equal to the last row of the pyramid to make sure it hold all the elements. If you check the previous figure, you’ll see that the number of the items contained in the last row is simply equal to the number of rows, which means the formula is: NR* (S + 2*MH).

You may have also noticed that we also added an <i> element in there. We did that because we need two floating elements where we will apply shape-outside.

To understand why we need two floating elements let’s see what is done behind the scenes:

A pyramid grid of octagon shapes. The octagons alternate between green and red. There are 5 rows of octagons.
Pyramidal grid

The blue elements are our floating elements. Each one is having a width equal to half the container size, minus half a shape size, plus margin. The height is equal to four rows in our case, and to NR - 1 in a more generic case. Earlier, we defined the height of two rows, F, so the height of one row is F/2. That’s how we landed at height: calc(var(--f)*(var(--nr) - 1)/2.

Now that we have the size of our elements, we need to apply a gradient to our shape-outside.

The purple coloration in the figure above is the restricted area for our elements (it need to be an opaque color). The remaining area is the free space where the elements can flow (it need to be a transparent color). This can be done using a diagonal gradient:

shape-outside: linear-gradient(to bottom right, #000 50%, #0000 0); 

We simply change right with left for the other floated element. You have probably noticed that this is not responsive. In fact, go ahead and adjust the viewport width of the demo and see just how unresponsive this is.

We have a couple of options to get responsive:

  1. We can fall back to the first grid when the container width is smaller than the viewport width. It’s a bit tricky to code, but it allows us to preserve the same size for our elements.
  2. We can reduce the size of our elements in order to keep the pyramidal grid. This is easier to code using the percentage-based value trick, but that could result in super tiny elements on smaller screen sizes.

Let’s go with the first solution. We like a good challenge, right?

To get the pyramidal grid, we needed two floated element. The initial grid needed just one floated element. Luckily, our structure allows us to have three floated elements without needing to add more elements to the markup, thanks to pseudo-elements. We will use container::before, i::before, i::after:

/* Same as before... */  /* The initial grid */ .container::before {   content: "";   width: calc(var(--s)/2 + var(--mh));   float: left;   height: 120%;    shape-outside: repeating-linear-gradient(#0000 0 calc(var(--f) - 3px),#000 0 var(--f)); }  /* The pyramidal grid */ .container i::before , .container i::after {   content: "";   width: calc(50% - var(--mh) - var(--s)/2);   float: left;   height: calc(var(--f)*(var(--nr) - 1)/2);   shape-outside: linear-gradient(to bottom right,#000 50%,#0000 0); }  .container i::after {   float:right;   shape-outside: linear-gradient(to bottom left,#000 50%,#0000 0); }

Now we need a trick that lets us use either the first floated element or the other two, but not all of them at the same time. This condition should be based on the width of our container:

  • If the container width is bigger than the width of the last row, we can have our pyramid and use the floated elements inside of <i>.
  • If the container width is smaller than the width of the last row, we switch to the other grid and use the first floated element.

We can use clamp() for this! It’s sort of like a conditional function that sets a minimum and maximum range and, within that range, we provide it an “ideal” value to use between those points. This way, we can “switch” between grids using our formulas as clamped values, and still avoid using media queries.

Our code will look like this:

.main {   /* the other variables won't change*/   --lw: calc(var(--nr)*(var(--s) + 2*var(--mh))); /* width of last row */ }  .container {   max-width: var(--lw); }  /* The initial grid */ .container::before {   width: clamp(0px, (var(--lw) - 100%)*1000, calc(var(--s)/2 + var(--mh))); }  /* The pyramidal grid */ .container i::before, .container i::after {   width: clamp(0px, (100% - var(--lw) + 1px)*1000, calc(50% - var(--mh) - var(--s)/2)); }

On larger screens, the width of the container (LW) is now equal to its max-width, so 100% == LW. That means that the width of .container::before is equal to 0px (and results in this floated element becoming disabled).

For the other floating elements, we clamp the width:

width: clamp(0px, (100% - var(--lw) + 1px)*1000, calc(50% - var(--mh) - var(--s)/2));

…where the middle value ((100% - LW + 1px)*1000) is equal to (0 + 1px)*1000 = 1000px (an intentionally large, but arbitrary value). It gets clamped to calc(50% - var(--mh) - var(--s)/2). In other words, these floated elements are enabled with the correct width (the one we defined previously)

Voilà! we have a pyramidal shape on large screen.

Now, when the container width get smaller, LW is going to be greater than 100%. So, (LW - 100%) will be positive. Multiplied by a big value, it’s clamped to calc(var(--s)/2 + var(--mh)), which enables the first floated element. For the other float elements, (100% - LW + 1px) resolves to a negative value and is clamped to 0px, which disables the float elements.

Resize the below demo and see how we switch between both grids

Let’s try adding more elements:

See that? Things are scaling perfectly. Let’s toss more elements at it just for kicks:

Still great. Notice that the last row isn’t even full. Just shows that this approach covers a bunch of cases. We can also combine this with the CSS grid alignment trick we used earlier:

Do you think “float” is such a bad thing now?

Want invert the pyramid?

Like illustrated with the above figure, two changes to the previous code can invert our pyramid:

  • I change the direction of the gradient from to bottom left|right to to top left|right,
  • I add a margin-top equal to the height of one row.

And, hey, we can swap between both pyramid easily:

Isn’t this beautiful? We have a responsive pyramidal grid with custom shapes that we can easily invert and that fallback to another responsive grid on small screen while everything is perfectly centred. All this without a single media query or JavaScript, but instead using the often overlooked float property.

You will probably notice some missalignment in some particular cases. Yes, it’s again some rounding issue related to the calculation we are doing and the fact that we are trying to make this generic with the interactive demos. To rectify this, we simply adjust few values manually (epsecially the percentage of the gradient) until we get back a perfect alignment.

That’s a float wrap!

There we have it: combining float with shape-outside can help us make complex, flexible and responsive layouts — long live float!

The article ends here but this is only the beginning. I provided you with the layout and now you can easily put any content inside the divs, apply a background, shadows, animations, etc.

The post Hexagons and Beyond: Flexible, Responsive Grid Patterns, Sans Media Queries appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , , , , ,

Auto-Generated Social Media Images

I’ve been thinking about social media images again. You know, the images that (can) show up when you share a link in places like Twitter, Facebook, or iMessage. You’re essentially leaving money on the table without them, because they turn a regular post with a little ol’ link in it into a post with a big honkin’ attention grabbin’ image on it, with a massive clickable area. Of any image on a website, the social media image might be the #1 most viewed, most remembered, most network-requested image on the site.

It’s essentially this bit of HTML that makes them happen:

<meta property="og:image" content="/images/social-media-image.jpg" />

But make sure to read up on it as there are a bunch other other HTML tags to get right.

I think I’m thinking about it again because GitHub seems to have new social media cards. These are new, right?

Those GitHub social media images are clearly programmatically generated. Check out an example URL.


While I think you can get a lot of bang out of a totally hand-crafted bespoke-designed social media image, that’s not practical for sites with lots of pages: blogs, eCommerce… you know what I mean. The trick for sites like that is to automate their creation via templating somehow. I’ve mentioned other people’s takes on this in the past, but let’s recap:

You know what all those have in common? Puppeteer.

Puppeteer is a way to spin up and control a headless copy of Chrome. It has this incredibly useful feature of being able to take a screenshot of the browser window: await page.screenshot({path: 'screenshot.png'});. That’s how our coding fonts website does the screenshots. The screenshotting idea is what gets people’s minds going. Why not design a social media template in HTML and CSS, then ask Puppeteer to screenshot it, and use that as the social media image?

I love this idea, but it means having access to a Node server (Puppeteer runs on Node) that is either running all the time, or that you can hit as a serverless function. So it’s no wonder that this idea has resonated with the Jamstack crowd who are already used to doing things like running build processes and leveraging serverless functions.

I think the idea of “hosting” the serverless function at a URL — and passing it the dynamic values of what to include in the screenshot via URL parameter is also clever.

The SVG route

I kinda dig the idea of using SVG as the thing that you template for social media images, partially because it has such fixed coordinates to design inside of, which matches my mental model of making the exact dimensions you need to design social media images. I like how SVG is so composable.

George Francis blogged “Create Your Own Generative SVG Social Images” which is a wonderful example of all this coming together nicely, with a touch of randomization and whimsy. I like the contenteditable trick as well, making it a useful tool for one-off screenshotting.

I’ve dabbled in dynamic SVG creation as well: check out this conference page on our Conferences site.

Unfortunately, SVG isn’t a supported image format for social media images. Here’s Twitter specifically:

URL of image to use in the card. Images must be less than 5MB in size. JPG, PNG, WEBP and GIF formats are supported. Only the first frame of an animated GIF will be used. SVG is not supported.

Twitter docs

Still, composing/templating in SVG can be cool. You convert it to another format for final usage. Once you have an SVG, the conversion from SVG to PNG is almost trivially easy. In my case, I used svg2png and a very tiny Gulp task that runs during the build process.

What about WordPress?

I don’t have a build process for my WordPress site — at least not one that runs every time I publish or update a post. But WordPress would benefit the most (in my world) from dynamic social media images.

It’s not that I don’t have them now. Jetpack goes a long way in making this work nicely. It makes the “featured image” of the post the social media image, allows me to preview it, and auto-posts to social networks. Here’s a video I did on that. That’s gonna get me to a place where the featured images are attached and showing nicely.

But it doesn’t automate their creation. Sometimes a bespoke graphic alone is the way to go (the one above might be a good example of that), but perhaps more often a nicely templated card is the way to go.

Fortunately I caught wind of Social Image Generator for WordPress from Daniel Post. Look how fancy:

This is exactly what WordPress needs!

Daniel himself helped me create a custom template just for CSS-Tricks. I had big dreams of having a bunch of templates to choose from that incorporate the title, author, chosen quotes, featured images, and other things. So far, we’ve settled on just two, a template with the title and author, and a template with a featured image, title, and author. The images are created from that metadata on the fly:

So meta.

This ain’t Puppeteer. This ain’t even the PhantomJS powered svgtopng. This is PHP generated images! And not even ImageMagick, but straight up GD, the thing built right into PHP. So these images are not created in any kind of syntax that would likely feel comfortable to a front-end developer. You’re probably better off using one of the templates, but if you wanna see how my custom one was coded (by Daniel), lemme know and I can post the code somewhere public.

Pretty cool result, right?


I get why it had to be built this way: it’s using technology that will work literally anywhere WordPress can run. That’s very much in the WordPress spirit. But it does make me wish creating the templates could be done in a more modern way. Like wouldn’t it be cool if the template for your social media images was just like social-image.php at the root of the theme like any other template file? And you template and design that page with all the normal WordPress APIs? Like an ACF Block almost? And it gets screenshot and used? I’ll answer for you: Yes, that would be cool.

The post Auto-Generated Social Media Images appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , ,

Deliver Enhanced Media Experiences With Google’s Core Web Vitals

Hello! Satarupa Chatterjee from Cloudinary. There is a big change coming from Google in May 2021 having to do with their Core Web Vitals (CWVs). It’s worth paying attention here, as this is going to be a SEO factor.

I recently spoke with Tamas Piros about CWVs. The May 2021 update will factor in CWVs, along with other factors like mobile-friendliness and safe browsing, to generate a set of benchmarks for search rankings. Doubtless, the CWVs will directly affect traffic for websites and apps alike. Tamas is a developer-experience engineer at Cloudinary, a media-optimization expert, and a Google developer-expert in web technologies and web performance.

Here’s a written version of the video above, where the questions (Qs) are me, Satarupa, asking and Tamas answering (As).

Q: How did Google arrive at the three Core Web Vitals and their values?

A: As a dominant force in the search space, Google has researched in depth what constitutes a superb user experience, arriving at three important factors, which the company calls, collectively, the Core Web Vitals.

Before explaining them, I’d like to recommend an informative article, published last May on the Chromium Blog, titled The Science Behind Web Vitals. At the bottom of the piece are links to papers on the research that led to the guidelines for accurately evaluating user experiences.

Now back to the three Core Web Vitals. The first one affects page-load speed, which Google calls Largest Contentful Paint (LCP) with a recommendation of 2.5 seconds or less for the largest element on a page to load.

The second metric is First Input Delay (FID), which is a delta between a user trying to interact with a page, and the browser effectively executing that action. Google recommends 100 milliseconds or less. 

The third and last metric is Cumulative Layout Shift (CLS), which measures how stable a site is while it’s loading or while you’re interacting with it. In other words it is a measurement of individual layout shifts as well as unexpected layout shifts that happen during the lifespan of a page. The calculation involves impact and distance fractions which are multiplied together to give a final value. Google advocates this value to be 0.1 or less.

Q: How do the Core Web Vitals affect e-commerce?

A: Behind the ranking of Google search results are many factors, such as whether you use HTTPS and how you structure your content. Let’s not forget that relevant and well-presented content is as important as excellent page performance. The difference that core web vitals will make cannot be overstated. Google returns multiple suggestions for every search, however remember that good relevance is going to take priority. In other words good page experience will not override having great relevant content For example, if you search for Cloudinary, Google will likely show the Cloudinary site at the top of the results page. Page experience will become relevant when there are multiple available results, for a more generic search such as ‘best sports car’. In this case Google establishes that ranking based on the page’s user experience, too, which is determined by the Core Web Vitals.

Q: What about the other web vitals, such as the Lighthouse metrics? Do they still matter?

A: Businesses should focus primarily on meeting or staying below the threshold of the Core Web Vitals. However, they must also keep in mind that their page load times could be affected by other metrics, such as the length of time the first purchase takes and the first contentful paint.

For example, to find out what contributes to a bad First Input Delay—the FID, check the total blocking time and time to interact. Those are also vitals, just not part of the Core Web Vitals. You can also customize metrics with the many robust APIs from Google.. Such metrics could prove to be invaluable in helping you identify and resolve performance issues.

Q: Let’s talk about the Largest Contentful Paint metric, called LCP. Typically, the heaviest element on a webpage or in an app is an image. How would you reduce LCP and keep it below the Google threshold of 2.5 seconds?

A: What’s important to remember with regards to LCP is that we are talking about the largest piece of content that gets loaded on a page, and that is visible in the viewport (that is, it’s visible above the fold). Due to popular UX design patterns it’s likely that the largest, visible element is a hero image.

Google watches for <img> elements as well as <image> elements inside an SVG element. Video elements are considered too but only if they contain a poster attribute. Also of importance to Google are block-level elements, such as text-related ones like <h1>, <h2>, etc., and <span>.

All that means that you must load the largest piece of content as fast as possible. If your LCP is a hero image, be sure to optimize it—but without degrading the visual effects. Check out Cloudinary’s myriad effective and intuitive options for optimization. If you can strike a good balance between the file size and the visual fidelity of your image, your LCP will shine. 

Q: Suppose it’s now May 2021. What’s the likely effect of Google’s new criteria for search rankings for an e-commerce business that has surpassed the thresholds of all three or a couple of the Core Web Vitals?

A: According to Google, sites that meet the thresholds of the Core Web Vitals enjoy a 24-percent lower abandonment rate. The more you adhere to Google’s guidelines, the more engaging your site or app becomes and the faster your sales will grow. Needless to say, an appealing user experience attracts visitors and retains them, winning you an edge over the competition. Of course bear in mind the other search optimization guidelines set out by Google.

Again, be sure to optimize images, especially the most sizable one in the viewport, so that they load as fast as possible.

Q:  It sounds like e-commerce businesses should immediately start exploring ways to meet or better the vitals’ limits. Before we wrap up, what does the future look like for Core Web Vitals?

A: Late last year, Google held a conference and there were multiple talks touching upon this exact subject. All major changes will go into effect on a per-year basis, and Google has committed to announcing them well in advance.

Behind the scenes, Google is constantly collecting data from the field and checking them against user expectations. The first contentful paint, which I mentioned before, is under consideration as another Core Web Vital. Also, Google is thinking about reducing the yardstick for the First Input Delay metric—the FID, remember?—from 100 milliseconds to 75 or even 50.

Beyond that, Google has received a lot of feedback about some of the Core Web Vitals not working well for single-page apps. That’s because those apps are loaded only once. Even if they score an ideal Cumulative Layout Shift—that’s CLS—as you click around the page, things might move around and bring down the score. Down the road, Google might modify CLS to better accommodate single-page apps. 

Also on Google’s radar screen are metrics for security, privacy, and accessibility. Google promises to fine-tune the current metrics and launch new ones more frequently than major releases, including the introduction of new Core Web Vital metrics. 

So, change is the only constant here. I see a bright future for the vitals and have no doubt that we’re in good hands. Remember that Google vigilantly collects real user data as analytics to help figure out the appropriate standards. As long as you keep up with the developments and ensure that your site or app comply with the rules, you’ll get all greens throughout the scoreboard. That’s a great spot to be in.

Cloudinary offers myriad resources on media experience (MX), notably the MX Matters podcast, which encompasses experts’ take on the trends in today’s visual economy along with bulletins on new products and enhancements. Do check them out.

The post Deliver Enhanced Media Experiences With Google’s Core Web Vitals appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.


, , , , , ,