Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Tue, 04 Nov 2025 22:04:12 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 Affinity, Free https://frontendmasters.com/blog/affinity-free/ https://frontendmasters.com/blog/affinity-free/#comments Tue, 04 Nov 2025 22:04:11 +0000 https://frontendmasters.com/blog/?p=7695 When people complain about Photoshop or other various Adobe products and the subscription model they require (The Onion had a good one), people tend to reply with two options:

  1. Use GIMP (free, open-source)
  2. Use Affinity (was ~$50, but a one-time cost)

But now, Affinity is free (and all the varieties combined into one app). You can thank the Canva acquisition for that. You might think that would hurt GIMP usage, but I suspect it won’t as GIMP runs on Linux (which neither Adobe or Affinity do) and it feels… Linuxy. I can’t imagine Adobe is happy about it. Canva says “there is absolutely no catch whatsoever”, and also that their mission is to “build one of the world’s most valuable companies” so, ya know, you make the call.

]]>
https://frontendmasters.com/blog/affinity-free/feed/ 1 7695
The Two Button Problem https://frontendmasters.com/blog/the-two-button-problem/ https://frontendmasters.com/blog/the-two-button-problem/#comments Tue, 21 Oct 2025 23:16:01 +0000 https://frontendmasters.com/blog/?p=7422 I see this UI/UX issue all the time: there are two buttons, and it’s not clear which one of them is in the active state. Here’s an example from my TV:

Which one of those two buttons above is active? Like if I press the enter/select button on my remote, am I selecting “Try it free” or “Sign in”? It’s entirely unclear to me based on the design. Those two design styles are ambiguous. Just two random selections from today’s trends of button design.

If I press the up (or down) arrows on my remote, the styles of the buttons just reverses, so even interacting with the interface doesn’t inform the choice.

This is a problem that can be solved at multiple levels. If the buttons are toggles that affect on-page content, the accessibility angle is partially solved by the aria-selected attribute, for example. It’s also slightly less of an issue on devices with a cursor, as you likely just click on the one that you want. This is mostly a problem with remote control and keyboard usage where the active state is unclear or ambiguous.

I call it the “two button” problem because if there were more than two buttons, the one that is styled differently is probably the one that is active. We could use our grade school brains to figure out which button is active.

(via)

Ideally, though, we don’t have to think very hard. It should be obvious which one is active.

Again, the problem:

The most obvious solution here is to make both button styles the same, but be additive when one of them is the active button.

I feel like it’s very clear now that “Try it free” is the selected button now. Even if it’s not to a user immediately. If they tab/arrow/whatever to the other button, that outline design will move to it and it will become clear then.

You could also, ya know, literally point to it:

Perhaps you could resort to more “extreme” design styles like this when there is prove-ably no mouse/cursor involved, like:

@media (hover: none) and (pointer: coarse) {
  button:active {
    /* big obvious arrow styles */
  }
}

We’ve got a recent post on @media queries that goes into lots of situations like this!

This “two button” problem also can come up in the design pattern of “toggles”. Take something like this:

A “pill” toggle design pattern.

Which one of those buttons is currently active? The up arrow? The down arrow? Neither? It’s impossible to tell by look alone.

Sometimes in this case the “active” button is “grayed out”:

The implication here is that the up arrow is the “active” one, so you don’t need to press it again as it won’t do anything. Only the non-active button is pressable. I feel like this is okay-ish as a pattern, but it’s not my favorite as the active state is less prominent instead of more prominent almost indicating it’s disabled for some other reason or doesn’t matter.

This kind of thing makes me almost miss the days of skeuomorphism where digital interfaces were designed to try to mimic real world surfaces and objects. We don’t have to go full leather-coated buttons though, we can just make the active button appear pressed through shadows and flattening.

This situation differs from the TV interface issue above in that this “active” button is indicating the button has already been pressed, not that it’s the button that will be pressed. So you’d need a style for that state as well.

Maybe these aren’t the most amazing examples in the world, but I hope I’ve got you thinking about the two-button problem. When there are only two buttons, you can’t just pick two arbitrary different styles, as that doesn’t help people understand which of the two are active. You need to think about it a little deeper and get those styles in a place where it’s obvious.

]]>
https://frontendmasters.com/blog/the-two-button-problem/feed/ 5 7422
Very Early Playing with random() in CSS https://frontendmasters.com/blog/very-early-playing-with-random-in-css/ https://frontendmasters.com/blog/very-early-playing-with-random-in-css/#comments Mon, 25 Aug 2025 21:59:02 +0000 https://frontendmasters.com/blog/?p=6931 WebKit/Safari has rolled out a preview version of random() in CSS.

Random functions in programming languages are amazing. You can use them to generate variations, to make things feel spontaneous and fresh. Until now there was no way to create a random number in CSS. Now, the random() function is on its way.

Upon first play, it’s great!

This is only in Safari Technical Preview right now. I’ll post videos below so you can see it, and link to live demos.

CSS processors like Sass have been able to do this for ages, but it’s not nearly as nice in that context.

  1. The random() numbers in Sass are only calculated at compile time. So they are only random at the time. Refreshing the page doesn’t mean newly random values.
  2. Random numbers are usually paired with a loop. So if you want 1,000 randomly placed elements, you need 1,000 :nth-child() selectors with a randomly generated value in each, meaning bulky CSS.

With random() in vanilla CSS, no such loops are needed and making the code quite simple and satisfying.

I found a 12-year-old Sass demo of mine playing with random() that is like this:

This compiled to over 200 lines of CSS.

But now it’s just like this:

Demo

Much of the magic, to me, is how each matching element gets its own random values. So if you had three things like this:

<div class="thing"></div>
<div class="thing"></div>
<div class="thing"></div>

Then this simple CSS could make them all quite different:

.thing {
  position: absolute;
  background: red;
  width: 100px;
  height: 100px;
  
  top: random(10px, 200px);
  left: random(100px, 400px);
  
  background: rgb(
    random(0, 255),
    random(0, 255),
    random(0, 255)
  )
}
Demo

The blog post doesn’t mention “unitless” numbers like I’ve used above for the color, but they work fine. If you’re using units, they need to be the same across all parameters.

The “starfield” demo in the blog post is pretty darn compelling!

Demo

I found another old demo where I used a bit of randomized animation-delay where in the SCSS syntax I did it like this:

animation-delay: (random(10) / 40) + s;

Notice I had to append the “s” character at the end to get units there. Now in vanilla CSS you just declare the range with the units on it, like:

animation-delay: calc(random(0s, 10s) / 40);

And it works great!


The feature does have a spec, and I’m pleased that it has covered many things that I hadn’t considered before but are clearly good ideas. The blog post covers this nicely, but allow me to re-iterate:

.random-rect {
  width: random(100px, 200px);
  height: random(100px, 200px);
}

Both the width and height will be different random values. But if you want them to be the same random value, you can set a custom ident value that will “cache” that value for one element:

.random-square {
  width: random(--foo, 100px, 200px);
  height: random(--foo, 100px, 200px);
}

Nice!

But if you had 20 of these elements, how could you make sure all had the same random values? Well there is a special keyword for that, ensuring all matched elements share the same random values:

.shared-random-rect {
  width: random(element-shared, 100px, 200px);
  height: random(element-shared, 100px, 200px);
}

But in that case, all matched elements would share the same random values, but the width and height wouldn’t be equal. So you’d do both to make sure it’s all equal:

.shared-random-squares {
  width: random(--foo element-shared, 100px, 200px);
  height: random(--foo element-shared, 100px, 200px);
}

That’s all very nicely considered, I think.


Ranges are also handled with a final parameter:

top: random(10px, 100px, 20px);
transition-delay: random(0s, 5s, 1s);

The top value above can only be: 10px, 30px, 50px, 70px, or 90px.

The transition-delay value above can only be: 0s, 1s, 2s, 3s, 4s, or 5s.

Otherwise you can get decimal values of either which might be more random than you want. Even 1px for random pixel values as an increment seems to be suggested.

(Note the WebKit blog has a code sample with by 20deg in it, which I think is a typo as that doesn’t work for me.)


I didn’t have a chance to try it yet — but doesn’t it make you wanna force a re-render and see if it will work with document.startViewTransition??

]]>
https://frontendmasters.com/blog/very-early-playing-with-random-in-css/feed/ 4 6931
Web Design: What is the web capable of that is hard to express in design software? https://frontendmasters.com/blog/web-design-what-is-the-web-capable-of-that-is-hard-to-express-in-design-software/ https://frontendmasters.com/blog/web-design-what-is-the-web-capable-of-that-is-hard-to-express-in-design-software/#comments Mon, 18 Aug 2025 18:15:30 +0000 https://frontendmasters.com/blog/?p=6030 Designs aren’t websites, they are pictures of websites.

I heard someone say that once while lamenting the state of design software for web design.

At some point, I think all web designers circle around to the thought that if design software was only more like the web itself that it would be better for it. We would gain efficiency in that there may not need be much translation at all between design and the finished product. Time and quality suffer during the translation required now.

We could look at all the different things tried to bridge this gap. There are many — all of varying interest. I believe it is safe to say that none of them have “won”. Tools that replicate the features of the web faithfully have trouble with complexity and finding an audience that wants that. Even the prevailing web design tool today, Figma, runs on the web but the designs you create there are not really of the web. You’re drawing rectangles on a <canvas> there, not crafting <div>s. And while some of those abstractions in Figma are designed to replicate web features, they are that: a replication, not reality.

But instead of looking at takes on design tool abstraction, I’d rather enumerate many of the things that make design tools that replicate the web difficult.

Staring, naturally, with this most basic of web design concepts.

Link & Button States

Most sites have a style for links and buttons, and design software has no problem accommodating that. But those styles change in different states. What happens when you hover them? Click them? Tab to focus them? Those can (and should) have different designs, but static design software doesn’t always accommodate that very well, leaving you to invent your own system for how to represent it.

I don’t find :active states particularity common, likely because design software doesn’t care to prompt you into state-based design.

Color Modes

Speaking of state, you could think of light/dark mode as a state for the entire design of the website.

Looking at the button designs above, if you put a very dark background instead of the light tan, those dark brown button borders will not be very visible if at all. It’s a designer’s job to consider that, plan for that, and design for that. Can/should/does your design software help with that?

Color modes are complicated anyway, as the choice of a color mode can be set as low-level as the operating system the browser is running on, the browser may change the mode, the browser may offer site-specific color mode settings, and the site itself may offer code mode settings of it’s own. And perhaps more than just light and dark!

Not to mention accessibility considerations. There has been experimental features like browsers than can force sites into certain color modes, and long-standing accessibility features like High Contrast mode on Windows. Does your design software help you with designing for those states?

From “Windows High Contrast Mode, Forced Colors Mode And CSS Custom Properties” by Eric Bailey

Variables

Speaking of code modes, maybe the way you’re handling that is through variables, or custom properties in CSS. Does your design software have a way of specifying variables? Then setting state in various places that change the values of those variables? I can answer that for you: it probably doesn’t. Even if it has a concept of variables, it’s likely abstracted in a way that doesn’t work quite like the web does. The key difficulty being that CSS custom properties cascade through the DOM and can be set/adjusted at any level. This isn’t particularly feasible in a static design, as the DOM is not a concept they are concerned with.

Any Other Kind of State

What about the loading state of dynamically loaded data in your app? Do you have a plan for that? Spinners? Skeletons? Nothing? Do they take up the appropriate space to avoid layout shifting? Are you using some kind of placeholder technique for images? Did you design this stuff in your design tool? Or do you just skip over it and deal with it as a forlorn implementation detail?

What about error states?

What about the state where a paying user’s credit card has lapsed and they no longer have access to certain features?

I find that “different states” is a tricky concept for design tools. They tend to just ignore it and let you invent how you want to handle it yourself, which usually amounts to: draw another rectangle nearby and design the alternate state in there — if you remember to.

Good designers will work from design documents that spell out the differnet necessary states to deal with and organize the documents to showcase those important states. But it can also be true that there are so many permutations of bits of state that hand-designing each one is too much to be practical for most design tools, so they end up more like vague sketches of the somewhat random combinations of state.

Viewport Sizes

Responsive design is so locked in that design tools largely can’t ignore it. But they also tend not to nail it either. I find the most common approach is the: Uhhh I guess just design a largish-one, a pretty small one, and something in between. Then just made educated guesses for stuff in between.

This can and does work fine sometimes.

But when I say it doesn’t nail it, it’s because it doesn’t communicate things in a way that is perfectly relevant to what the web can do. For instance, those two columns of type above, is that saying… always do two columns until the 799px breakpoint then do one column? That seems like a reasonable read, but… maybe the breakpoint should be driven by the direct parent not the whole viewport? Maybe at really wide widths it could go up to more columns? How flexible are those column widths? Do they have a min and max? Should we be thinking about how paragraphs break across them? What if an image or other media goes into the columns, should it behave in any special way?

What about the font sizing in the example above? The biggest bit of text definitely changes size, but how? Fluidly? Set sizes at set breakpoints? Does all type do that or just some? How can we design that?

There is so much to think about with fluid, responsive design. Design tools I feel barely scratch the surface, and it’s been a good decade-and-a-half here. What would it be like to stop thinking about fixed breakpoints at all?

Anything Scrolling or Mouse Movement Related

At the most basic level, we already rarely deal with styling scrollbars at all. Static designs are designed such that you see the entire page from header to footer. Does this affect how we prioritize content? Do we think about how much scrolling is too much?

But more to the point, there are lots of web design possibilities specifically related to scrolling. Think about position: fixed; — how do you represent that on a picture of a website? Consider the different possibilities of background-attachment, can a static design explain that? Smooth scrolling? Scroll snapping? What about all the CSS carousel stuff?

Variable Content

Another classic! Static designs tend to have “happy path” content where the images are beautiful and the content fits perfectly into the layout.

Some classic lorem ipsum text in here. It gets ridiculed sometimes because it means you aren’t designing around even a single real world design situation let alone the many that are likely needed.

What if that card design above had 12 tags? It would probably look silly if they wrapped, pushing the more-important header too far down. What if one of the tags was “Hamlet in Northumberland, England“. Will it break the container?

It looks like the header above is already truncated to two lines. It’s nice we can do that on the web, but as Karen always reminds us, truncation is not a content strategy. Does the CMS get involved to limit things? Are design people at the CMS meetings so that good design ideas can be a part of the decision making process?

What if there isn’t enough content? Will this card work without the description text? Without a photo or author?

What about translation? That’s a big one as sites are translated whether or not you deal with it. Can languages that translate with generally more characters than the original language find enough room?

Units & Math

CSS has interesting, real, meaningful units. Sure there are “pixels” and that’s all well and good, but we’ve got relative units that relate to each other and are flexible with user settings. We’ve got percentages. We’ve got typography-relational units. We’ve got units relative to the viewport or the container. And quite a bit more!

Design tools don’t even go there with units. They tend to be unitless.

Something is “244” wide, which is probably best mappable to px, but doesn’t actually say px because the exported files may or may not actually map to that number. Units alone represent a significant devide between static designs and real website implementations.

And then there is the slew of functional calculations possible on the web available in JavaScript and CSS. Your calc() and tan() and min() and clamp() and the slew of other functions that help express interesting design intent are largely absent in design tools.

Even a real practical grid layout like:

.grid {
  display: grid;
  grid-template-columns: minmax(100px, auto) 4fr minmax(100px, 1fr);
}

Above, I’m trying to say: the 1st column is a navigation, so have it be as wide as the widest bit of content, but it’s allowed to be squished smaller, to a point, then the the widest, generally about 2/3rds of layout, and the 3rd column takes the leftover after that, and is allowed to squish smaller than auto. That’s a mouthful, but it’s pretty practical/common and all but unexpressable in design tools.

Animations & Transitions

Transitions can be so simple. Here’s just a smidge of scaling on some element on hover:

https://sarthakmishra.com/

Even that, does your design tool make that easy to mock up? And thus show to your team and talk about and get signoff or whatever is needed? It seems to me these types of things tend to come later in the process. Like they get slapped on during implementation because code is such a better place to explore and play with these ideas. Maybe that’s OK. Maybe that’s a shame that design tools to make it easier to think through these things during initial design.

From the same nice website as above, look at these slightly more complex transitions:

One look at a GSAP showreel and you gotta wonder: how much of this is done in a design tool vs how much of this is just people who know how code works just getting into the browser and just working there?

Animation isn’t an isolated concept here either, it can be linked to scrolling and state, for instance.

Device-Specific Details

How do you mockup handling the touch target requirements for devices with touch screens? Are you thinking about where people normally have their thumbs? Have you done everything you can to make form input good, particularly for on-screen keyboards? Do you think you might have to make some design choices for low-bandwidth situations? Did you make a mockup for landscape mode? How’s that information hierarchy on a tiny screen?

What about big ol’ 1980px wide screens with browsers open on the entire thing, which isn’t even particularly rare! Or even bigger — does the design hold up? Are you implementing appropriate cursors in appropriate situations for systems with a mouse? For eCommerce sites, have you thought about designing the checkout experience differences between browsers that support Google Pay vs Apple Pay and those types of differences?

Some of this stuff design software is fine at. They can’t anticipate every design need for every app, that’s on you, so you do the work. But some stuff is just hard in design software. It certainly isn’t going to help you design appropriate cursors, for instance.

Media Handling

Did you mock up how videos are integrated into the site? Are you doing anything with audio? More exotic stuff like a 3D viewing experience? You’re likely to be faking parts of it at best in static designs.

So Many More Things

I’m not trying to dog on design software. I actually quite like a lot of design software, and think it’s often an ideal place for ideas to start and early marination of design choices. Sometimes design software frees up the mind to think about design choices you may not make once you’re in code. An example of that might be something like strangely-overlapping elements which is easy to do while dragging rectangles around in design software, but harder think of once they are <div>s.

But then it ends up as like… 5%? Five percent of the time spent in the design software getting ideas going, and the rest of the time in the browser. And the rest of that time is also design work. Not just implementation but design itself.

Stay tuned for the Frontend Masters course Award-Winning Marketing Websites with Matias Gonzalez, who will surely be blowing minds while teaching you the power of design on the web platform.


I first had an inkling to write this blog post when listening to the Complementary podcast episode The Gap Between Design and Execution with Alex Krasikov.

]]>
https://frontendmasters.com/blog/web-design-what-is-the-web-capable-of-that-is-hard-to-express-in-design-software/feed/ 1 6030
Mingcute https://frontendmasters.com/blog/mingcute/ https://frontendmasters.com/blog/mingcute/#respond Sat, 16 Aug 2025 20:12:18 +0000 https://frontendmasters.com/blog/?p=6782

🔥 Mingcute has been my go-to icon library for a while.- Open source and open license- "Cute" and bubbly icon style with more options than most- Really nice Figma plugin- Iconify support to use in any web project

Ben Holmes (@bholmes.dev) 2025-08-04T12:39:30.195Z
]]>
https://frontendmasters.com/blog/mingcute/feed/ 0 6782
Get the number of auto-fit/auto-fill columns in CSS https://frontendmasters.com/blog/count-auto-fill-columns/ https://frontendmasters.com/blog/count-auto-fill-columns/#comments Wed, 06 Aug 2025 15:08:24 +0000 https://frontendmasters.com/blog/?p=6567 Ever wanted to get the number of auto-fit/auto-fill columns in a grid? For example, because you want to highlight just the items in the first or last row or column? Do something special just for even or for odd rows or columns (e.g. zebra striping)? Or for any one specific row or column? Create responsive non-rectangular grids? And all of this with zero breakpoints?

This is all doable with pure CSS by using container query units, CSS variables, and CSS mathematical functions! Of course, it also involves navigating browser bugs and support gaps. But at the end of the day, it is possible to do it cross-browser!

Let’s see how!

The Basic Idea

Setup

We start with a .grid with a lot of items, let’s say 100. I normally prefer to generate them in a loop using a preprocessor to avoid clutter in the HTML and to make it easy to change their number, but it’s also possible to do so using Emmet. For the demos illustrating the concept here, we’re using Pug, and also numbering our items via their text content:

.grid
- for(let i = 0; i < 100; i++)
.item #{i + 1}

Our .grid has auto-fit columns:

.grid {
  --u: 7em;

  display: grid;
  grid-template-columns: repeat(auto-fit, var(--u));
  container-type: inline-size
}

This means our .grid has as many columns of unit width u as can fit within its own content-box width. This width is flexible and is given by the page layout, we don’t know it. However, its children (the .item elements) can know it as 100cqw in container query units. To have these container units available for the .grid element’s children (and pseudos), we’ve made the .grid an inline container.

This should work just fine. And it does, in both Chrome and Firefox. However, if we try it out in Safari, we see our .grid is collapsed into a point. Unfortunately, in Safari, auto-fit grids break if they are also containers. (Note: this Safari bug is actually fixed, it’s just waiting to make its way to a stable release.)

We have two options in this case.

The first would be to replace auto-fit with auto-fill. When we have as many items as we do in this case, we can use either of them, the difference between them is only noticeable when we don’t even have enough items to fill one row.

.grid {
  --u: 7em;

  display: grid;
  grid-template-columns: repeat(auto-fill, var(--u));
  container-type: inline-size
}

The second would be to put the .grid inside a wrapper .wrap element and move the container property on the wrapper.

.wrap { container-type: inline-size }

.grid {
  --u: 7em;

  display: grid;
  grid-template-columns: repeat(auto-fit, var(--u))
}

We’re going for the first option here.

Now we’re getting to the interesting part!

Getting the number of columns

In theory, we could get the number n of columns on the .item children of the .grid via division, whose result we round down (if the container width of 100cqw is 2.23 times the unit width u of a column, then we round down this ratio to get the number of columns we can fit, which is 2 in this case):

--n: round(down, 100cqw/var(--u))

In practice, while this should work, it only works in Safari (since Sept 2024) and in Chrome (since June 2025), where we can test it out by displaying it using the counter hack:

.grid::before {
  --n: round(down, 100cqw/var(--u));

  counter-reset: n var(--n);
  content: counter(n)
}

We’ve wrapped this inside a @supports block so we have a message that lets us know about this failing in non-supporting browsers (basically Firefox), where we see the following:

base_idea_fallback
the result in non-supporting browsers: no number of columns can be computed

In Safari and Chrome, things look like in the recording below:

We can see we have a problem when we have one column and it overflows the parent: the ratio between the parent .grid width of 100cqw and the column unit width u drops below 1, so we can fit one item 0 times inside the content-box width of the .grid. And this is reflected in the n value, even though, in practice, we cannot have a grid with less than one column. However, the fix for this is simple: use a max() function to make sure n is always at least 1.

--n: max(1, round(down, 100cqw/var(--u)))

Whenever the division result drops below 1, the result of the max() function isn’t the round() value anymore, but 1 instead.

You can see it in action in demo below, but keep in mind it can only compute the number of columns in supporting browsers (Safari/Chrome):

Great, but what Firefox? The Firefox bug looks like it’s dormant, so we cannot get the ratio between two length values there.

Extending support

However, we have a clever hack to solve the problem!

The idea behind is the following: the tangent of an acute angle in a right triangle is the ratio between the length of the cathetus opposing the angle and the length of the cathetus adjacent to it. So basically, the tangent is a ratio between two length values and such a ratio is precisely what we need.

A diagram illustrating basic trigonometry, featuring the tangent function, labeled 'tan(a) = opposing / adjacent', with definitions for 'opposing cathetus' and 'adjacent cathetus', and an angle 'a' represented in a right triangle.
basic trigonometry recap

Now you may be wondering what right triangle and what angle do we even have here. Well, we can imagine building a triangle where a cathetus has the same length as the .grid parent’s content-box width (100cqw on the .item elements, which we’ll call w) and the other has the same length as the column unit width (u).

The tangent of the angle opposing the cathetus of length w is the ratio between w and u. Okay, but what is this angle?

A mathematical diagram illustrating the relationship between the tangent function, the angle 'a', and the lengths 'w' and 'u' in a right triangle.
using trigonometric functions to get around browser support gaps

We can get this angle using the atan2() function, which takes two arguments, the length of the opposing cathetus w and the length of the adjacent cathetus u:

--a: atan2(var(--w), var(--u))

Having the angle a and knowing that the ratio f between w and u is the tangent of this angle, we can write:

--f: tan(var(--a))

Or, replacing the angle in the formula:

--f: tan(atan2(var(--w), var(--u)))

In general, know that a length ratio like w/u can always be computed as tan(atan2(w, u)).

Rounding down this ratio f gives us the number of columns of unit width u that fit within the .grid parent’s content-box width w.

--n: round(down, var(--f))

So we can write it all as follows, introducing also the correction that the number of columns needs to be at least 1:

--f: tan(atan2(var(--w), var(--u)));
--n: max(1, round(down, var(--f)))

That’s it, that’s the formula for --n in the case when we don’t have support for getting the ratio of two length values! There is one catch, though: both --w and --u have to be registered as lengths in order for atan2() to work properly!

Putting it all together, the relevant code for our demo looks as follows:

.grid {
  --u: 7em;

  display: grid;
  grid-template-columns: repeat(auto-fill, var(--u));
  container-type: inline-size
}

.grid::before, .item {
  --w: 100cqw;
  --f: var(--w)/var(--u);
  --n: max(1, round(down, var(--f)));
}

@supports not (scale: calc(100cqh/3lh)) {
  @property --w {
    syntax: '<length-percentage>';
    initial-value: 0px;
    inherits: true
  }

  @property --u {
    syntax: '<length-percentage>';
    initial-value: 0px;
    inherits: true
  }
	
  .grid::before, .item { --f: tan(atan2(var(--w), var(--u))) }
}

Note that the .grid pseudo is only needed to display the --n value (using the counter hack) for us to see in the demo without having to register it and then look for it in DevTools (which is the tactic I most commonly use to check the computed value of a CSS variable).

Almost there, but not exactly.

Fixing tiny issues

If you’ve played with resizing the demo above, you may have noticed something is off in Firefox at times. At certain points when the .grid element’s content-box width w is a multiple of the unit column width u, for example, when w computes to 1008px and the unit column with u of 112px fits inside it exactly 9 times, Firefox somehow computes the number of columns as being smaller (8 instead of 9, in this example).

My first guess was this is probably due to some rounding errors in getting the angle via atan2() and then going back from an angle to a ratio using tan(). Indeed, if we register --f so we can see its value in DevTools, it’s displayed as 8.99999 in this case, even though 1008px/112px is exactly 9.

Screenshot of browser developer tools showing a grid layout with items numbered from 1 to 18. The grid has a header indicating the number of auto-fill columns, which is 8. The inspector highlights CSS variables related to grid sizing.
rounding error caught by Firefox DevTools

So this means rounding down f results in the number of columns n being computed as 8, even though it’s actually 9. Hmm, in this case, it might be better to round f to a tiny precision of .00001 before rounding it down to get the number of columns n:

--f: round(tan(atan2(var(--w), var(--u))), .00001)

This seems to get the job done.

Still, I’m a bit worried this still might fail in certain scenarios, even though I’ve kept resizing obsessively in Firefox and haven’t encountered any problems after rounding f.

So let’s make sure we’re on the safe side and place the .grid in a wrapper .wrap, make this wrapper the container, compute the number of columns n on the .grid and use it to set the grid-template-columns. This way, the essential CSS becomes:

.wrap {
  container-size: inline-type;
}

.grid {
  --w: 100cqw;
  --u: 7em;
  --f: var(--w) / var(--u);
  --n: max(1, round(down, var(--f)));

  display: grid;
  grid-template-columns: repeat(var(--n), var(--u));
  justify-content: center;
}

@supports not (scale: calc(100cqh / 3lh)) {
  @property --w {
    syntax: "<length-percentage>";
    initial-value: 0px;
    inherits: true;
  }

  @property --u {
    syntax: "<length-percentage>";
    initial-value: 0px;
    inherits: true;
  }

  .grid {
    --f: round(tan(atan2(var(--w), var(--u))), 0.00001);
  }
}

Note that we may also use 1fr instead of var(--u) for the grid-template-columns property if we want the .item elements to stretch.

Mind the gap

Nice, but oftentimes we also want to have a gap in between our rows and columns, so let’s see how the number of columns can be computed in that case.

Whenever we have n columns, we have n - 1 gaps in between them.

This means that n times the unit column width plus (n - 1) times the gap space adds up to the container’s content-box width:

n·u + (n - 1)·s = w

If we add s on both sides in the equation above, we get:

n·u + (n - 1)·s + s = w + s ⇒ 
n·u + n·s - s + s = w + s ⇒
n·u + n·s = w + s ⇒
n·(u + s) = w + s ⇒
n = (w + s)/(u + s)

Putting this into CSS, our ratio looks as follows:

(var(--w) + var(--s))/(var(--u) + var(--s))

Note that in our case, it’s the fraction f that we compute this way before we round it to get the number of items n and ensure n is always at least 1.

Also note that the CSS variables we need to register for the no calc() length division fallback are the numerator and denominator of this fraction. So our essential CSS becomes:

.wrap {
  container-size: inline-type;
}

.grid {
  --w: 100cqw;
  --u: 7em;
  --s: 3vmin;
  --p: calc(var(--w) + var(--s)); /* numerator */
  --q: calc(var(--u) + var(--s)); /* denominator */
  --f: var(--p) / var(--q);
  --n: max(1, round(down, var(--f)));

  display: grid;
  grid-gap: var(--s);
  grid-template-columns: repeat(var(--n), 1fr);
}

@supports not (scale: calc(100cqh / 3lh)) {
  @property --p {
    /* numerator */
    syntax: "<length-percentage>";
    initial-value: 0px;
    inherits: true;
  }

  @property --q {
    /* denominator */
    syntax: "<length-percentage>";
    initial-value: 0px;
    inherits: true;
  }

  .grid {
    --f: round(tan(atan2(var(--p), var(--q))), 0.00001);
  }
}

Let’s Go Wild!

And let’s see where we can use this!

Highlighting items on a certain column

In order to do something like this, we use the item indices. Once sibling-index() is supported cross-browser, we’ll be able to do this:

.item { --i: calc(sibling-index() - 1) }

Note that we need to subtract 1 because sibling-index() is 1-based and we need our index i to be 0-based for modulo and division purposes.

Until then, we add these indices in style attributes when generating the HTML:

.grid
- for(let i = 0; i < 100; i++)
.item(style=`--i: ${i}`) #{i + 1}

Let’s say we want to highlight the items on the first column. We get the number of columns n just like before. An item is on the first column if i%n (which gives us the 0-based index of the column an item of index i is on) is 0. Now given I used the word if there, you might be thinking about the new CSS if() function. However, we have a way better supported method here.

If the column index i%n is 0, then min(1, i%n) is 0. If the column index i%n isn’t 0, then min(1, i%n) is 1. So we can do the following:

.item {
  --nay: min(1, mod(var(--i), var(--n))); /* 1 if NOT on the first column */
  --yay: calc(1 - var(--nay)); /* 1 if on the first column! */
}

So then we can use --yay to highlight the items on the first column by styling them differently, for example by giving them a different background:

.item {
  --nay: min(1, mod(var(--i), var(--n))); /* 1 if NOT on the first column */
  --yay: calc(1 - var(--nay)); /* 1 if on the first column! */

  background: color-mix(in srgb, #fcbf49 calc(var(--yay)*100%), #dedede)
}

You can see it in action in the live demo below:

Now let’s say we want to highlight the items on the last column. In this case, the column index i%n is n - 1, which means that their difference is 0:

(n - 1) - (i%n) = 0

Using this, we can do something very similar to what we did before, as the minimum between 1 and this difference is 0 for items on the last column and 1 for those that aren’t on the last column:

.item {
  /* 1 if NOT on the last column */
  --nay: min(1, (var(--n) - 1) - mod(var(--i), var(--n))));
  /* 1 if on the last column! */
  --yay: calc(1 - var(--nay));
}

For example, if n is 7, then the column index i%n can be 0, 1, … 6 and n - 1 is 6. If our item of index i is on the last column, then its column index i%n = i%7 = 6, so the difference between n - 1 = 7 - 1 = 6 and i%n = i%7 = 6 is 0. If our item of index i isn’t on the last column, then its column index i%n = i%7 < 6, so the difference between n - 1 = 6 and i%n < 6 is 1 or bigger. Taking the minimum between 1 and this difference ensures we always get either 0 or 1.

In general, if we want to highlight a column of index k (0-based, but we can just subtract 1 in the formula below if it’s given 1-based), we need to compute the difference between it and i%n (the column index of an item of index i), then use the absolute value of this difference inside the min():

.item {
  --dif: var(--k) - mod(var(--i), var(--n));
  --abs: abs(var(--dif));
  --nay: min(1, var(--abs)); /* 1 if NOT on column k */
  --yay: calc(1 - var(--nay)); /* 1 if on column k! */
}

The difference and its absolute value are 0 when the item of index i is on column k and different (bigger in the case of absolute value) when it isn’t.

We need the absolute value here because, while the difference between n - 1 and i%7 is always 0 or bigger, that is not the case for the difference between any random k and i%n. For example, if n is 7 and k is 2, the k - i%n difference is negative when k is smaller than i%n, for example when i%n is 5. And we need the difference that goes into the min() to be 0 or bigger in order for the min() to always give us either 0 or 1.

All modern stable browsers support abs(), but for the best possible browser support, we can still test for support and use the fallback:

@supports not (scale: abs(-2)) {
  .item { --abs: max(var(--dif), -1*(var(--dif))) }
}

Also, note that if the selected column index k is equal to n or bigger, no items get selected.

In the interactive demo below, clicking an item selects all items on the same column:

It does this by setting --k (in the style attribute of the .grid) to the index of that column.

A code snippet from a web developer's browser console showcasing CSS rules for items in a grid layout, including custom properties for styling.
Chrome DevTools screenshot showing --k being set on the .grid parent and used in computations on .item children

We can also highlight items on either odd or even columns:

.item {
  /* 1 if on an even column, 0 otherwise */
  --even: min(1, mod(mod(var(--i), var(--n)), 2));
  /* 1 if on an odd colum, 0 otherwise */
  --odd: calc(1 - var(--even));
}

This is a particular case of highlighting every k-th column starting from column j (again, j is a 0-based index and smaller than k):

.item {
  --dif: var(--j) - mod(mod(var(--i), var(--n)), var(--k));
  --abs: abs(var(--dif));
  --nay: min(1, var(--abs));
  /* 1 if on one of every kth col starting from col of index j */
  --yay: calc(1 - var(--nay));
}

Highlighting items on a certain row

If we want to highlight the items on the first row, this means their index i must be smaller than the number of columns n. This means the difference n - i must be bigger than 0 for items on the first row. If we clamp it to the [0, 1] interval, we get a value that’s 0 on every row but the first and 1 on the first row.

.item {
  --yay: clamp(0, var(--n) - var(--i), 1)  /* 1 if on the first row! */
}

There is more than one way to skin a cat however, so another approach would be to get the row index, which is the result of i/n rounded down. If this is 0, the item of index i is on the first row. If it’s bigger than 0, it isn’t. This makes the minimum between 1 and i/n rounded down be 0 when the item of index i is on the first row and 1 when it isn’t.

.item {
  --nay: min(1, round(down, var(--i)/var(--n))); /* 1 if NOT on the first row */
  --yay: calc(1 - var(--nay)); /* 1 if on the first row! */
}

This second approach can be modified to allow for highlighting the items on any row of index k as the difference between k and i/n rounded down (the row index) is 0 if the item of index i is on the row of index k and non-zero otherwise:

.item {
  --dif: var(--k) - round(down, var(--i)/var(--n));
  --abs: abs(var(--dif));
  --nay: min(1, var(--abs)); /* 1 if NOT on row of index k */
  --yay: calc(1 - var(--nay)); /* 1 if on row of index k! */
}

Highlighting the items on any row includes the last one. For this, we need to know the total number t of items on our grid. This means (t - 1) is the index of the last grid item, and we can get the index of the row it’s on (that is, the index of the final row) by rounding down (t - 1)/n. Then we substitute k in the previous formula with the index of the final row we’ve just obtained this way.

.item {
  /* 1 if NOT on last row */
  --nay: min(1, round(down, (var(--t) - 1)/var(--n)) - round(down, var(--i)/var(--n)));
  /* 1 if on last row! */
  --yay: calc(1 - var(--nay));
}

There are two things to note here.

One, we don’t need the absolute value here anymore, as the last row index is always going to be bigger or equal to any other row index.

Two, we’re currently passing the total number of items t to the CSS as a custom property when generating the HTML:

- let t = 24; //- total number of items on the grid

.wrap
.grid(style=`--t: ${t}`)
- for(let i = 0; i < t; i++)
.item(style=`--i: ${i}`) #{i + 1}

But once sibling-count() is supported cross-browser, we won’t need to do this anymore and we’ll be able to write:

.item { --t: sibling-count() }

Just like before, we can highlight items on odd or even rows.

.item {
  /* 1 if on an even row */
  --even: min(1, mod(round(down, var(--i)/var(--n)), 2));
  /* 1 if on an odd row */
  --odd: calc(1 - var(--even));
}

And the odd/ even scenario is a particular case of highlighting items on every k-th row, starting from row of index j.

.item {
  --dif: var(--j) - mod(round(down, var(--i)/var(--n)), var(--k));
  --abs: abs(var(--dif));
  --nay: min(1, var(--abs));
  /* 1 if on one of every kth row starting from row of index j */
  --yay: calc(1 - var(--nay));
}

Taking it Further

Another thing this technique can be used for is creating responsive grids of non-rectangular shapes with no breakpoints. An example would be the hexagon grid below. We aren’t going into the details of it here, but know it was done using this technique plus a few more computations to get the right hexagon alignment.

]]>
https://frontendmasters.com/blog/count-auto-fill-columns/feed/ 1 6567
Out-of-your-face AI https://frontendmasters.com/blog/out-of-your-face-ai/ https://frontendmasters.com/blog/out-of-your-face-ai/#respond Sun, 01 Jun 2025 15:29:05 +0000 https://frontendmasters.com/blog/?p=6007 A very interesting aspect of the AI smashing its way into every software product known to man, is how it’s integrated. What does it look like? What does it do? Are we allowed to control it? UX patterns are evolving around this. In coding tools, I’ve felt the bar being turned up on “anticipate what I’m doing and offer help”. Personally I’ve gone from, hey that’s nice thanks to woah woah woah, chill out, you’re getting in my way.

I’m sure this will be fascinating to watch for a long time to come. For example, “Subtle Mode” in Zed gets AI more “out of your face” and if you want to see a suggestion, you press a button. I love that idea. But I also understand Kojo Osei’s point here: there should be no AI button.

]]>
https://frontendmasters.com/blog/out-of-your-face-ai/feed/ 0 6007
The HTML Review https://frontendmasters.com/blog/the-html-review/ https://frontendmasters.com/blog/the-html-review/#respond Tue, 08 Apr 2025 00:42:30 +0000 https://frontendmasters.com/blog/?p=5538 The fourth issue of The HTML Review is out. Wonderful writing framed by entirely different and unusual interactive interfaces, brought to you by the power of web technology. A zine come to life.

Just try to pick a favorite.

]]>
https://frontendmasters.com/blog/the-html-review/feed/ 0 5538
Our interfaces havelost their senses https://frontendmasters.com/blog/our-interfaces-havelost-their-senses/ https://frontendmasters.com/blog/our-interfaces-havelost-their-senses/#respond Fri, 14 Mar 2025 14:42:55 +0000 https://frontendmasters.com/blog/?p=5376 Amelia Wattenberger asks in a wonderful blog post wondering about more human interfaces:

Can we build something richer—something that moves with us, speaks our language, and molds to our bodies?

]]>
https://frontendmasters.com/blog/our-interfaces-havelost-their-senses/feed/ 0 5376
Mesh Gradient Generator https://frontendmasters.com/blog/mesh-gradient-generator/ https://frontendmasters.com/blog/mesh-gradient-generator/#respond Thu, 20 Feb 2025 15:51:41 +0000 https://frontendmasters.com/blog/?p=5210 A nice tool for generating mesh gradients from Erik D. Kennedy. You might call it a bit of a trend, but as Erik pointed out in a recent newsletter, they can be quite versatile because it’s just a nice background look that doesn’t demand anything in particular from the brand.

Mesh gradients can work across a huge variety of brands.

  • Dark? Yes.
  • Light? Yes.
  • Grainy? Sure.
  • Pensive? Absolutely.
  • Delicate? Yup.
  • Weatherworn and rugged? Totally.
]]>
https://frontendmasters.com/blog/mesh-gradient-generator/feed/ 0 5210