QuirksBlog

Syndicate content
Updated: 20 hours 5 min ago

Inherit, initial, unset, revert

Wed, 06/02/2021 - 12:55am

Today we’re going to take a quick look at a few special CSS keywords you can use on any CSS property: inherit, initial, revert, and unset. Also, we will ask where and when to use them to the greatest effect, and if we need more of those keywords.

The first three were defined in the Cascading Level 3 spec, while revert was added in Cascading Level 4. Despite 4 still being in draft revert is already supported. See also the MDN revert page, Chris Coyier’s page, and my test page

inherit

The inherit keyword explicitly tells an element that it inherits the value for this declaration from its parent. Let’s take this example:

.my-div { margin: inherit; } .my-div a { color: inherit; }

The second declaration is easiest to explain, and sometimes actually useful. It says that the link colour in the div should be the same as the text colour. The div has a text colour. It’s not specified here, but because color is inherited by default the div gets the text color of its parent. Let’s say it’s black.

Links usually have a different colour. As a CSS programmer you frequently set it, and even if you don’t browsers automatically make it blue for you. Here, however, we explicitly tell the browsers that the link colour should be inherited from its parent, the div. In our example links become black.

(Is this a good idea? Occasionally. But if you remove the colour difference between links and main text, make sure your links are underlined. Your users will thank you.)

Now let’s look at the margin: inherit. Normally margins don’t inherit, and for good reason. The fact that an element has margin-left: 10% does not mean all of its descendents should also get that margin. In fact, you most likely don’t want that. Margins are set on a per-case basis.

This declaration tells the div to use the margin specified on its parent, however. This is an odd thing to specify, and I never saw a practical use case in the wild. Still CSS, being ridiculously powerful, allows it.

In any case, that’s how the inherit keyword works. Using it for font sizes or colours may occasionally be a good idea. In other contexts - rarely.

And keep the difference between inheriting and non-inheriting properties in mind. It’s going to be important later on.

initial

The initial keywords sets the property back to its initial value. This is the value specified in the W3C specification for that property.

Initial values from the spec are a bit of a mixed bag. Some make sense, others don’t, really. float: none and background-color: transparent are examples of the first category. Of course an element does not have a background colour without you specifying one, nor does it float automatically.

Others are historically determined, such as background-repeat: repeat. Back in the Stone Age before CSS all background images repeated, and the CSS1 specification naturally copied this behaviour.

Still others are essentially arbitrary, such as display: inline. Why did W3C opt for inline instead of block? I don’t know, and it doesn’t really matter any more. They had to decide on an initial value, and while inline is somewhat strange, block would be equally strange.

In any case, the initial keyword makes the property revert to this initial value from the specification, whether that makes sense or not.

unset

When we get to the unset value the distinction between inheriting and non-inheriting properties becomes important. unset has a different effect on them.

  • If a property is normally inherited, unset means inherit.
  • If a property is normally not inherited, unset means initial.
revert

revert, the newest of these keywords, also distinguishes between inheriting and non-inheriting properties.

  • If a property is normally inherited, revert means inherit.
  • If a property is normally not inherited, revert reverts to the value specified in the browser style sheet.
all

Finally, we should treat all. It is not a value but a property, or rather, the collection of all CSS properties on an element. It only takes one of the keywords we discussed, and allows you to apply that keyword to all CSS properties. For instance:

.my-div { all: initial; }

Now all CSS properties on the div are set to initial.

Examples

The reaction of my test page to setting the display of all elements to the four keywords is instructive. My test script sets the following style:

body * { display: [inherit | initial | unset | revert] !important; }

The elements react as follows:

  • display: inherit: all elements now inherit their display value from the body. Since the body has display: block all elements get that value, whether that makes sense or not.
  • display: initial: the initial value of display is inline. Therefore all elements get that value, whether that makes sense or not.
  • display: unset: display does not inherit. Therefore this behaves as initial and all elements get display: inline.
  • display: revert: display does not inherit. Therefore the defaults of the browser style sheet are restored, and each element gets its proper display — except for the dl, which I had given a display: grid. This value is now supplanted by the browser-provided block.

Unfortunately the same test page also contains a riddle I don’t understand the behaviour of <button>s when I set color to the four keywords:

  • color: inherit: all elements, including <button>s, now inherit their colour from the body, which is blue. So all text becomes blue.
  • color: initial: since the initial value of color is black, all elements, including <button>s, become black.
  • color: unset: color inherits. Therefore this behaves as inherit and all elements, including <button>s, become blue.
  • color: revert: This is the weird one. All elements become blue, except for <button>s, which become black. I don’t understand why. Since colors inherit, I expected revert to work as inherit and the buttons to also become blue. But apparently the browser style sheet of button {color: black} (more complicated in practice) is given precedence. Yes, revert should remove author styles (the ones we write), and that would cause the black from the browser style sheet to be applied, but only if a property does not inherit — and color does. I don’t know why the browser style sheet is given precedence in this case. So I’m going to cop out and say form elements are weird.
Practical use: almost none

The purpose of both unset and revert is to wipe the slate clean and return to the initial and the browser styles, respectively — except when the property inherits; in that case, inheritance is still respected. initial, meanwhile, wipes the slate even cleaner by also reverting inheriting properties to their initial values.

This would be useful when you create components that should not be influenced by styles defined elsewhere on the page. Wipe the slate clean, start styling from zero. That would help modularisation.

But that’s not how these keywords work. We don’t want to revert to the initial styles (which are sometimes plain weird) but to the browser style sheet. unset comes closest, but it doesn’t touch inherited styles, so it only does half of what we want.

So right now these keywords are useless — except for inherit in a few specific situations usually having to do with font sizes and colours.

New keyword: default

Chris Coyier argues we need a new value which he calls default. It reverts to the browser style sheet in all cases, even for inherited properties. Thus it is a stronger version of revert. I agree. This keyword would be actually useful. For instance:

.my-component,.my-component * { all: default; font-size: inherit; font-family: inherit; color: inherit; }

Now we have a component that’s wiped clean, except that we decide to keep the fonts and colour of the main page. The rest is a blank slate that we can style as we like. That would be a massive boon to modularisation.

New keyword: cascade

For years now I have had the feeling that we need yet another keyword, which I’ll call cascade for now. It would mean “take the previous value in the cascade and apply it here.” For instance:

.my-component h2 { font-size: 24px; } .my-other-component h2 { font-size: 3em; } h2#specialCase { font-size: clamp(1vw,cascade,3vw) }

In this (slightly contrived) example I want to clamp the font-size of a special h2 between 1vw and 3vw, with the preferred value being the one defined for the component I’m working in. Here, cascade would mean: take the value the cascade would deliver if this rule didn’t exist. This would make the clamped font size use either 24px or 3em as its preferred value, depending on which component we’re in.

The problem with this example is that it could also use custom properties. Just set --h2size to either 24px or 3em, use it in the clamp, and you’re done.

.my-component h2 { --h2size: 24px; font-size: var(--h2size); } .my-other-component h2 { --h2size: 3em; font-size: var(--h2size); } h2#specialCase { font-size: clamp(1vw,var(--h2size),3vw) }

Still, this is but the latest example I created. I have had this thought many, many times, but because I didn’t keep track of my use cases I’m not sure if all of them could be served by custom properties.

Also, suppose you inherit a very messy CSS code base with dozens of components written at various skill levels. In that case adding custom properties to all components might be impractical, and the cascade keyword might help.

Anyway, I barely managed to convince myself, so professional standard writers will certainly not be impressed. Still, I thought I’d throw it out here to see if anyone else has a use case for cascade that cannot be solved with custom properties.

aspect-ratio

Wed, 05/19/2021 - 12:35am

This week we’ll take a look at the new aspect-ratio declaration and its use. Una Kravets wrote the introductory article, but there are some additional technical points to be made. I also wrote a little fallback that you might use if you need aspect-ratio right now.

At the time of writing aspect-ratio is supported by Chrome 90, by Safari Technology Preview, and by Firefox 88 if you set the aspect-ratio flag in about:config. You need one of these browsers to see the examples below — except for the fallback, which should work in all browsers that support custom properties.

.inner-box { border: 0; outline: 1px solid black; background-size: contain; background-position: center; background-repeat: no-repeat; background-color: red; } .flex div { flex-basis: 30%; flex-grow: 1; } .ar169 { aspect-ratio: 16/9; background-image: url(/blog/pix/ar-16-9.png); } .ar916 { aspect-ratio: 9/16; flex-grow: 0; background-image: url(/blog/pix/ar-9-16.png); } .ar43 { aspect-ratio: 4 / 3; background-image: url(/blog/pix/ar-4-3.png); } .ar11 { aspect-ratio: 1 / 1; background-image: url(/blog/pix/ar-1-1.png); } output { display: block; } code { font-size: inherit; } .mincontent { height: min-content; } .fixedwidth { width: 150px; height: 100px; } .height { height: 75px; } .maxheight { max-height: 50px; } .minheight,.minheight * { min-height: 50px; } .minwidth { min-width: 100px; } .padding { padding: 10%; } .contentbox { box-sizing: content-box; } .alignitems { align-items: flex-start; } Weak declaration

aspect-ratio defines the ratio between the width and height of a box, but it is a weak declaration. If the box has a specified width and height the browser uses those values and ignores the aspect ratio. Width and height might be specified by explicit width and height declarations or by other means, as we’ll see below.

aspect-ratio

In this example all three boxes have aspect-ratio: 16/9. The first box has width: auto; height: auto; i.e. as much width as you can take, and as little height as you need. aspect-ratio takes the width, converts it to pixels, and applies the defined aspect ratio to calculate the height.

The second box has a height: 50px; width: auto. The height is strong, but the auto width is weak and allows aspect-ratio to override it. Thus the box’s width is calculated by taking the height and applying the aspect ratio.

The third box has a fixed width: 150px; height: 100px as well as an aspect-ratio: 16/9. Now both width and height are strong and the aspect ratio is ignored.

Rounding

Even if aspect-ratio works fine the browser must find an integer number of device pixels for the box’s width and height. Fractions are discarded somewhere along the way. That’s why the calculated aspect ratio of a box is rarely 100% exact. In the examples you’ll often see a narrow stripe of red poking from underneath the background image. That image has the same aspect ratio as the box it appears in, but apparently uses a different calculation. In normal circumstances these tiny differences are not visible to the naked eye, so you can safely ignore them.

Fat red stripes, such as in the last box in the first example, are a sign of trouble, though.

min- and max-width and -height grid aspect-ratio and min- and max-height

You can set a min/max-width/height on the boxes. These are obeyed as normal, and aspect-ratio is obeyed as well. In this example the first box has a min-height: 100px, the second a max-height: 50px, and the third min-width: 100px. As you see they stretch up or down to their defined maximum and minimum and retain their aspect-ratio. box-sizing: border-box!

Now we come to a trickier topic unearthed originally by Ana Tudor — and this entire article is a good read that I recommend.

If it works, aspect-ratio sets either the width or the height of a box to match the other side and the defined aspect ratio. However, the exact effect depends on how width and height are defined; on box-sizing, in other words.

width may mean either the content width, without padding and border (box-sizing: content-box; the default), or the width of content + padding + border (box-sizing: border-box). In general, the latter is what we want.

aspect-ratio and box-sizing

In the next example the boxes have padding: 10%. Percentual padding is always calculated relative to the width of the parent element. Thus this box has a padding of 10% of its parent element’s width, even at the top and the bottom.

Since the padding is equal on all four sides, it may break the box’s aspect ratio. This depends on box-sizing. If you use the default box-sizing: content-box the width and height have the correct aspect ratio, but they define only the content area. An equal amoung of padding is added on all sides, and the aspect ratio is destroyed.

This problem is easy to solve: set box-sizing: border-box. Now width and height define the content, padding, and border combined, and this entire area is given the correct aspect ratio. Thus the padding is seamlessly integrated with the proper aspect ratio.

In fact, you should always set box-sizing: border-box in all your sites. content-box was a mistake, as W3C itself admitted (and as I said back in 2002). The fact that it fises aspect-ratio merely gives us an extra reason to do so.

aapect-ratio in flexbox

In a flex or grid context, aspect-ratio can appear not to work. In fact, running into these problems was what caused me to write this article in the first place.

Look at the example below. It doesn’t work! Oh noesies! What’s going on?

flex aspect-ratio

What happens here is default flexbox behaviour. First the widths of the items are calculated (here from flex-basis: 30%; grow: 1), and once that’s done the height of all items is set. These heights are calculated by applying their aspect ratio, but the tallest box is used to set the height of all items in the row. In our example that is the 1/1 box, so the 16/9 and 4/3 boxes also have an aspect ratio of 1/1.

This default behaviour is ruled by align-items: stretch, which is part of the flexbox default. If you use any other value the boxes’ height is set to auto and they take their proper aspect ratio. flex-start is the most obvious choice, but see CSS Tricks’ great flexbox guide for more options.

flex aspect-ratio and align-items

If for some reason you want to overrule the height stretch on only a single item you can give that item either align-self: flex-start or any other value, or height: min-content. Both work fine. The third box in the next example has height: min-content.

flex aspect-ratio and height: min-content aspect-ratio and grid

In a grid context aspect-ratio encounters the same problems as in a flexbox environment, only with added browser bugs that I wrote about last week. I expected the same behaviour as in a flexbox context, but that’s not entirely what’s happening.

The good news is that align-items, align-self, and height: min-content all work exactly as with flexbox. They negate the default grid behaviour of stretching the height of all elements in a row to the height of the highest element.

grid aspect-ratio and align-items

The problems are in the default rendering of aspect-ratio. Chrome and Safari implement this in one incorrect way, and Firefox is a quite different, equally incorrect way.

If all boxes in the example below have the same width and height the bugs have been solved. They get the same height for the reasons I explained under flexbox — grid works the same in this respect (I hope).

grid aspect-ratio with browser bugs

Firefox obeys the aspect-ratio while I think it shouldn’t. Instead, like in the flexbox example, it should stretch the boxes to the height of the highest in the row. In addition it calculates the height of the entire grid by assuming its items have the minimum height needed for their content — and since my example boxes do not have any content that height is 0. Thus the grid container is way too small. Both are clearly bugs that will probably be fixed soon.

Chrome and Safari TP size the grid container correctly, but seem to take the height as reference and size the width accordingly instead of taking the width as reference and sizing the height. Thus the 16/9 and 4/3 items become way too wide. I think this is also a bug — if it isn’t someone will have to carefully explain to me what’s going on. Fortunately this bug goes away if you use align-items — which you’re going to want to do in any case.

A fallback

(After writing this fallback I found that Ana Tudor wrote pretty much the same one in her article. I mean, why do I even bother competing with scarily smart people like her? But I came by it independently, honest.)

p.test { --aspectRatio: 16/9; --padding: 0.5em; border: 1px solid; padding: var(--padding); aspect-ratio: var(--aspectRatio); box-sizing: border-box; box-shadow: 0.3em 0.3em 0.5em darkgray; } @supports not (aspect-ratio: 16/9) { p.test { padding: 0; padding-top: calc((1 / (var(--aspectRatio)))*100%); position: relative; } p.test > span { display: block; position: absolute; top: var(--padding); left: var(--padding); } p.test::after { content: " (aspect-ratio not supported; falling back)"; position: absolute; bottom: 0; right: var(--padding); } } function initBoxes() { let controls = document.querySelector('p.controls'); let element = document.querySelector('p.test') controls.addEventListener('click',function(e) { if (e.target.nodeName !== 'INPUT') return; let newAspectRatio = e.target.value; element.style.setProperty('--aspectRatio',newAspectRatio); // element.style.setProperty('--aspectHeight',newAspectRatio[1]); },false); window.initBoxes = function () {} }

5/1 3/1 16/9 4/3 9/16

Since browser support isn’t quite there yet we need to continue to use the old padding-top trick as a fallback, as I do in this paragraph. It is supposed to have an aspect ratio even in browsers that do not support aspect-ratio.

The core of the fallback is the following CSS. Fair warning: this solution is only lightly tested; I went from conception to successful execution in about 30 minutes — though I spent 90 more minutes on a custom property issue.

The extra <span> is ugly, but I don’t see a way around it. If your aspect-ratioed boxes do not contain text you can leave it out.

<p class="test"><span>The text</span></p> p.test { --aspectRatio: 16/9; --padding: 0.5em; border: 1px solid; padding: var(--padding); aspect-ratio: var(--aspectRatio); box-sizing: border-box; } @supports not (aspect-ratio: 16/9) { p.test { padding: 0; padding-top: calc((1 / (var(--aspectRatio)))*100%); position: relative; } p.test > span { display: block; position: absolute; top: var(--padding); left: var(--padding); } }

Store the desired aspect ratio in --aspectRatio. Set aspect-ratio to that value. If the browser doesn’t support aspect-ratio, set the padding-top to 1 / the aspect ratio as a percentage. The script that runs in this page changes the value of --aspectRatio.

The trick here is that percentual padding is calculated relative to the parent element’s width. If the box spans its parent’s entire width, you can take that width, multiply it by 1 divided by the aspect ratio (so for instance 9/16th when the aspect ratio is 16/9) and convert the result to a percentage. Now the padding stretches the box to the desired aspect ratio.

If the box has any real content we have to wrap it in an extra HTML element and give that element position: absolute so that it does not influence the box’s height. Then we place it in the box, with a top and left coordinate equal to the box’s padding. Now the text appears to flow naturally. You don’t need this trick if the box only has a background image or gradient; those ignore padding anyway.

Or you can wait a few months until all browsers support aspect-ratio. It won’t be long now.

I may write a separate article about the incredible number of brackets we need in the padding-top line.

aspect-ratio and grid

Tue, 05/11/2021 - 2:42am

I’m currently investigating the new aspect-ratio declaration and plan to write an article about it. However, I got stuck on aspect ratios in a grid context. Chrome/Safari and Firefox do something different here, and I understand neither approach. So I hope I can get some help.

aspect-ratio is currently supported by Chrome 90, by Firefox 88 with the correct flag enabled, and by Safari Technology Preview. I tested mostly in the first two — for complicated reasons I cannot install STP right now, but a kind Twitter follower sent me a few screenshots. It behaves as Chrome.

First, a general remark. aspect-ratio is intentionally a fairly weak declaration. It gives way if other constraints on boxes make the requested aspect ratio impossible. Take this example:

.my-box { width: 100px; height: 50px; aspect-ratio: 16/9; }

The box has a fixed width and height, and they overrule the aspect-ratio. The box will thus have a 2/1 aspect ratio, as dictated by its width and height, and not a 16/9 one.

.outer-box { gap: 1em; } .inner-box { border: 0; outline: 1px solid black; background-size: 90%; background-position: center; background-repeat: no-repeat; } .flex div { flex-basis: 30%; flex-grow: 1; } .ar169 { aspect-ratio: 16/9; background-image: url(/blog/pix/ar-16-9.png); } .ar43 { aspect-ratio: 4 / 3; background-image: url(/blog/pix/ar-4-3.png); } output { display: block; } .inner-box code { background: none; color: inherit; } .minheight .inner-box { min-height: 100px; } .mincontent { height: min-content; } Flexbox

With that in mind, let’s first look at aspect-ratio in a flexbox environment. I think I understand what’s going on here, and the browsers all do the same, so this is a good reference point for the grid problems we’ll encounter later.

Flex items take their width from the flexbox environment. In my example they have a flex-basis: 30%, but they could also have a width or even no width/flex-basis definition at all. In all cases the flexbox algorithm decides on the width of each item.

Once the width has been determined, it’s time for the height. Let’s assume it’s not set. In flexbox, height: auto means not “as high as you need to be for your content” but “as high as the highest box in your row.”

That is, naturally flexbox would give the boxes an equal width (because that’s what my flex declarations say) and an equal height (because that always happens in flexbox). Apparently, this counts as a set height for the aspect-ratio algorithm.

As a result the 16/9 value is ignored because the 4/3 results in a larger height, and this value is therefore the one that determines the height of the entire row.

As you see, the third box in this example does have the correct aspect ratio. That’s because it has an explicit height: min-content: set your height to whatever your content needs, and, more importantly, ignore the row height of the flex box. This, apparently, gives the aspect ratio algorithm the opening it needs to set the height to the one requested by the aspect-ratio: 16/9.

I’m not sure if my reasoning is right. I am very certain that this works in all browsers, though, so you can use height: min-content in production straight away. (max-content also works. There’s no real difference between the two in height declarations.)

flex aspect-ratio and min-content The problem: grid

Now we get to the problem: grid. To follow along, please look at the example below in Firefox 88 with the aspect-ratio flag on, and in either Chrome or Safari Technology Preview.

I expected grid to more or less behave the same as flexbox: the widths are set by the grid, the heights by the row height, and getting the proper aspect ratio would require height: min-content. That last clause is correct: the min-content trick works as it does in flexbox. It’s the behaviour of th 16/9 box without min-content that surprises me.

Here, again, the third box has height: min-content and takes the correct aspect ratio, which means not obeying the row height, in all browsers.

grid aspect-ratio and min-content

Firefox first. All boxes get their correct aspect ratio and they all have the same width, as the repeat: (3,1fr) grid template dictates. That means their height differs. More importantly, the grid container box now becomes only as high as is necessary to contain the items as they would have been without their aspect ratio.

I am 99% certain that the grid container behaviour is a bug. I am less certain whether the aspect-ratio being obeyed is also a bug.

In Chrome, the second and third box behave as expected: the last box becomes less high than the row height because of height: min-content, and the second box dictates the row height with its 4/3 aspect ratio.

But what’s up with the first box? It appears that it takes the row height as a given, but then sets the width to the value dictated by the 16/9 aspect ratio, ignoring the fact that this box now overflows its proper grid placement. Is this a bug? Or does height count for more than width in a grid context? I don’t know.

In the second example all grid items have min-height: 100px. In all browsers they they calculate their width from their aspect ratio. Thus they break the grid-defined widths. This is understandable, given that the explicit height declaration is “stronger” than the implied widths from the grid definition. (Or rather: I devoutly hope I’m right here and not talking nonsense.)

grid aspect-ratio and min-height: 100px;

Thus maybe Firefox on the one hand and Chrome/Safari on the other are not as far apart as one would think from the first grid example. Still, something is buggy in that example. I just can’t figure out what it is.

Stumped. Please help.

Two options for using custom properties

Tue, 05/04/2021 - 4:16am

Recently I interviewed Stefan Judis for my upcoming book. We discussed CSS custom properties, and something interesting happened.

We had a period of a few minutes where we were talking past one another, because, as it turns out, we have completely opposite ideas about the use of CSS custom properties. I had never considered his approach, and I found it interesting enough to write this quick post.

Option 1

Take several site components, each with their own link and hover/focus colours. We want to use custom properties for those colours. Exactly how do we do that?

Before my discussion with Stefan that wasn’t even a question for me. I would do this:

.component1 { --linkcolor: red; --hovercolor: blue; } .component2 { --linkcolor: purple; --hovercolor: cyan; } a { color: var(--linkcolor); } a:hover,a:focus { color: var(--hovercolor) }

I set the normal and hover/focus colour as a custom property, and leave the definition of those properties to the component the link appears in. The first and second component each define different colours, which are deployed in the correct syntax. Everything works and all’s well with the world.

As far as I can see now this is the default way of using CSS custom properties. I wasn’t even aware that another possibility existed.

Option 2

Stefan surprised me by doing almost the complete opposite. He uses only a single variable and changes its value where necessary:

.component1 { --componentcolor: red; } .component1 :is(a:hover,a:focus) { --componentcolor: blue; } .component2 { --componentcolor: purple; } .component2 :is(a:hover,a:focus) { --componentcolor: cyan; } a { color: var(--componentcolor) }

At first I was confused. Why would you do this? What’s the added value of the custom property? Couldn’t you just have entered the colour values in the component styles without using custom properties at all?

Well, yes, you could. But that’s not Stefan’s point.

The point

In practice, component definitions have way more styles than just colours. There’s a bunch of box-model properties, maybe a display, and possibly text styling instructions. In any case, a lot of lines of CSS.

If you use custom properties only for those CSS properties that will change you give future CSS developers a much better and quicker insight in how your component works. If the definition uses a custom property that means the property may change in some circumstances. If it uses a fixed definition you know it’s a constant.

Suppose you encounter this component definition in a codebase you just inherited:

.component { --color: red; --background: blue --layout: flex; --padding: 1em; --borderWidth: 0.3em; display: var(--layout); color: var(--color); background: var(--background); padding: var(--padding); border: var(--borderWidth) solid black; margin: 10px; border-radius: 2em; grid-template-columns: repeat(3,1fr); flex-wrap: wrap; }

Now you essentially found a definition file. Not only do you see the component’s default styles, you also see what might change and what will not. For instance, because the margin and border-radius are hard-coded you know they are never changed. In the case of the border, only the width changes, not the style or the colour. Most other properties can change.

The use of display: var(--layout) is particularly revealing. Apparently something somewhere changes the component’s layout from grid to flexbox. Also, if it’s a grid it has three equal columns, while if it’s a flexbox it allows wrapping. This suggests that the flexbox layout is used on narrower screens, switching to a grid layout on wider screens.

Where does the flexbox change to a grid? As a newbie to this codebase you don’t know, but you can simply search for --layout: grid and you’ll find it, probably neatly tucked away in a media query somewhere. Maybe there is a basic layout as well, which uses neither flexbox nor grid? Search for --layout: block and you’ll know.

Thus, this way of using custom properties is excellently suited for making readable code bases that you can turn over to other CSS developers. They immediately know what changes and what doesn’t.

Teaching aid?

There’s another potential benefit as well: this way of using custom properties, which are essentially variables, aligns much more with JavaScript’s use of variables. You set an important variable at the start of your code, and change it later on if necessary. This is what you do in JavaScript all the time.

Thus this option may be better suited to teaching CSS to JavaScripters, which remains one of my preoccupations due to the upcoming book.

Picking an option

Which option should you pick? That’s partly a matter of personal preference. Since the second option is still fairly new to me, and I rarely work on large projects, I am still feeling my way around it. Right at this moment I prefer the first way because I’m used to it. But that might change, given some extra time.

Still, I think Stefan is on to something. I think that his option is very useful in large codebases that can be inherited by other developers. I think it deserves careful consideration.

fit-content and fit-content()

Thu, 04/29/2021 - 2:28am

Today we will look at fit-content and fit-content(), which are special values for width and grid definitions. It’s ... complicated — not as a concept, but in its practical application.

.width { width: -moz-fit-content; width: fit-content; } .minwidth { min-width: -moz-fit-content; min-width: fit-content; } .maxwidth { max-width: -moz-fit-content; max-width: fit-content; } div.radios { display: grid; grid-template-columns: 1fr 1fr; } div.radios output { grid-column: span 2; } label { display: block; } .flex div { flex-basis: 30%; flex-grow: 1; } function initBoxes(obj) { initCheck(); let container = obj.querySelector('.outer-box'); initRadios(); if (container.slider && container.showComputed) { container.slider.addEventListener('input',container.showComputed,true) } function initCheck() { let check = obj.querySelector('input[type=checkbox]'); if (!check) return; let span = obj.querySelector('span.toggle'); check.onchange = function () { if (this.checked) span.style.display = ''; else span.style.display = 'none'; } } function initRadios() { let radioRoot = obj.querySelector('div.radios'); if (!radioRoot) return; let radios = radioRoot.querySelectorAll('input[type=radio]') let output = radioRoot.querySelector('output'); radios.forEach(function(radio){ let val = radio.value; radio.parentNode.innerHTML += ' ' + val; }) radioRoot.onclick = function (e) { container.style.gridTemplateColumns = e.target.value; container.showComputed(); } container.showComputed = function() { output.innerHTML = 'Computed ' + window.getComputedStyle(container).getPropertyValue('grid-template-columns').split(' ').join(' / ') } } } min- and max-content

Before looking at fit-content we have to briefly review two other special width values: min-content and max-content. You need those in order to understand fit-content.

Normally (i.e. with width: auto defined or implied) a box will take as much horizontal space as it can. You can change the horizontal space by giving width a specifc value, but you can also order the browser to determine it from the box’s contents. That’s what min-content and max-content do.

Try them below.

min-content and max-content width: auto: as much as possible width: max-content width: min-content width: max-content with a long text that runs the danger of disappearing right out of the browser window if it continues for much longer
  • min-content means: the minimal width the box needs to contain its contents. In practice this means that browsers see which bit of content is widest and set the width of the box to that value. The widest bit of content is the longest word in a text, or the widest image or video or other asset.
  • max-content means: the width the box needs to contain all of its contents. In the case of text this means that all text is placed on one line and the box becomes as wide as necessary to contain that entire line. In general this is not a desirable effect. The largest bit of content may also be an image or video other asset; in that case browsers use this width to determine the box’s width.

If you use hyphens: auto or something similar, the browser will break the words at the correct hyphenation points before determining the minimal width. (I turned off hyphenation in the examples.)

Quick Chromia/Android rabbit hole

All Chromium-based browsers on Android (tested in Chrome (v90), Samsung Internet (v87), and Edge (v77)) break off the 'width: max-content' text in the example above at the dash, and thus take the 'width: max-' as the max-content, provided the page does NOT have a meta viewport. No other browser does this — and that includes Chrome on Mac.

Also, Chromia on Android make the font-size a tad smaller when you resize the boxes below the maximum possible width. I will ignore both bugs because this article is about fit-content, and not about these rabbit holes.

These bugs do NOT occur in UC (based on Chromium 78). Seems UC is taking its own decisions here, and is impervious to these particular bugs.

fit-content

Now that we understand these values we also understand fit-content. It is essentially a shorthand for the following:

box { width: auto; min-width: min-content; max-width: max-content; }

Thus the box sizes with its containing box, but to a minimum of min-content and to a maximum of max-content.

fit-content as width, min-width, and max-width width: fit-content: trying to find my fit min-width: fit-content max-width: fit-content

I’m not sure if this effect is useful outside a grid or flexbox context, but it’s here if you need it.

fit-content as min- or max-width

You can also use fit-content as a min-width or max-width value; see the example above. The first means that the width of the box varies between min-content and auto, while the second means it varies between 0 and max-content.

I find this fairly useless and potentially confusing. What you really mean is min-width: min-content or max-width: max-content. If that’s what you mean, say so. Your CSS will be clearer if you do.

So I believe that it would be better not to use fit-content for min-width or max-width; but only for width.

-moz-

Unfortunately, while fit-content works in all other browsers, Firefox still needs a vendor prefix. So the final code becomes:

box { width: -moz-fit-content; width: fit-content; }

(These prefixes get harder and harder to defend as time goes by. fit-content has perfectly fine cross-browser support, so I don’t see why Firefox doesn’t just go over to the regular variant.)

fit-content in flexbox and grid: nope

fit-content does not work in flexbox and grid. In the example below the centre box has width: fit-content; it does not work. If it worked the middle box would have a width of max-content; i.e. as small as it needs to be to contain its text.

Flexbox with fit-content Test content fit-content Test content

The final example on this page has a test where you can see grid doesn’t understand this keyword, either.

Note that grid and flex items have min-width: min-content by default, as you can see in the example above.

fit-content()

Let’s go to the more complex part: fit-content(). Although it’s supposed to work for a normal width, it doesn’t.

fit-content and fit-content() as width width: fit-content: trying to find my fit width: fit-content(200px) Grid

You can use fit-content(value) in grid templates, like:

1fr fit-content(200px) 1fr Grid with fit-content(200px) Test content fit-content(200px) Test content

It means

1fr min(max-content-size, max(min-content, 200px)) 1fr

The max() argument becomes min-content or 200 pixels, whichever is larger. This is then compared to the maximum content size, which is the actual width available due to the constraints of the grid, but with a maximum of max-content. So the real formula is more like this one, where available-size is the available width in the grid:

1fr min(min(max-content,available-size), max(min-content, 200px)) 1fr

Some syntactic notes:

  • We’re talking fit-content() the function here. fit-content the keyword does not work in grid definitions.
  • Here Firefox does not need -moz-. Go figure.
  • fit-content() needs an argument; an empty function does not work. Also, an argument in fr units is invalid.
  • MDN mentions a value fit-content(stretch). It does not work anywhere, and isn’t referred to anywhere else. I assume it comes from an older version of the spec.

I tested most of these things in the following example, where you can also try the bits of syntax that do not work — maybe they’ll start working later.

And that’s fit-content and fit-content() for you. It’s useful in some situations.

Below you can play around with fit-content() in a grid.

Grid with controls

Set grid-template-columns: to

Test content fit-content() with some more text show extra text

CSS terminology question

Thu, 03/25/2021 - 1:32am

I need to figure out how to call a pair of fundamental CSS concepts in my upcoming book and would like to hear which option you think is best — or least confusing.

When introducing width and height I explain that by default width takes as much horizontal space as it can, while height takes as little vertical space as possible. This leads to a discussion of these two opposed models that I excerpt below.

My question is: which names do I give to these models?

  1. The excerpt uses the names inside-out and outside-in — the ones I decided to use initially. However, a Twitter thread revealed two other good options:
  2. context-based and content-based
  3. extrinsic and intrinsic size, which I think are the terms used in the specifications. I'm afraid they may seem daunting to novices — you need to know what extrinsic and intrinsic mean.

Results: Content- and context-base 130 votes (62%); intrinsic and extrinsic 53 votes (25%); inside-out and outside-in 26 votes (12%).

So the terms will be content-based and context-based. Duly noted.

Excerpt from the Flow chapter

Here follows an excerpt from my current draft.

[end of previous section]

We’ll get back to setting widths and heights later [in the book]. For now it’s enough to note that their default behavior is quite different: width takes as much horizontal space as it can, while height takes as little vertical space as it can. Understanding this difference is key to understanding the browsers’ default layout.

Inside-out and outside-in

An important purpose of any layout module is figuring out what dimensions blocks should take. Why does a block have the width and height that it has? How do browsers determine that?

There are two models for determining block sizes: the outside-in model and the inside-out model. Width works outside-in, while height works inside-out. What does that mean?

In the outside-in model the block looks at the context outside itself to figure out which dimensions it should have. For instance, the default width of a paragraph is determined by the width of its container. If that container width changes, for instance because the user resizes the browser window, the width of the paragraphs changes in reaction to these factors outside themselves. Grid layout is another example of outside-in: it gives a central definition of the columns and rows that all blocks obey. If that definition changes, the blocks’ dimensions also change, regardless of their content.

In the inside-out model the block looks at its content to figure out which dimensions it should take. For instance, the default height of a paragraph is determined by the amount of text it contains. If a script adds more text, the paragraph’s height grows in reaction to these factors inside it. Flexbox layout is another example of inside-out: all individual items in a flexbox look at their contents first to determine their width. If the content of an item changes, the item’s width also changes, regardless of its neighbors.

If a script adds text to a paragraph its width stays the same. Width does not concern itself with what happens inside the paragraph; only with external factors.

Inside-out is more complex than outside-in because indirectly it may also react to outside factors. When the user makes the browser window narrower the width of the paragraphs decreases, which means less text fits on one line, which means the text needs more lines, which means the height of the paragraphs increases. Height is still inside-out, though: the reason the height changes is that the contents of the paragraphs need more space.The fact that they need more space because of the outside-in changes to the width is irrelevant to the height calculation.

As a general rule, when you create a layout you set outside-in dimensions such as width and grid fairly strictly, while you generally leave inside-out dimensions such as height and the width of flex items alone. Like any general rule this one has plenty of exceptions, and we'll encounter some of them in later chapters. Still, if you're unfamilier with CSS layout I advise you to stick to setting outside-in sizes, and allow the browser to set inside-out sizes for itself. That will make your life a lot easier.

Excerpt ends.

Progressive enhancement and accessibility redux

Mon, 03/08/2021 - 3:00am

A while ago I asked about the exact relationship between progressive enhancement and accessiblity. What were the responses?

The resulting Twitter thread was interesting, and basically boiled down to this:

Progressive enhancement is an important accessibility tool.

I can agree with that without reservation, but still had a nagging feeling that this was not the entire story.

Then I received a private communication from Josh Fremer that connected the dots for me. His mail is worth quoting at length — with permission:

I think of [progressive enhancement and accessibility] as the same process, but with different foci. Accessibility aims to optimize an experience across a spectrum of user capabilities. Progressive enhancement aims to optimize an experience across a spectrum of user _agent_ capabilities.

Indeed, if you broaden the definition of "user agent" to include a user's physiology, I think the concepts become nearly identical.

What is the application of color to a website if not a progressive enhancement targeting user agents that can discern colors? Whether the "agent" in this case is the electronic device in the user's hands or the cells in their eyes is kind of moot. The principles of both PE and accessibility require us to consider the user who is unable to receive color information.

Conversely, what is the inability to process javascript other than a "disability" of the user agent? We would probably call handling this case a PE concern, but what If the user has disabled javascript in order to mitigate a physical disability? The capabilities of the user agent have been modified to better match the capabilities of the user and now it starts to look like an accessibility issue.

In closing a fun little thought experiment is to imagine a sci fi future in which users can plug computers directly into their brains, or swap their personalities into different bodies with different capabilities. This further blurs the line between what we traditionally call a "user agent" and a user's innate disabilities. "This web site is best viewed in a body with 20/20 vision and quick reflexes."

Food for thought.

(Josh promised to write an article about his idea, which I’ve never seen expressed like this before. If you have seen a similar argument, please let me know. Once Josh’s article is live I’ll include a link in this post.)

Progressive enhancement and accessibility

Thu, 02/18/2021 - 1:29am

This article asks a question I don’t know the answer to: What is the exact relationship between progressive enhancement and accessiblity?

I mean, there is considerable overlap between the two. Good progressive enhancement can help accessibility, while accessibility can guide progressive enhancement decisions.

When I created the progressive enhancement reading list for my PE course in March I asked for good articles. I got a few submissions that I eventually rejected because they were about an accessibility problem that isn’t (quite) PE. Still, these submissions indicate that PE problems can be confused with accessibility ones. We instinctively we feel there’s considerable overlap between the two.

Still, they are not the same. You can have an thorny progressive enhancement problem that poses no accessibility problems, and you can have an accessibility problem that bears no relation to progressive enhancement.

The problem is that I can’t quite put my finger on where progressive enhancement and accessibility meet. Well, they can meet in individual problems, but that’s not what I mean. I’m looking for serious theoretical thought, and so far I haven’t found any. Also, my brain refuses to throw out a workable definition and stays stubbornly silent.

So can anyone define the relationship between progressive enhancement and accessibility? You can reach me via Twitter or my contact form.

Progressive Enhancement reading list 2021

Thu, 02/18/2021 - 1:23am

In March I am going to teach the Progressive Enhancement course of the Web minor at university for the third time. I decided to expand the reading list for this course, and here I present the version I’m going to use in 2021.

img { max-width: 100%; } Defining progressive enhancement

Let’s start at the original definition of progressive enhancement.

In 2008 Aaron Gustafson published Understanding Progressive Enhancement at A List Apart, and placed progressive enhancement at the forefront of web developer thinking. Aaron did not invent the concept — he points to prior art (slides) by Steve Champeon and Nick Finck. Still, Aaron’s article popularised the concept.

Aaron compares progressive enhancement to its precursor graceful degradation and finds that PE is the better approach — something that has become generally accepted since then. He also described progressive enhancement comprehensively, and we essentially still follow his definition.

Chris Taylor sees progressive enhancement as technical credit, the opposite of the technical debt you incur when you quickly insert some hacks to get the system to work and solemnly resolve to fix them “later.” (Hint: later never comes.) PE is the opposite: it prevents problems from arising, and thus saves you time in the future. The article also contains a useful overview of progressive enhancement principles.

Progressive enhancement also helps you combat browser incompatibilities. Justin Crawford, Chris Mills, and Ali Spivak make the web work for everyone by addressing cross-browser problems and their causes (which mostly come down to web developers not quite knowing what they’re doing). Better browser compatibility leads to better accessibility, and also to better results, since users will switch sites if they encounter problems. The best way to get these results is to practice PE. (And there we go again: PE and accessibility are clearly related. But how exactly?)

Their browser market share notes are worth repeating. Although Chrome is the largest browser, in January 2021 it still has only 63% of the market according to StatCounter. That sounds like a lot, but it means 37% of your users do not use Chrome. Don’t leave them out in the cold.

Does this sound theoretical? Let’s get practical. Robin Whittleton explains why we use progressive enhancement to build GOV.UK and walks you through the decisions. This is still a high-level overview, but it was actually used in an actual major website. We’ll see gov.uk in action later in this reading list.

Progressive enhancement and UX

The Nielsen Norman Group discusses the role of enhancement in web design and shows that progressive enhancement is useful in non-technical contexts as well. Where the previous articles focused on technical issues, this one gives a good theoretical overview of PE from a UX perspective.

What is progressive enhancement’s purpose in user interface design, and how do you decide which functionalities should be progressively enhanced? The article offers a few hardware examples, for instance the mouse wheel, which was a progressive enhancement when it was introduced, and still is, because touchscreen devices as well as keypads lack a wheel (and, in fact, a mouse). Input modes are ruled by PE, too.

Two excellent general rules are worth quoting in full:

  1. Don’t add other enhancements that provide the same functionality. [...] You want to give people the chance to practice and strengthen the enhancement; if you add other enhancements for the same interface functionality, it will be less likely that any one of them will get enough use.
  2. Be consistent. Use the same enhancement everywhere you can. That is, try to give users many opportunities to practice and thus strengthen the association between the enhancement and its effect in the interface. (Remember, repetition is the mother of retention.) Ideally, designers should use the same enhancements across many different sites, to allow people to learn them.

Aaron Walter created the Hierarchy of User Needs model, which forms the basis of a theory of user delight: why usability is the foundation for delightful experiences, also by the Nielsen Norman Group (image below borrowed from this article).

Delight is all very well, but without a solid foundation of usability, reliability, and functionality it falls flat. If something looks gorgeous but is incomprehensible, doesn’t always work, or doesn’t work at all, it is useless. The relationship between the four layers of user needs is pure progressive enhancement: the next layer is only useful if the layers below it work, and for each functionality you have to decide in which layer(s) to put it.

The PE layers

Let’s return to the technical aspects of progressive enhancement.

In order to progressively enhance a site you first need to define its core functionality. As Andy Bell says, a minimum viable experience makes for a resilient, inclusive website or app. He argues that porting the idea of a minimum viable product to the website experience is a good starting point for your PE project. Which core functionalities are required to give any user the opportunity to finish the task at hand? How do we identify a minimum viable experience, and how do we layer JavaScript functionalities on top of them?

Speaking of layers, Hidde de Vries explains why it's good for users that HTML, CSS and JS are separate languages. He argues that separation of concerns between structure, presentation, and behaviour remains a good thing. This impacts progressive enhancement by forcing you to think about which functionalities you should implement in which layer(s).

So let’s go through the layers.

Layer 1: HTML

Terence Eden discusses the unreasonable effectiveness of simple HTML by telling a story about a user being forced to work on a government-related task on the PlayStation browser (which is — let’s call it not good and leave it at that). The user managed to do what she set out because the site she needed had a simple, good HTML structure that works in any browser. This is what core functionality and minimum viable experience are all about. And you just need HTML to do it.

Dave Rupert created an indispensable list of HTML: the inaccessible parts, where he gives a round-up of accessibility problems with HTML elements. If you want to use any of these HTML elements you should apply the principles of progressive enhancement to make sure they work everywhere. This shows that HTML is not necessarily restricted to the lowest, functional layer but can also enhance (or detract from) the reliable, functional, and pleasurable layers. (Also, it shows yet again there is a close relationship between progressive enhancement and accessibility.)

Léonie Watson wrote a short note on progressive ARIA where she shows that changing the role of an element (here a link to a button; why?; but this happens all the time) also means you should make sure users can click it with the Space key. This is possible for buttons, but not for links, and if you change a link to a button the browser doesn’t automatically add the Space key. So you have to progressively enhance it.

Layer 2: CSS

I have not been able to find specific articles about CSS and progressive enhancement, although they must be out there. I’ll update this section when I find any.

Layer 3: JavaScript

Then we come to the elephant in the room: JavaScript. I mean, everyone has JavaScript, right? As Stuart Langridge shows, this is untrue. Even if the user doesn’t disable JavaScript explicitly there may be many reasons why they don’t have JavaScript.

Incidentally, measuring the number of users who turn off JavaScript is very easy. Just tweet something like “No one turns off JavaScript anyway,” wait for about a day for people to tell you in no uncertain terms they do in fact turn off JavaScript, count these reactions, and you know how many people turn off JavaScript.

Given that JavaScript is a progressive enhancement challenge, not only because it can be absent but also because “modern” web development uses such an incredible amount of it, Jeremy Keith’s 2014 call to be progressive remains relevant today. This article summarises an interesting discussion from those days and contains a lot of excellent points about how to view progressive enhancement, browsers, JavaScript, and more.

Jeremy calls for more server-side components:

Personally, I try not to completely reinvent all the business logic that I’ve already figured out on the server, and then rewrite it all in JavaScript. I prefer to use JavaScript—and specifically Ajax—as a dumb waiter, shuffling data back and forth between the client and server, where the real complexity lies.

I also think that building in this way will take longer …at first. But then on the next project, it takes less time. And on the project after that, it takes less time again. From that perspective, it’s similar to switching from tables for layout to using CSS, or switching from building fixed-with sites to responsive design: the initial learning curve is steep, but then it gets easier over time, until it simply becomes normal.

Did anyone listen to him in the years since 2014? (Hint: no.)

Chris James gives a quick overview of the past web, when things were still good, and describes the Web I Want. Then he gives a useful overview of why we don’t need that much JavaScript in our pages: HTML is already perfectly suited for showing content pages.

Finally, Thomas Steiner explains how to progressively enhance your Progressive Web App by digging into layers of JavaScript support in an advanced article. Assuming JavaScript is present, he gives an example where a simple but insufficient JavaScript is enhanced when the browser supports the File System Access API. He then gives a few other examples of modern APIs and how to feature-detect them. This is on a site by the Chrome devrel team, so it’s Chrome-centric in the features it treats, but the techniques Thomas teaches are applicable anywhere.

Practical examples

Jim Nielsen shows how he is progressively enhancing a small widget. This is a simple, first PE mini-project that is well suited to understand the basic concepts. He uses JavaScript to progressively enhance a static page, and the key takeaway is how the script changes the page’s static content.

Andy Bell (again) studies the power of progressive enhancement. He discusses a (no longer existing) web app in PE terms: start with the core functionality, then layer extras on top. Contains an excellent, simple bit of advice on how to handle the fact that some browsers don’t support CSS grid. This article highlights practical PE decisions and how they work.

Chris Scott created a practical Progressive Enhancement and Data Visualizations example project where he progressively enhances a timeline by first creating a solid HTML structure, then adds some presentational CSS, and then layers JavaScript and SVG on top that changes the presentation considerably.

That’s it. That was the reading list.

If you know of more good articles for the next version you can reach me via Twitter or my contact form.

©2003 - Present Akamai Design & Development.