Here’s what I didn’t know about :where() - Manuel Matuzović
I feel like I’m starting to understand how the CSS :where
pseudo-class works and why it’s useful. The cogs are slowly turning in my brain.
I feel like I’m starting to understand how the CSS :where
pseudo-class works and why it’s useful. The cogs are slowly turning in my brain.
This detailed proposal from Miriam for scoping CSS is well worth reading—it makes a lot of sense to me.
This is supposed to be a defence of utility classes …but it’s actually a great explanation of why classes in general are a great mechanism for styling.
I don’t think anyone has ever seriously suggested using inline styles—the actual disagreement is about how ludicrously rigid and wasteful the class names dictated by something like Tailwind are. When people criticise those classes they aren’t advocating for inline styles—they’re advocating for better class names and making more use of the power of the class selector in CSS, not less.
Anyway, if you removed every instance of the word “utility” from this article, it would still work.
This would be such a great addition to CSS—a parent/ancestor selector!
With the combined might of :has()
, :not()
, nth-child()
, and calc()
, CSS has become a powerful language for specifying rules to account for all kinds of situations.
Oh, this is smart! You can’t target pseudo-elements in JavaScript, but you can use custom properties as a proxy instead.
If we, as a community, start to appreciate the complexity of writing CSS, perhaps we can ask for help instead of blaming the language when we’re confused or stuck. We might also stop looking down on CSS specialists.
A handy tool for getting an overview of your site’s CSS:
CSS Stats provides analytics and visualizations for your stylesheets. This information can be used to improve consistency in your design, track performance of your app, and diagnose complex areas before it snowballs out of control.
I wrote a little something recently about using ARIA attributes as selectors in CSS. For me, one of the advantages is that because ARIA attributes are generally added via JavaScript, the corresponding CSS rules won’t kick in if something goes wrong with the JavaScript:
Generally, ARIA attributes—like
aria-hidden
—are added by JavaScript at runtime (rather than being hard-coded in the HTML).
But there’s one instance where I actually put the ARIA attribute directly in the HTML that gets sent from the server: aria-live
.
If you’re not familiar with it, aria-live
is extremely useful if you’ve got any dynamic updates on your page—via Ajax, for example. Let’s say you’ve got a bit of your site where filtered results will show up. Slap an aria-live
attribute on there with a value of “polite”:
<div aria-live="polite">
...dynamic content gets inserted here
</div>
You could instead provide a value of “assertive”, but you almost certainly don’t want to do that—it can be quite rude.
Anyway, on the face it, this looks like exactly the kind of ARIA attribute that should be added with JavaScript. After all, if there’s no JavaScript, there’ll be no dynamic updates.
But I picked up a handy lesson from Ire’s excellent post on using aria-live
:
Assistive technology will initially scan the document for instances of the aria-live attribute and keep track of elements that include it. This means that, if we want to notify users of a change within an element, we need to include the attribute in the original markup.
Good to know!
Sara tweeted something recently that resonated with me:
Also, Pro Tip: Using ARIA attributes as CSS hooks ensures your component will only look (and/or function) properly if said attributes are used in the HTML, which, in turn, ensures that they will always be added (otherwise, the component will obv. be broken)
Yes! I didn’t mention it when I wrote about accessible interactions but this is my preferred way of hooking up CSS and JavaScript interactions. Here’s old Codepen where you can see it in action:
[aria-hidden='true'] {
display: none;
}
In order for the functionality to work for everyone—screen reader users or not—I have to make sure that I’m toggling the value of aria-hidden
in my JavaScript.
There’s another advantage to this technique. Generally, ARIA attributes—like aria-hidden
—are added by JavaScript at runtime (rather than being hard-coded in the HTML). If something goes wrong with the JavaScript, the aria-hidden
value isn’t set to “true”, which means that the CSS never kicks in. So the default state is for content to be displayed. There’s no assumption that the JavaScript has to work in order for the CSS to make sense.
It’s almost as though accessibility and progressive enhancement are connected somehow…
An excellent and clear explanation of specificity in CSS.
I can see this coming in very handy at Codebar—pop any CSS selector in here and get a plain English explanation of what it’s doing.
This is a wonderful interactive explanation of the way CSS hierarchy works—beautiful!
Everyone wants it, but it sure seems like no one is actively working on it.
Zach traces the earliest inklings of container queries to an old blog post of Andy’s—back when he was at Clearleft—called Responsive Containers:
For fun, here’s some made-up syntax (which Jeremy has dubbed ‘selector queries’)…
Well, the clever CSS techniques just keep on comin’ from Trys—I’m learning so much from him!
This is a great explanation of the difference between the [lang]
and :lang
CSS selectors. I wouldn’t even have thought’ve the differences so this is really valuable to me.
There’s a worrying tendency for “real” programmers look down their noses at CSS. It’s just a declarative language, they point out, not a fully-featured programming language. Heck, it isn’t even a scripting language.
That may be true, but that doesn’t mean that CSS isn’t powerful. It’s just powerful in different ways to traditional languages.
Take CSS selectors, for example. At the most basic level, they work like conditional statments. Here’s a standard if
statement:
if (condition) {
// code here
}
The condition needs to evaluate to true
in order for the code in the curly braces to be executed. Sound familiar?
condition {
// styles here
}
That’s a very simple mapping, but what if the conditional statement is more complicated?
if (condition1 && condition2) {
// code here
}
Well, that’s what the decendant selector does:
condition1 condition2 {
// styles here
}
In fact, we can get even more specific than that by using the child combinator, the sibling combinator, and the adjacent sibling combinator:
condition1 > condition2
condition1 ~ condition2
condition2 + condition2
AND is just one part of Boolean logic. There’s also OR:
if (condition1 || condition2) {
// code here
}
In CSS, we use commas:
condition1, condition2 {
// styles here
}
We’ve even got the :not()
pseudo-class to complete the set of Boolean possibilities. Once you add quantity queries into the mix, made possible by :nth-child
and its ilk, CSS starts to look Turing complete. I’ve seen people build state machines using the adjacent sibling combinator and the :checked
pseudo-class.
Anyway, my point here is that CSS selectors are really powerful. And yet, quite often we deliberately choose not to use that power. The entire raison d’être for OOCSS, BEM, and Smacss is to deliberately limit the power of selectors, restricting them to class selectors only.
On the face of it, this might seem like an odd choice. After all, we wouldn’t deliberately limit ourselves to a subset of a programming language, would we?
We would and we do. That’s what templating languages are for. Whether it’s PHP’s Smarty or Twig, or JavaScript’s Mustache, Nunjucks, or Handlebars, they all work by providing a deliberately small subset of features. Some pride themselves on being logic-less. If you find yourself trying to do something that the templating language doesn’t provide, that’s a good sign that you shouldn’t be trying to do it in the template at all; it should be in the controller.
So templating languages exist to enforce simplicity and ensure that the complexity happens somewhere else. It’s a similar story with BEM et al. If you find you can’t select something in the CSS, that’s a sign that you probably need to add another class name to the HTML. The complexity is confined to the markup in order to keep the CSS more straightforward, modular, and maintainable.
But let’s not forget that that’s a choice. It’s not that CSS in inherently incapable of executing complex conditions. Quite the opposite. It’s precisely because CSS selectors (and the cascade) are so powerful that we choose to put guard rails in place.
Rachel gives us the run-down on what’s coming soon to Cascading Style Sheets near you, including an aspect-ratio unit and a matches
selector (as originally proposed by Lea).
The fascinating results of Brad’s survey.
Personally, I’m not a fan of nesting. I feel it obfuscates more than helps. And it makes searching for a specific selector tricky.
That said, Danielle feels quite strongly that nesting is the way to go, so on Clearleft projects, that’s how we write Sass + BEM.
Variable fonts are a very exciting and powerful new addition to the toolbox of web design. They was very much at the centre of discussion at this year’s Ampersand conference.
A lot of the demonstrations of the power of variable fonts are showing how it can be used to make letter-by-letter adjustments. The Ampersand website itself does this with the logo. See also: the brilliant demos by Mandy. It’s getting to the point where logotypes can be sculpted and adjusted just-so using CSS and raw text—no images required.
I find this to be thrilling, but there’s a fly in the ointment. In order to style something in CSS, you need a selector to target it. If you’re going to style individual letters, you need to wrap each one in an HTML element so that you can then select it in CSS.
For the Ampersand logo, we had to wrap each letter in a span
(and then, becuase that might cause each letter to be read out individually instead of all of them as a single word, we applied some ARIA shenanigans to the containing element). There’s even a JavaScript library—Splitting.js—that will do this for you.
But if the whole point of using HTML is that the content is accessible, copyable, and pastable, isn’t a bit of a shame that we then compromise the markup—and the accessibility—by wrapping individual letters in presentational tags?
What if there were an ::nth-letter
selector in CSS?
There’s some prior art here. We’ve already got ::first-letter
(and now the initial-letter
property or whatever it ends up being called). If we can target the first letter in a piece of text, why not the second, or third, or nth?
It raises some questions. What constitutes a letter? Would it be better if we talked about ::first-character
, ::initial-character
, ::nth-character
, and so on?
Even then, there are some tricksy things to figure out. What’s the third character in this piece of markup?
<p>AB<span>CD</span>EF</p>
Is it “C”, becuase that’s the third character regardless of nesting? Or is it “E”, becuase techically that’s the third character token that’s a direct child of the parent element?
I imagine that implementing ::nth-letter
(or ::nth-character
) would be quite complex so there would probably be very little appetite for it from browser makers. But it doesn’t seem as problematic as some selectors we’ve already got.
Take ::first-line
, for example. That violates one of the biggest issues in adding new CSS selectors: it’s a selector that depends on layout.
Think about it. The browser has to first calculate how many characters are in the first line of an element (like, say, a paragraph). Having figured that out, the browser can then apply the styles declared in the ::first-line
selector. But those styles may involve font sizing updates that changes the number of characters in the first line. Paradox!
(Technically, only a subset of CSS of properties can be applied to ::first-line
, but that subset includes font-size
so the paradox remains.)
I checked to see if ::first-line
was included in one of my favourite documents: Incomplete List of Mistakes in the Design of CSS. It isn’t.
So compared to the logic-bending paradoxes of ::first-line
, an ::nth-letter
selector would be relatively straightforward. But that in itself isn’t a good enough reason for it to exist. As the CSS Working Group FAQs say:
The fact that we’ve made one mistake isn’t an argument for repeating the mistake.
A new selector needs to solve specific use cases. I would argue that all the letter-by-letter uses of variable fonts that we’re seeing demonstrate the use cases, and the number of these examples is only going to increase. The very fact that JavaScript libraries exist to solve this problem shows that there’s a need here (and we’ve seen the pattern of common JavaScript use-cases ending up in CSS before—rollovers, animation, etc.).
Now, I know that browser makers would like us to figure out how proposed CSS features should work by polyfilling a solution with Houdini. But would that work for a selector? I don’t know much about Houdini so I asked Una. She pointed me to a proposal by Greg and Tab for a full-on parser in Houdini. But that’s a loooong way off. Until then, we must petition our case to the browser gods.
This is not a new suggestion.
Anne Van Kesteren proposed ::nth-letter
way back in 2003:
While I’m talking about CSS, I would also like to have
::nth-line(n)
,::nth-letter(n)
and::nth-word(n)
, any thoughts?
Trent called for ::nth-letter
in January 2011:
I think this would be the ideal solution from a web designer’s perspective. No javascript would be required, and 100% of the styling would be handled right where it should be—in the CSS.
Chris repeated the call in October of 2011:
Of all of these “new” selectors,
::nth-letter
is likely the most useful.
In 2012, Bram linked to a blog post (now unavailable) from Adobe indicating that they were working on ::nth-letter
for Webkit. That was the last anyone’s seen of this elusive pseudo-element.
In 2013, Chris (again) included ::nth-letter
in his wishlist for CSS. So say we all.
One thing I gained a stronger awareness of (simply from working with checkboxes) is that it’s important to progressively enhance UI components, so that a fancy custom one is able to fall back to the default browser styles and functionality. This way, a user can still access the UI if JavaScript or CSS fail.