Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Tue, 12 Aug 2025 15:48:52 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 225069128 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
Is there a Correct Answer? Flipping Layouts When Google Translate Swaps between a Left-to-Right Language and a Right-to-Left Language https://frontendmasters.com/blog/to-flip-or-not-to-flip/ https://frontendmasters.com/blog/to-flip-or-not-to-flip/#comments Fri, 16 May 2025 20:24:11 +0000 https://frontendmasters.com/blog/?p=5890 My personal website was designed in English, because that’s the only language I speak. English is a left-to-right language (LTR).

Anybody can translate the website though. There are a variety of site translation tools. I don’t have any data on popularity, but I gotta imagine Google Translate is up there, which is a website and Chrome Extension and, to some degree, automatic translation is just built into Chrome (and other browsers).

So let’s say I translate my website from English to Arabic, a right-to-left language (RTL). Here’s what I get:

It’s the exact same layout, it’s just the words are in Arabic now. Which is not terribly surprising, I guess? But even the alignments have stayed the same, so this RTL language is still being show in an LTR way.

Google Translate, aside from the text node translation, makes a few other changes that are notable here. What used to be:

<html lang="en">

Becomes:

<html lang="ar" class="translated-rtl">

Those changes do not actually change the direction to RTL. It could have, like:

<html 
  lang="ar" 
  class="translated-rtl" 
  dir="rtl"
>

Or it could have injected CSS like:

.translated-rtl {
  direction: rtl;
}

/* or */

[lang="ar"] {
  direction: rtl;
}

But it doesn’t. I don’t know for sure, but my guess is that it intentionally doesn’t do that, because it jacks up more layouts than it helps.

But let’s say you’re me (perfect, handsome) and you’ve changed your muscle memory for writing a lot of CSS properties to using logical properties. That is, stuff like padding-inline-start instead of padding-left, and the like. That, plus using layout like flexbox and grid, will reflow naturally with direction changes. So if you change the direction to rtl on my site, you get:

The whole layout flips.

So the question is:

Is that better”?

Meaning: does it read better for native Arabic speakers? Does it generally feel better or more native? Or is it worse, in that it’s unexpected or unnatural somehow?

I have a friend who speaks/reads Arabic, just for one anecdotal bit of data. I showed them a site and translated it, and they were like “it’s fine”. But then I showed them a tweaked one where things like the header and hero text and stuff was actually flipped, and they thought it was better. They were like “I never actually see this done, but it’s better this way.”

It’s likely that this no One True Answer here. Even if you’ve done a good job with a layout that flips and looks sensible. Alda Vigdís told me:

As someone who has worked on bi-lingual content, dir=”rtl” should of course be indicated for textual content, but the layout question depends on a lot more factors.

Arabic and Hebrew speaking power users often prefer to have a ltr-oriented layout, while some other groups prefer rtl-oriented layout.

So it may just be a matter of preference of individuals, which is more evidence for why Google Translate doesn’t go there (for layout). Perhaps they should be trying to find more fine-grained text nodes and flipping the dir there, but they don’t do that either.

If you land on “leave the layout alone, but flip the dir for text”, like Alda suggests, it would be a bring-your-own-CSS situation. You could use Google Translate’s class and flip just the text you need to, like:

.translated-rtl {
  p, li, dt, dd, td, th, h1, h2, h3 {
    direction: rtl;
  }
}

That feels a little 😬 to me, like you’ll miss some and make it worse instead of better or something. (I just picked those selectors randomly quick, to illustrate.) So much testing needed.

A flipped layout can be preferable even here though, as Naman told me:

There are somethings that work both ways. The sidebar can be on either side and it makes sense.

But something like the search bar makes a lot more sense with the layout flipped. [Also: headings in the sidebar are also a lot better with the layout flipped]

On balance, I think yes, flipping has an overall better result.

So if you’re looking for a straight answer, I’m afraid I can’t give you one. Except, ya know, do a good job.

]]>
https://frontendmasters.com/blog/to-flip-or-not-to-flip/feed/ 2 5890
The Latest in the “How are we going to do masonry?” Debate: Apple Says “Item Flow” https://frontendmasters.com/blog/the-latest-in-the-how-are-we-going-to-do-masonry-debate-apple-says-item-flow/ https://frontendmasters.com/blog/the-latest-in-the-how-are-we-going-to-do-masonry-debate-apple-says-item-flow/#respond Fri, 11 Apr 2025 15:52:40 +0000 https://frontendmasters.com/blog/?p=5565 Last we checked in with how the web platform was planning on helping us with “masonry” layout, it was a versus battle between “just use grid” and “make a new layout type”. Now Apple folks have an idea: a new shorthand property called item-flow. Nothing is decided here (I don’t think), but it seems very sensible. I like how it has meaning to multiple layout modes, potentially opening up new possibilities instead of masonry alone. They are going to elaborate in a future post, but it sounds like item-pack: collapse; is the road to masonry specifically, perhaps across multiple layout modes.

I’ll repoint to my feeling that reading-order is very related and also opens possibility in that we can use what we’ve already got in broader ways without negative accessibility implications.

]]>
https://frontendmasters.com/blog/the-latest-in-the-how-are-we-going-to-do-masonry-debate-apple-says-item-flow/feed/ 0 5565
Display Contents https://frontendmasters.com/blog/display-contents/ https://frontendmasters.com/blog/display-contents/#respond Thu, 19 Sep 2024 19:25:50 +0000 https://frontendmasters.com/blog/?p=3892 Ahmad Shadeed on CSS display: contents; — a feature that makes the DOM pretend that element just isn’t there (but it’s children are). Anything you use it for requires specific accessibility testing, but it can be quite useful. There are lots of use-cases here many of which boil down to “sometimes I want all these elements to be siblings, and sometimes I want wrappers around some of them”. I like Jeremy’s idea that HTML web component wrappers use this to remove themselves from equation, if the only purpose of that web component is augmenting the content already within it.

]]>
https://frontendmasters.com/blog/display-contents/feed/ 0 3892
What is safe alignment in CSS? https://frontendmasters.com/blog/what-is-safe-alignment-in-css/ https://frontendmasters.com/blog/what-is-safe-alignment-in-css/#respond Fri, 15 Mar 2024 00:48:35 +0000 https://frontendmasters.com/blog/?p=1258 Stefan Judis covered this recently, and like Stefan, I first heard of the safe keyword in CSS from Rachel Andrews at an AEA conference years ago. It’s used in addition to other alignment keywords in layout like this for example:

.flex {
  display: flex;
  align-items: safe center;
}

The extra value safe here is essentially saying “don’t let the alignment force a situation where the content is positioned off the screen where a user can’t scroll to it”. Content that is pushed off the screen in a way a user can’t scroll to it (think off the top or left of a browser window, or any element with scrolling overflow) is called data loss. I love the term data loss in CSS because that’s exactly what it is. For sighted users who only access websites with their vision, if they can’t see content, it ain’t there.

Here’s my own demo for explaining this.

I’ll post a video here below. Without using the safe keyword, and using align-items: center; in a flexbox context, you can see how if there isn’t enough room in an element, it’s possible for elements to go up over the top (block start) of the element in such a way it’s impossible to scroll to. You can scroll down just not up.

Then if you add the safe keyword, you can see the the layout won’t let the items go up into that inaccessible/unscrollable. That way you can always scroll down to get the content, the content never goes up

See how the elements stick to the top as the size of the parent changes rather than go above it? safe!

]]>
https://frontendmasters.com/blog/what-is-safe-alignment-in-css/feed/ 0 1258
The Fifty-Fifty Split and Overflow https://frontendmasters.com/blog/the-fifty-fifty-split-and-overflow/ https://frontendmasters.com/blog/the-fifty-fifty-split-and-overflow/#respond Wed, 06 Mar 2024 01:55:48 +0000 https://frontendmasters.com/blog/?p=1114 A left-and-right layout is blissfully easy with CSS grid. Something like grid-auto-rows: 1fr; will do it — just put two elements as the children. This is how I like to roll though, making two equal columns that can shrink properly:

.halfs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

But Ryan Mulligan found a very interesting way to make it complicated in The Fifty-Fifty Split and Overflow. Let’s say one side needs to naturally grow with the content, but if the other side is longer, then it needs to scroll. I wouldn’t call it intuitive, but it turns out using contain: size; on the scrolling container will do the trick.

]]>
https://frontendmasters.com/blog/the-fifty-fifty-split-and-overflow/feed/ 0 1114
Vertical Centering in a Block-Level Element https://frontendmasters.com/blog/vertical-centering-in-a-block-level-element/ https://frontendmasters.com/blog/vertical-centering-in-a-block-level-element/#comments Thu, 28 Dec 2023 21:48:31 +0000 https://frontendmasters.com/blog/?p=325 Rachel Andrew, blowing my mind a bit:

The align-content property from the Box Alignment spec does the job, however browsers require that you make the parent element either a flex or a grid layout. While the property was specified to work in block layout, no browser had implemented it, until now.

align-content in block layout

So now we can vertically center an element without having to reach for grid or flexbox.

Ollie Williams covered the news as well in Easy vertical alignment without flexbox, and also notes:

align-content now also works for elements with a display of table-cell or list-item.

]]>
https://frontendmasters.com/blog/vertical-centering-in-a-block-level-element/feed/ 1 325