Journal tags: styling

30

Codestin Search App

Underlines and line height

I was thinking about something I wrote yesterday when I was talking about styling underlines on links:

For a start, you can adjust the distance of the underline from the text using text-underline-offset. If you’re using a generous line-height, use a generous distance here too.

For some reason, I completely forgot that we’ve got a line-height unit in CSS now: lh. So if you want to make the distance of your underline proportional to the line height of the text that the link is part of, it’s easy-peasy:

text-underline-offset: 0.15lh;

The greater the line height, the greater the distance between the link text and its underline.

I think this one is going into my collection of CSS snippets I use on almost every project.

Style your underlines

We shouldn’t rely on colour alone to indicate that something is interactive.

Take links, for example. Sure, you can give them a different colour to the surrounding text, but you shouldn’t stop there. Make sure there’s something else that distinguishes them. You could make them bold. Or you could stick with the well-understood convention of underlying links.

This is where some designers bristle. If there are a lot of links on a page, it could look awfully cluttered with underlines. That’s why some designers would rather remove the underline completely.

As Manu observed:

I’ve done a lot of audits in the first half of this year and at this point a believe that designing links without underlines is a kink. The idea that users don’t understand that links are clickable arouses some designers. I can’t explain it any other way.

But underlining links isn’t the binary decision it once was. You can use CSS to style those underlines just as you’d style any other part of your design language.

Here’s a regular underlined link.

For a start, you can adjust the distance of the underline from the text using text-underline-offset. If you’re using a generous line-height, use a generous distance here too.

Here’s a link with an offset underline.

If you’d rather have a thinner or thicker underline, use text-decoration-thickness.

Here’s a link with a thin underline.

The colour of the underline and the colour of the link don’t need to be the same. Use text-decoration-color to make them completely different colours or make the underline a lighter shade of the link colour.

Here’s a link with a translucent underline.

That’s quite a difference with just a few CSS declarations:

text-underline-offset: 0.2em;
text-decoration-thickness: 1px;
text-decoration-color: oklch(from currentColor l c h / 50%);

If that still isn’t subtle enough for you, you could even use text-decoration-style to make the underline dotted or dashed, but that might be a step too far.

Here’s a link with a dotted underline.

Whatever you decide, I hope you’ll see that underlines aren’t the enemy of good design. They’re an opportunity.

You should use underlines to keep your links accessible. But you should also use CSS to make those underlines beautiful.

CSS snippets

I’ve been thinking about the kind of CSS I write by default when I start a new project.

Some of it is habitual. I now use logical properties automatically. It took me a while to rewire my brain, but now seeing left or top in a style sheet looks wrong to me.

When I mentioned this recently, I had some pushback from people wondering why you’d bother using logical properites if you never planned to translate the website into a language with a different writing system. I pointed out that even if you don’t plan to translate a web page, a user may still choose to. Using logical properties helps them. From that perspective, it’s kind of like using user preference queries.

That’s something else I use by default now. If I’ve got any animations or transitions in my CSS, I wrap them in prefers-reduced-motion: no-preference query.

For instance, I’m a huge fan of view transitions and I enable them by default on every new project, but I do it like this:

@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

I’ll usually have a prefers-color-scheme query for dark mode too. This is often quite straightforward if I’m using custom properties for colours, something else I’m doing habitually. And now I’m starting to use OKLCH for those colours, even if they start as hexadecimal values.

Custom properties are something else I reach for a lot, though I try to avoid premature optimisation. Generally I wait until I spot a value I’m using more than two or three times in a stylesheet; then I convert it to a custom property.

I make full use of clamp() for text sizing. Sometimes I’ll just set a fluid width on the html element and then size everything else with ems or rems. More often, I’ll use Utopia to flow between different type scales.

Okay, those are all features of CSS—logical properties, preference queries, view transitions, custom properties, fluid type—but what about actual snippets of CSS that I re-use from project to project?

I’m not talking about a CSS reset, which usually involves zeroing out the initial values provided by the browser. I’m talking about tiny little enhancements just one level up from those user-agent styles.

Here’s one I picked up from Eric that I apply to the figcaption element:

figcaption {
  max-inline-size: max-content;
  margin-inline: auto;
}

That will centre-align the text until it wraps onto more than one line, at which point it’s no longer centred. Neat!

Here’s another one I start with on every project:

a:focus-visible {
  outline-offset: 0.25em;
  outline-width: 0.25em;
  outline-color: currentColor;
}

That puts a nice chunky focus ring on links when they’re tabbed to. Personally, I like having the focus ring relative to the font size of the link but I know other people prefer to use a pixel size. You do you. Using the currentColor of the focused is usually a good starting point, thought I might end up over-riding this with a different hightlight colour.

Then there’s typography. Rich has a veritable cornucopia of starting styles you can use to improve typography in CSS.

Something I’m reaching for now is the text-wrap property with its new values of pretty and balance:

ul,ol,dl,dt,dd,p,figure,blockquote {
  hanging-punctuation: first last;
  text-wrap: pretty;
}

And maybe this for headings, if they’re being centred:

h1,h2,h3,h4,h5,h6 {
  text-align: center;
  text-wrap: balance;
}

All of these little snippets should be easily over-writable so I tend to wrap them in a :where() selector to reduce their specificity:

:where(figcaption) {
  max-inline-size: max-content;
  margin-inline: auto;
}
:where(a:focus-visible) {
  outline-offset: 0.25em;
  outline-width: 0.25em;
  outline-color: currentColor;
}
:where(ul,ol,dl,dt,dd,p,figure,blockquote) {
  hanging-punctuation: first last;
  text-wrap: pretty;
}

But if I really want them to be easily over-writable, then the galaxy-brain move would be to put them in their own cascade layer. That’s what Manu does with his CSS boilerplate:

@layer core, third-party, components, utility;

Then I could put those snippets in the core layer, making sure they could be overwritten by the CSS in any of the other layers:

@layer core {
  figcaption {
    max-inline-size: max-content;
    margin-inline: auto;
  }
  a:focus-visible {
    outline-offset: 0.25em;
    outline-width: 0.25em;
    outline-color: currentColor;
  }
  ul,ol,dl,dt,dd,p,figure,blockquote {
    hanging-punctuation: first last;
    text-wrap: pretty;
  }
}

For now I’m just using :where() but I think I should start using cascade layers.

I also want to start training myself to use the lh value (line-height) for block spacing.

And although I’m using the :has() selector, I don’t think I’ve yet trained my brain to reach for it by default.

CSS has sooooo much to offer today—I want to make sure I’m taking full advantage of it.

Style legend

There’s a new proposal for giving developers more control over styling form controls. I like it.

It’s clearly based on the fantastic work being done by the Open UI group on the select element. The proposal suggests that authors can opt-in to the new styling possibilities by declaring:

appearance: base;

So basically the developer is saying “I know what I’m doing—I’m taking the controls.” But browsers can continue to ship their default form styles. No existing content will break.

The idea is that once the developer has opted in, they can then style a number of pseudo-elements.

This proposal would apply to pretty much all the form controls you can think of: all the input types, along with select, progress, meter, buttons and more.

But there’s one element more that I wish were on the list:

legend

I know, technically it’s not a form control but legend and fieldset are only ever used within forms.

The legend element is notoriously annoying to style. So a lot of people just don’t bother using it, which is a real shame. It’s like we’re punishing people for doing the right thing.

Wouldn’t it be great if you, as a developer, had the option of saying “I know what I’m doing—I’m taking the controls”:

legend {
  appearance: base;
}

Imagine if that nuked the browser’s weird default styles, effectively turning the element into a span or div as far as styling is concerned. Then you could style it however you wanted. But crucially, if browsers shipped this, no existing content would break.

The shitty styling situation for legend (and its parent fieldset) is one of those long-standing annoyances that seems to have fallen down the back of the sofa of browser vendors. No one’s going to spend time working on it when there are more important newer features to ship. That’s why I’d love to see it sneak in to this new proposal for styling form controls.

I was in Amsterdam last week. Just like last year I was there to help out Vasilis’s students with a form-based assignment:

They’re given a PDF inheritance-tax form and told to convert it for the web.

Yes, all the excitement of taxes combined with the thrilling world of web forms.

(Side note: this time they were told to style it using the design system from the Dutch railway because the tax office was getting worried that they were making phishing sites.)

I saw a lot of the same challenges again. I saw how students wished they could specify a past date or a future date in a date picker without using JavaScript. And I saw them lamenting the time they spent styling legends that worked across all browsers.

Right now, Mason Freed has an open issue on the new proposal with his suggestion to add some more elements to consider. Both legend and fieldset are included. That gets a thumbs-up from me.

Making the new Salter Cane website

With the release of a new Salter Cane album I figured it was high time to update the design of the band’s website.

Here’s the old version for reference. As you can see, there’s a connection there in some of the design language. Even so, I decided to start completely from scratch.

I opened up a text editor and started writing HTML by hand. Same for the CSS. No templates. No build tools. No pipeline. Nothing. It was a blast!

And lest you think that sounds like a wasteful way of working, I pretty much had the website done in half a day.

Partly that’s because you can do so much with so little in CSS these days. Custom properties for colours, spacing, and fluid typography (thanks to Utopia). Logical properties. View transitions. None of this takes much time at all.

Because I was using custom properties, it was a breeze to add a dark mode with prefers-color-scheme. I think I might like the dark version more than the default.

The final stylesheet is pretty short. I didn’t bother with any resets. Browsers are pretty consistent with their default styles nowadays. As long as you’ve got some sensible settings on your body element, the cascade will take care of a lot.

There’s one little CSS trick I think is pretty clever…

The background image is this image. As you can see, it’s a rectangle that’s wider than it is tall. But the web pages are rectangles that are taller than they are wide.

So how I should I position the background image? Centred? Anchored to the top? Anchored to the bottom?

If you open up the website in Chrome (or Safari Technical Preview), you’ll see that the background image is anchored to the top. But if you scroll down you’ll see that the background image is now anchored to the bottom. The background position has changed somehow.

This isn’t just on the home page. On any page, no matter how tall it is, the background image is anchored to the top when the top of the document is in the viewport, and it’s anchored to the bottom when you reach the bottom of the document.

In the past, this kind of thing might’ve been possible with some clever JavaScript that measured the height of the document and updated the background position every time a scroll event is triggered.

But I didn’t need any JavaScript. This is a scroll-driven animation made with just a few lines of CSS.

@keyframes parallax {
    from {
        background-position: top center;
    }
    to {
        background-position: bottom center;
    }
}
@media (prefers-reduced-motion: no-preference) {
        html {
            animation: parallax auto ease;
            animation-timeline: scroll();
        }
    }
}

This works as a nice bit of progressive enhancement: by default the background image stays anchored to the top of the viewport, which is fine.

Once the site was ready, I spent a bit more time sweating some details, like the responsive images on the home page.

But the biggest performance challenge wasn’t something I had direct control over. There’s a Spotify embed on the home page. Ain’t no party like a third party.

I could put loading="lazy" on the iframe but in this case, it’s pretty close to the top of document so it’s still going to start loading at the same time as some of my first-party assets.

I decided to try a little JavaScript library called “lazysizes”. Normally this would ring alarm bells for me: solving a problem with third-party code by adding …more third-party code. But in this case, it really did the trick. The library is loading asynchronously (so it doesn’t interfere with the more important assets) and only then does it start populating the iframe.

This made a huge difference. The core web vitals went from being abysmal to being perfect.

I’m pretty pleased with how the new website turned out.

Displaying HTML web components

Those HTML web components I made for date inputs are very simple. All they do is slightly extend the behaviour of the existing input elements.

This would be the ideal use-case for the is attribute:

<input is="input-date-future" type="date">

Alas, Apple have gone on record to say that they will never ship support for customized built-in elements.

So instead we have to make HTML web components by wrapping existing elements in new custom elements:

<input-date-future>
  <input type="date">
<input-date-future>

The end result is the same. Mostly.

Because there’s now an additional element in the DOM, there could be unexpected styling implications. Like, suppose the original element was direct child of a flex or grid container. Now that will no longer be true.

So something I’ve started doing with HTML web components like these is adding something like this inside the connectedCallback method:

connectedCallback() {
    this.style.display = 'contents';
  …
}

This tells the browser that, as far as styling is concerned, there’s nothing to see here. Move along.

Or you could (and probably should) do it in your stylesheet instead:

input-date-future {
  display: contents;
}

Just to be clear, you should only use display: contents if your HTML web component is augmenting what’s within it. If you add any behaviours or styling to the custom element itself, then don’t add this style declaration.

It’s a bit of a hack to work around the lack of universal support for the is attribute, but it’ll do.

Who knows?

I love it when I come across some bit of CSS I’ve never heard of before.

Take this article on the text-emphasis property.

“The what property?”, I hear you ask. That was my reaction too. But look, it’s totally a thing.

Or take this article by David Bushell called CSS Button Styles You Might Not Know.

Sure enough, halfway through the article David starts talking about styling the button in an input type="file” using the ::file-selector-button pseudo-element:

All modern browsers support it. I had no idea myself until recently.

He’s right!

Then I remembered that I’ve got a file upload input in the form I use for posting my notes here on adactio.com (in case I want to add a photo). I immediately opened up my style sheet, eager to use this new-to-me bit of CSS.

I found the bit where I style buttons and this is the selector I saw:

button,
input[type="submit"],
::file-selector-button

Huh. I guess I did know about that pseudo-element after all. Clearly the knowledge exited my brain shortly afterwards.

There’s that tautological cryptic saying, “You don’t know what you don’t know.” But I don’t even know what I do know!

Assumption

While I’m talking about the SVGs on The Session, I thought I’d share something else related to the rendering of the sheet music.

Like I said, I use the brilliant abcjs JavaScript library. It converts ABC notation into sheet music on the fly, which still blows my mind.

If you view source on the rendered SVG, you’ll see that the path and rect elements have been hard-coded with a colour value of #000000. That makes sense. You’d want to display sheet music on a light background, probably white. So it seems like a safe assumption.

Ah, but when it comes to front-end development, assumptions are like little hidden bombs just waiting to go off!

I got an email the other day:

Hi Jeremy,

I have vision problems, so I need to use high-contrast mode (using Windows 11). In high-contrast mode, the sheet-music view is just black!

Doh! All my CSS adapts just fine to high-contrast mode, but those hardcoded hex values in the SVG aren’t going to be affected by high-contrtast mode.

Stepping back, the underlying problem was that I didn’t have a full separation of concerns. Most of my styling information was in my CSS, but not all. Those hex values in the SVG should really be encoded in my style sheet.

I couldn’t remove the hardcoded hex values—not without messing around with JavaScript beyond my comprehension—so I made the fix in CSS:

[fill="#000000"] {
  fill: currentColor;
}
[stroke="#000000"] {
  stroke: currentColor;
}

That seemed to do the trick. I wrote back to the person who had emailed me, and they were pleased as punch:

Well done, Thanks!  The staff, dots, etc. all appear as white on a black background.  When I click “Print”, it looks like it still comes out black on a white background, as expected.

I’m very grateful that they brought the issue to my attention. If they hadn’t, that assumption would still be lying in wait, preparing to ambush someone else.

Culture and style

Ever get the urge to style a good document?

No? Just me, then.

Well, the urge came over me recently so I started styling this single-page site:

A Few Notes On The Culture by Iain M Banks

I’ve followed this document across multiple locations over the years. It started life as a newsgroup post on rec.arts.sf.written in 1994. Ken McLeod published it there on Iain M Banks’s behalf.

The post complements the epic series of space opera books that Iain M Banks set in the anarcho-utopian society of The Culture. It’s a fascinating piece of world building, as well as an insight into the author’s mind.

I first became aware of it many few years later, after a copy had been posted to the web. That URL died, but Adrian Hon kept a copy on his site. Lots of copies keep stuff safe, so after contemplating linkrot, I made a copy on this site too.

But I recently thought that maybe it deserved a bit of art direction, so I rolled up my sleeves and started messing around, designing in the browser and following happy little accidents.

The finished result is still fairly sparse. It’s still entirely text, except for a background image that shows up if your screen is wide enough. That image of a planet originally started as an infra-red snapshot of Jupiter by the James Webb Space Telescope that I worked over until it was unrecognisable.

The text itself is the main focus of the design though. I knew I wanted to play around with a variable font. Mona Sans from Github was one of the first ones I tried and I found it instantly suitable. I had a lot of fun playing with different weights and widths.

After a bit of messing around, I realised that the heading styles were reminding me of some later reissues of The Culture novels, so I leant into that, deliberately styling the byline to resemble the treatment of the author’s name on those book covers.

There isn’t all that much CSS. I’ve embedded it in the head of the HTML rather than linking to a separate style sheet, so feel free to view source and poke around in there. You’ll see that I’m making liberal use of custom properties, the clamp function, and logical properties.

Originally I had a light mode and dark mode but I found that the dark mode was much more effective so I ditched the lighter option.

I did make sure to include some judicious styles for print, so if you fancy reading on paper, it should print out nicely.

Oh, and of course it’s a progressive web app that works offline.

I didn’t want to mess with the original document other than making some typographic tweaks to punctuation, but I wanted to break up the single wall of text. I wasn’t about to start using pull quotes on the web so in the end I decided to introduce some headings that weren’t in the original document:

  1. Government
  2. Economics
  3. Technology
  4. Philosophy
  5. Lifestyle
  6. Travel
  7. Habitat
  8. Legal System
  9. Politics
  10. Identity
  11. Nomenclature
  12. Cosmology

If your browser viewport is tall enough, the heading for the current section you’re reading will remain sticky as you scroll. No JavaScript required.

I’m pretty pleased with how this little project turned out. It was certainly fun to experiment with fluid type and a nice variable font.

I can add this to my little collection of single-page websites I’ve whittled over the years:

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.

Alternative stylesheets

My website has different themes you can choose from. I don’t just mean a dark mode. These themes all look very different from one another.

I assume that 99.99% of people just see the default theme, but I keep the others around anyway. Offering different themes was originally intended as a way of showcasing the power of CSS, and specifically the separation of concerns between structure and presentation. I started doing this before the CSS Zen Garden was created. Dave really took it to the next level by showing how the same HTML document could be styled in an infinite number of ways.

Each theme has its own stylesheet. I’ve got a very simple little style switcher on every page of my site. Selecting a different theme triggers a page refresh with the new styles applied and sets a cookie to remember your preference.

I also list out the available stylesheets in the head of every page using link elements that have rel values of alternate and stylesheet together. Each link element also has a title attribute with the name of the theme. That’s the standard way to specify alternative stylesheets.

In Firefox you can switch between the specified stylesheets from the View menu by selecting Page Style (notice that there’s also a No style option—very handy for checking your document structure).

Other browsers like Chrome and Safari don’t do anything with the alternative stylesheets. But they don’t ignore them.

Every browser makes a network request for each alternative stylesheet. The request is non-blocking and seems to be low priority, which is good, but I’m somewhat perplexed by the network request being made at all.

I get why Firefox is requesting those stylesheets. It’s similar to requesting a print stylesheet. Even if the network were to drop, you still want those styles available to the user.

But I can’t think of any reason why Chrome or Safari would download the alternative stylesheets.

Control

In two of my recent talks—In And Out Of Style and Design Principles For The Web—I finish by looking at three different components:

  1. a button,
  2. a dropdown, and
  3. a datepicker.

In each case you could use native HTML elements:

  1. button,
  2. select, and
  3. input type="date".

Or you could use divs with a whole bunch of JavaScript and ARIA.

In the case of a datepicker, I totally understand why you’d go for writing your own JavaScript and ARIA. The native HTML element is quite restricted, especially when it comes to styling.

In the case of a dropdown, it’s less clear-cut. Personally, I’d use a select element. While it’s currently impossible to style the open state of a select element, you can style the closed state with relative ease. That’s good enough for me.

Still, I can understand why that wouldn’t be good enough for some cases. If pixel-perfect consistency across platforms is a priority, then you’re going to have to break out the JavaScript and ARIA.

Personally, I think chasing pixel-perfect consistency across platforms isn’t even desirable, but I get it. I too would like to have more control over styling select elements. That’s one of the reasons why the work being done by the Open UI group is so important.

But there’s one more component: a button.

Again, you could use the native button element, or you could use a div or a span and add your own JavaScript and ARIA.

Now, in this case, I must admit that I just don’t get it. Why wouldn’t you just use the native button element? It has no styling issues and the browser gives you all the interactivity and accessibility out of the box.

I’ve been trying to understand the mindset of a developer who wouldn’t use a native button element. The easy answer would be that they’re just bad people, and dismiss them. But that would probably be lazy and inaccurate. Nobody sets out to make a website with poor performance or poor accessibility. And yet, by choosing not to use the native HTML element, that’s what’s likely to happen.

I think I might have finally figured out what might be going on in the mind of such a developer. I think the issue is one of control.

When I hear that there’s a native HTML element—like button or select—that comes with built-in behaviours around interaction and accessibility, I think “Great! That’s less work for me. I can just let the browser deal with it.” In other words, I relinquish control to the browser (though not entirely—I still want the styling to be under my control as much as possible).

But I now understand that someone else might hear that there’s a native HTML element—like button or select—that comes with built-in behaviours around interaction and accessibility, and think “Uh-oh! What if there unexpected side-effects of these built-in behaviours that might bite me on the ass?” In other words, they don’t trust the browsers enough to relinquish control.

I get it. I don’t agree. But I get it.

If your background is in computer science, then the ability to precisely predict how a programme will behave is a virtue. Any potential side-effects that aren’t within your control are undesirable. The only way to ensure that an interface will behave exactly as you want is to write it entirely from scratch, even if that means using more JavaScript and ARIA than is necessary.

But I don’t think it’s a great mindset for the web. The web is filled with uncertainties—browsers, devices, networks. You can’t possibly account for all of the possible variations. On the web, you have to relinquish some control.

Still, I’m glad that I now have a bit more insight into why someone would choose to attempt to retain control by using div, JavaScript and ARIA. It’s not what I would do, but I think I understand the motivation a bit better now.

Talking about style

I’ve published a transcription of the talk I gave at CSS Day:

In And Out Of Style.

The title is intended to have double meaning. The obvious reference is that CSS is about styling web pages. But the talk also covers some long-term trends looking at ideas that have appear, disappear, and reappear over time. Hence, style as in trends and fashion.

There are some hyperlinks in the transcript but I also published a list of links if you’re interested in diving deeper into some of the topics mentioned in the talk.

I also published the slides but, as usual, they don’t make much sense out of context. They’re on Noti.st too.

I made an audio recording for your huffduffing pleasure.

There are two videos of this talk. On Vimeo, there’s the version I pre-recorded for An Event Apart online. On YouTube, there’s the recording from CSS Day.

It’s kind of interesting to compare the two (well, interesting to me, anyway). The pre-recorded version feels like a documentary. The live version has more a different vibe and it obviously has more audience interaction. I think my style of delivery suits a live audience best.

I invite you to read, watch, or listen to In And Out Of Style, whichever you prefer.

Faulty logic

I’m a fan of logical properties in CSS. As I wrote in the responsive design course on web.dev, they’re crucial for internationalisation.

Alaa Abd El-Rahim has written articles on CSS tricks about building multi-directional layouts and controlling layout in a multi-directional website. Not having to write separate stylesheets—or even separate rules—for different writing modes is great!

More than that though, I think understanding logical properties is the best way to truly understand CSS layout tools like grid and flexbox.

It’s like when you’re learning a new language. At some point your brain goes from translating from your mother tongue into the other language, and instead starts thinking in that other language. Likewise with CSS, as some point you want to stop translating “left” and “right” into “inline-start” and “inline-end” and instead start thinking in terms of inline and block dimensions.

As is so often the case with CSS, I think new features like these are easier to pick up if you’re new to the language. I had to unlearn using floats for layout and instead learn flexbox and grid. Someone learning layout from scatch can go straight to flexbox and grid without having to ditch the cognitive baggage of floats. Similarly, it’s going to take time for me to shed the baggage of directional properties and truly grok logical properties, but someone new to CSS can go straight to logical properties without passing through the directional stage.

Except we’re not quite there yet.

In order for logical properties to replace directional properties, they need to be implemented everywhere. Right now you can’t use logical properties inside a media query, for example:

@media (min-inline-size: 40em)

That wont’ work. You have to use the old-fashioned syntax:

@media (min-width: 40em)

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 then take a look at how the syntax for container queries is going to work. First you declare the axis that you want to be contained using the syntax from logical properties:

main {
  container-type: inline-size;
}

But then when you go to declare the actual container query, you have to use the corresponding directional property:

@container (min-width: 40em)

This won’t work:

@container (min-inline-size: 40em)

I kind of get why it won’t work: the syntax for container queries should match the syntax for media queries. But now the theory behind disallowing logical properties in media queries doesn’t hold up. When it comes to container queries, the physical layout of the viewport isn’t what matters.

I hope that both media queries and container queries will allow logical properties sooner rather than later. Until they fall in line, it’s impossible to make the jump fully to logical properties.

There are some other spots where logical properties haven’t been fully implemented yet, but I’m assuming that’s a matter of time. For example, in Firefox I can make a wide data table responsive by making its container side-swipeable on narrow screens:

.table-container {
  max-inline-size: 100%;
  overflow-inline: auto;
}

But overflow-inline and overflow-block aren’t supported in any other browsers. So I have to do this:

.table-container {
  max-inline-size: 100%;
  overflow-x: auto;
}

Frankly, mixing and matching logical properties with directional properties feels worse than not using logical properties at all. The inconsistency is icky. This feels old-fashioned but consistent:

.table-container {
  max-width: 100%;
  overflow-x: auto;
}

I don’t think there are any particular technical reasons why browsers haven’t implemented logical properties consistently. I suspect it’s more a matter of priorities. Fully implementing logical properties in a browser may seem like a nice-to-have bit of syntactic sugar while there are other more important web standard fish to fry.

But from the perspective of someone trying to use logical properties, the patchy rollout is frustrating.

Stakeholders of styling

When I wrote about the new accent-color property in CSS, I pondered how much control a web developer should have over styling form controls:

Who are we to make that decision? Shouldn’t the user’s choice take primacy over our choices?

But then again, where do we draw the line? We’re allowed over-ride link colours. We’re allowed over-ride font choices.

Ultimately, I came down on the side of granting authors more control:

If developers don’t get a standardised way to customise native form controls, they’ll just recreate their own over-engineered versions.

This question of “who gets to decide?” used to be much more prevelant in the early days of the web. One way to think about this is that there are three stakeholders involved in the presentation of a web page:

  1. The author of the page. “Author” is spec-speak for designer or developer.
  2. The user.
  3. The browser, or user agent. A piece of software tries to balance the needs of both author and user. But, as the name implies, the user takes precedence.

These days we tend to think of web design a single-stakeholder undertaking. The author decides how something should be presented and then executes that decision using CSS.

But as Eric once said, every line of you CSS you write is a suggestion to the browser. That’s not how we think about CSS though. We think of CSS like a series of instructions rather than suggestions. Never mind respecting the user’s preferences; one of the first things we do is reset all the user agent’s styles.

In the early days of the web, more consideration was given to the idea of style suggestions rather than instructions. Heck, users could always over-ride any of your suggestions with their own user stylesheet. These days, users would need to install a browser extension to do the same thing.

The first proposal for CSS had a concept called “influence”:

h2.font.size = 20pt 40%

Here, the requested influence is reduced to 40%. If a style sheet later in the cascade also requests influence over h2.font.size, up to 60% can be granted. When the document is rendered, a weighted average of the two requests is calculated, and the final font size is determined.

I think the only remnant of “influence” left in CSS is accidental. It’s in the specificity of selectors …and the !important declaration.

I think it’s a shame that user stylesheets are no longer a thing. But I get why they were dropped from browsers. They date from a time when it was mostly nerds using the web, before “regular folks” came on board. I understand why it became a little-used feature, suitable for being dropped. But the principle of it still rankles slightly.

But in recent years there has been a slight return to the multi-stakeholder concept of styling websites. Thanks to prefers-reduced-motion and prefers-color-scheme, a responsible author can choose to bow to the wishes of the user.

I was reminded of this when I added a dark mode to my website:

Y’know, when I first heard about Apple adding dark mode to their OS—and also to CSS—I thought, “Oh, great, Apple are making shit up again!” But then I realised that, like user style sheets, this is one more reminder to designers and developers that they don’t get the last word—users do.

Accent all areas

Whenever a new version of Chrome comes out, there’s an accompanying blog post listing what’s new. Chrome 93 just came out and, sure enough, Pete has written a blog post about it.

But what I think is the most exciting addition to the browser isn’t listed.

What is this feature that’s got me so excited?

Okay, I’ve probably oversold it now because actually, it looks like a rather small trivial addition. It’s the accent-color property in CSS.

Up until now, accent colour was controlled by the operating system. If you’re on a Mac, go to “System Preferences” and then “General”. There you’ll see an option to change your accent colour. Try picking a different colour. You’ll see that change cascade down into the other form fields in that preference pane: checkboxes, radio buttons, and dropdowns.

Your choice will also cascade down into web pages. Any web page that uses native checkboxes, radio buttons and other interface elements will inherit that colour.

This is how interface elements are supposed to work. The browser inherits the look’n’feel of the inputs from the operating system.

That’s the theory anyway. In practice, form elements—such as dropdowns—can look different from browser to browser, something that shouldn’t be happening if the browsers are all inheriting from the operating system.

Anyway, it’s probably this supposed separation of responsibility between browser and operating system which has led to the current situation with form fields and CSS. Authors can style form fields up to a point, but there’s always a line that you don’t get to cross.

The accent colour of a selected radio button or a checkbox has historically been on the other side of that line. You either had to accept that you couldn’t change the colour, or you had to make your own checkbox or radio button interface. You could use CSS to hide the native element and replace it with an image instead.

That feels a bit over-engineered and frankly kind of hacky. It reminds me of the bad old days of image replacement for text before we had web fonts.

Now, with the accent-color property in CSS, authors can over-ride the choice that the user has set at the operating system level.

On the one hand, this doesn’t feel great to me. Who are we to make that decision? Shouldn’t the user’s choice take primacy over our choices?

But then again, where do we draw the line? We’re allowed over-ride link colours. We’re allowed over-ride font choices.

Ultimately I think it’s a good thing that authors can now specify an accent colour. What makes me think that is the behaviour that authors have shown if they don’t have this ability—they do it anyway, and in a hackier manner. This is why I think the work of the Open UI group is so important. If developers don’t get a standardised way to customise native form controls, they’ll just recreate their own over-engineered versions.

The purpose of Open UI to the web platform is to allow web developers to style and extend built-in web UI controls, such as select dropdowns, checkboxes, radio buttons, and date/color pickers.

Trying to stop developers from styling checkboxes and radio buttons is like trying to stop teenagers from having sex. You might as well accept that it’s going to happen and give them contraception so they can at least do it safely.

So I welcome this new CSS condom.

You can see accent-color in action in this demo. Change the value of the accent-color property to see the form fields update:

:root {
  accent-color: rebeccapurple;
}

Applying it at the document level like that will make it universal, but you can also use the property on an element-by-element basis using whatever selector you want.

That demo works in Chrome and Edge 93, the current release. It also works in Firefox 92, which literally just landed (like as I was writing this blog post, support for accent-color magically arrived!).

As for Safari, well, who knows? If Apple published a roadmap, then developers would have a clue when to expect a property like this to land. But we mere mortals cannot be trusted with such important hush-hush information.

In the meantime, keep an eye on Can I Use. And lack of support on one browser is no reason not to use accent-color anyway. It’s a progressive enhancement. Add it to your CSS today and it will work in more browsers in the future.

SVGs in dark mode

I added a dark mode to my site last year. Since then I’ve been idly toying with the addition of a dark mode to The Session too.

As with this site, the key to adding a dark mode was switching to custom properties for color and background-color declarations. But my plans kept getting derailed by the sheet music on the site. The sheet music is delivered as SVG generated by ABCJS which hard-codes the colour in stroke and fill attributes:

fill="#000000" stroke="#000000"

When I was describing CSS recently I mentioned the high specifity of inline styles:

Whereas external CSS and embedded CSS don’t have any effect on specificity, inline styles are positively radioactive with specificity.

Given that harsh fact of life, I figured it would be nigh-on impossible to over-ride the colour of the sheetmusic. But then I realised I was an idiot.

The stroke and fill attributes in SVG are presentational but they aren’t inline styles. They’re attributes. They have no affect on specifity. I can easily over-ride them in an external style sheet.

In fact, if I had actually remembered what I wrote when I was adding a dark mode to adactio.com, I could’ve saved myself some time:

I have SVGs of sparklines on my homepage. The SVG has a hard-coded colour value in the stroke attribute of the path element that draws the sparkline. Fortunately, this can be over-ridden in the style sheet:

svg.activity-sparkline path {
  stroke: var(--text-color);
}

I was able to do something similar on The Session. I used the handy currentColor keyword in CSS so that the sheet music matched the colour of the text:

svg path {
  fill: currentColor;
}
svg path:not(stroke="none") {
  stroke: currentColor;
}

Et voila! I now had light-on-dark sheet music for The Session’s dark mode all wrapped up in a prefers-color-scheme: dark media query.

I pushed out out the new feature and started getting feedback. It could be best summarised as “Thanks. I hate it.”

It turns out that while people were perfectly fine with a dark mode that inverts the colours of text, it felt really weird and icky to do the same with sheet music.

On the one hand, this seems odd. After all, sheet music is a writing system like any other. If you’re fine with light text on a dark background, why doesn’t that hold for light sheet music on a dark background?

But on the other hand, sheet music is also like an image. And we don’t invert the colours of our images when we add a dark mode to our CSS.

With that in mind, I went back to the drawing board and this time treated the sheet music SVGs as being intrinsicly dark-on-light, rather than a stylistic choice. It meant a few more CSS rules, but I’m happy with the final result. You can see it in action by visiting a tune page and toggling your device’s “appearance” settings between light and dark.

If you’re a member of The Session, I also added a toggle switch to your member profile so you can choose dark or light mode regardless of your device settings.

Lists

We often have brown bag lunchtime presentations at Clearleft. In the Before Times, this would involve a trip to Pret or Itsu to get a lunch order in, which we would then proceed to eat in front of whoever was giving the presentation. Often it’s someone from Clearleft demoing something or playing back a project, but whenever possible we’d rope in other people to swing by and share what they’re up to.

We’ve continued this tradition since making the switch to working remotely. Now the brown bag presentations happen over Zoom. This has two advantages. Firstly, if you don’t want the presenter watching you eat your lunch, you can switch your camera off. Secondly, because the presenter doesn’t have to be in Brighton, there’s no geographical limit on who could present.

Our most recent brown bag was truly excellent. I asked Léonie if she’d be up for it, and she very kindly agreed. As well as giving us a whirlwind tour of how assistive technology works on the web, she then invited us to observe her interacting with websites using a screen reader.

I’ve seen Léonie do this before and it’s always struck me as a very open and vulnerable thing to do. Think about it: the audience has more information than the presenter. We can see the website at the same time as we’re listening to Léonie and her screen reader.

We got to nominate which websites to visit. One of them—a client’s current site that we haven’t yet redesigned—was a textbook example of how important form controls are. There was a form where almost everything was hunky-dory: form fields, labels, it was all fine. But one of the inputs was a combo box. Instead of using a native select with a datalist, this was made with JavaScript. Because it was lacking the requisite ARIA additions to make it accessible, it was pretty much unusable to Léonie.

And that’s why you use the right HTML element wherever possible, kids!

The other site Léonie visited was Clearleft’s own. That was all fine. Léonie demonstrated how she’d form a mental model of a page by getting the screen reader to read out the headings. Interestingly, the nesting of headings on the Clearleft site is technically wrong—there’s a jump from an h1 to an h3—probably a result of the component-driven architecture where you don’t quite know where in the page a heading will appear. But this didn’t seem to be an issue. The fact that headings are being used at all was the more important fact. As Léonie said, there’s a lot of incorrect HTML out there so it’s no wonder that screen readers aren’t necessarily sticklers for nesting.

I’ve said it before and I’ll say it again: if you’re using headings, labelling form fields, and providing alternative text for images, you’re already doing a better job than most websites.

Headings weren’t the only way that Léonie got a feel for the page architecture. Landmark roles—like header and nav—really helped too. Inside the nav element, she also heard how many items there were. That’s because the navigation was marked up as a list: “List: six items.”

And that reminded me of the Webkit issue. On Webkit browsers like Safari, the list on the Clearleft site would not be announced as a list. That’s because the lists’s bullets have been removed using CSS.

Now this isn’t the only time that screen readers pay attention to styling. If you use display: none to hide an element from sight, it will also be unavailable to screen reader users. Makes sense. But removing the semantic meaning of lists based on CSS? That seems a bit much.

There are good reasons for it though. Here’s a thread from James Craig on where this decision came from (James, by the way, is an absolute unsung hero of accessibility). It turns out that developers went overboard with lists a while back and that’s why we can’t have nice things. In over-compensating from divitis, developers ended up creating listitis, marking up anything vaguely list-like as an unordered list with styling adjusted. That was very annoying for screen reader users trying to figure out what was actually a list.

And James also asks:

If a sighted user doesn’t need to know it’s a list, why would a screen reader user need to know or want to know? Stated another way, if the visible list markers (bullets, image markers, etc.) are deemed by the designers to be visually burdensome or redundant for sighted users, why burden screen reader users with those semantics?

That’s a fair point, but the thing is …bullets maketh not the list. There are many ways of styling something that is genuinely a list that doesn’t involve bullets or image markers. White space, borders, keylines—these can all indicate visually that something is a list of items.

If you look at, say, the tunes page on The Session, you can see that there are numerous lists—newest tunes, latest comments, etc. In this case, as a sighted visitor, you would be at an advantage over a screen reader user in that you can, at a glance, see that there’s a list of five items here, a list of ten items there.

So I’m not disagreeing with the thinking behind the Webkit decision, but I do think the heuristics probably aren’t going to be quite good enough to make the call on whether something is truly a list or not.

Still, while I used to be kind of upset about the Webkit behaviour, I’ve become more equanimous about it over time. There are two reasons for this.

Firstly, there’s something that Eric said:

We have come so far to agree that websites don’t need to look the same in every browser mostly due to bugs in their rendering engines or preferences of the user.

I think the same is true for screen readers and other assistive technology: Websites don’t need to sound the same in every screen reader.

That’s a really good point. If we agree that “pixel perfection” isn’t attainable—or desirable—in a fluid, user-centred medium like the web, why demand the aural equivalent?

The second reason why I’m not storming the barricades about this is something that James said:

Of course, heuristics are imperfect, so authors have the ability to explicitly override the heuristically determined role by adding role="list”.

That means more work for me as a developer, and that’s …absolutely fine. If I can take something that might be a problem for a user, and turn into something that’s a problem for me, I’ll choose to make it my problem every time.

I don’t have to petition Webkit to change their stance or update their heuristics. If I feel strongly that a list styled without bullets should still be announced as a list, I can specificy that in the markup.

It does feel very redundant to write ul role="list”. The whole point of having HTML elements with built-in semantics is that you don’t need to add any ARIA roles. But we did it for a while when new structural elements were introduced in HTML5—main role="main", nav role="navigation", etc. So I’m okay with a little bit of redundancy. I think the important thing is that you really stop and think about whether something should be announced as a list or not, regardless of styling. There isn’t a one-size-fits-all answer (hence why it’s nigh-on impossible to get the heuristics right). Each list needs to be marked up on a case-by-case basis.

And I wouldn’t advise spending too much time thinking about this either. There are other, more important areas to consider. Like I said, headings, forms, and images really matter. I’d prioritise those elements above thinking about lists. And it’s worth pointing out that Webkit doesn’t remove all semantic meaning from styled lists—it updates the role value from list to group. That seems sensible to me.

In the case of that page on The Session, I don’t think I’m guilty of listitis. Yes, there are seven lists on that page (two for navigation, five for content) but I’m reasonably confident that they all look like lists even without bullets or markers. So I’ve added role="list" to some ul elements.

As with so many things related to accessibility—and the web in general—this is a situation where the only answer I can confidentally come up with is …it depends.

Influence

Hidde gave a great talk recently called On the origin of cascades (by means of natural selectors):

It’s been 25 years since the first people proposed a language to style the web. Since the late nineties, CSS lived through years of platform evolution.

It’s a lovely history lesson that reminded me of that great post by Zach Bloom a while back called The Languages Which Almost Became CSS.

The TL;DR timeline of CSS goes something like this:

Håkon and Bert joined forces and that’s what led to the Cascading Style Sheet language we use today.

Hidde looks at how the concept of the cascade evolved from those early days. But there’s another idea in Håkon’s proposal that fascinates me:

While the author (or publisher) often wants to give the documents a distinct look and feel, the user will set preferences to make all documents appear more similar. Designing a style sheet notation that fill both groups’ needs is a challenge.

The proposed solution is referred to as “influence”.

The user supplies the initial sheet which may request total control of the presentation, but — more likely — hands most of the influence over to the style sheets referenced in the incoming document.

So an author could try demanding that their lovely styles are to be implemented without question by specifying an influence of 100%. The proposed syntax looked like this:

h1.font.size = 24pt 100%

More reasonably, the author could specify, say, 40% influence:

h2.font.size = 20pt 40%

Here, the requested influence is reduced to 40%. If a style sheet later in the cascade also requests influence over h2.font.size, up to 60% can be granted. When the document is rendered, a weighted average of the two requests is calculated, and the final font size is determined.

Okay, that sounds pretty convoluted but then again, so is specificity.

This idea of influence in CSS reminds me of Cap’s post about The Sliding Scale of Giving a Fuck:

Hold on a second. I’m like a two-out-of-ten on this. How strongly do you feel?

I’m probably a six-out-of-ten, I replied after a couple moments of consideration.

Cool, then let’s do it your way.

In the end, the concept of influence in CSS died out, but user style sheets survived …for a while. Now they too are as dead as a dodo. Most people today aren’t aware that browsers used to provide a mechanism for applying your own visual preferences for browsing the web (kind of like Neopets or MySpace but for literally every single web page …just think of how empowering that was!).

Even if you don’t mourn the death of user style sheets—you can dismiss them as a power-user feature—I think it’s such a shame that the concept of shared influence has fallen by the wayside. Web design today is dictatorial. Designers and developers issue their ultimata in the form of CSS, even though technically every line of CSS you write is a suggestion to a web browser—not a demand.

I wish that web design were more of a two-way street, more of a conversation between designer and end user.

There are occassional glimpses of this mindset. Like I said when I added a dark mode to my website:

Y’know, when I first heard about Apple adding dark mode to their OS—and also to CSS—I thought, “Oh, great, Apple are making shit up again!” But then I realised that, like user style sheets, this is one more reminder to designers and developers that they don’t get the last word—users do.