Journal tags: writingmodes

2

Supporting logical properties

I wrote recently about making the switch to logical properties over on The Session.

Initially I tried ripping the band-aid off and swapping out all the directional properties for logical properties. After all, support for logical properties is green across the board.

But then I got some reports of people seeing formating issues. These people were using Safari on devices that could no longer update their operating system. Because versions of Safari are tied to versions of the operating system, there was nothing they could do other than switch to using a different browser.

I’ve said it before and I’ll say it again, but as long as this situation continues, Safari is not an evergreen browser. (I also understand that problem lies with the OS architecture—it must be incredibly frustrating for the folks working on WebKit and/or Safari.)

So I needed to add fallbacks for older browsers that don’t support logical properties. Or, to put it another way, I needed to add logical properties as a progressive enhancement.

“No problem!” I thought. “The way that CSS works, I can just put the logical version right after the directional version.”

element {
  margin-left: 1em;
  margin-inline-start: 1em;
}

But that’s not true in this case. I’m not over-riding a value, I’m setting two different properties.

In a left-to-right language like English it’s true that margin-inline-start will over-ride margin-left. But in a right-to-left language, I’ve just set margin-left and margin-inline-start (which happens to be on the right).

This is a job for @supports!

element {
  margin-left: 1em;
}
@supports (margin-inline-start: 1em) {
  element {
    margin-left: unset;
    margin-inline-start: 1em;
  }
}

I’m doing two things inside the @supports block. I’m applying the logical property I’ve just tested for. I’m also undoing the previously declared directional property.

A value of unset is perfect for this:

The unset CSS keyword resets a property to its inherited value if the property naturally inherits from its parent, and to its initial value if not. In other words, it behaves like the inherit keyword in the first case, when the property is an inherited property, and like the initial keyword in the second case, when the property is a non-inherited property.

Now I’ve got three CSS features working very nicely together:

  1. @supports (also known as feature queries),
  2. logical properties, and
  3. the unset keyword.

For anyone using an up-to-date browser, none of this will make any difference. But for anyone who can’t update their Safari browser because they can’t update their operating system, because they don’t want to throw out their perfectly functional Apple device, they’ll continue to get the older directional properties:

I discovered that my Mom’s iPad was a 1st generation iPad Air. Apple stopped supporting that device in iOS 12, which means it was stuck with whatever version of Safari last shipped with iOS 12.

Let’s get logical

I was refactoring some CSS on The Session over the weekend. I thought it would be good to switch over to using logical properties exclusively. I did this partly to make the site more easily translatable into languages with different writing modes, but mostly as an exercise to help train me in thinking with logical properties by default.

All in all, it went pretty smoothly. You can kick the tyres by opening up dev tools on The Session and adding a writing-mode declaration to the body or html element.

For the most part, the switchover was smooth. It mostly involved swapping out property names with left, right, top, and bottom for inline-start, inline-end, block-start, and block-end.

The border-radius properties tripped me up a little. You have to use shorthand like border-start-end-radius, not border-block-start-inline-end-radius (that doesn’t exist). So you have to keep the order of the properties in mind:

border-{{block direction}}-{{inline-direction}}-radius

Speaking of shorthand, I also had to kiss some shorthand declarations goodbye. Let’s say I use this shorthand for something like margin or padding:

margin: 1em 1.5em 2em 0.5em;

Those values get applied to margin-top, margin-right, margin-bottom, and margin-left, not the logical equivalents (block-start, inline-end, block-end, and inline-start). So separate declarations are needed instead:

margin-block-start: 1em;
margin-inline-end: 1.5em;
margin-block-end: 2em;
margin-inline-start: 0.5em;

Same goes for shorthand like this:

margin: 1em 2em;

That needs to be written as two declarations:

margin-block: 1em;
margin-inline: 2em;

Now I’ve said it before and I’ll say it again: it feels really weird that you can’t use logical properties in media queries. Although as I said:

Now you could rightly argue that in this instance we’re talking about the physical dimensions of the viewport. So maybe width and height make more sense than inline and block.

But along comes the new kid on the block (or inline), container queries, ready to roll with container-type values like inline-size. I hope it’s just a matter of time until we can use logical properties in all our conditional queries.

The other place where there’s still a cognitive mismatch is in transforms and animations. We’ve got a translateX() function but no translate-inline(). We’ve got translateY() but no translate-block().

On The Session I’m using some JavaScript to figure out the details of some animation effects. I’m using methods like getBoundingClientRect(). It doesn’t return logical properties. So if I ever want to adjust my animations based on writing direction, I’ll need to fork my JavaScript code.

Oh, and one other thing: the aspect-ratio property takes values in the form of width/height, not inline/block. That makes sense if you’re dealing with images, videos, or other embedded content but it makes it really tricky to use aspect-ratio on elements that contain text. I mean, it works fine as long as the text is in a language using a top-to-bottom writing mode, but not for any other languages.