One of the ways to help get a grasp of CSS specificity is thinking terms of “what beats what” or how strong the specificity is. Manuel Matuzovic has a helpful interactive step-by-step demo. You keep clicking the “Add selector” button, and the CSS shown (and applied to the page) changes with ever-increasingly-strong selectors applied to the body that change the background-color. At the end, it veers into not-really-selectors trickery, like using @keyframes to override things.
More specificity practice
If you enjoyed the trickery at the end, check out Francisco Dias’ A Specificity Battle!, an article we published a few years back that does a back-and-forth styling battle with nineteen steps “selecting” the same element to re-style it. CSS is cray sometimes.
Paper documents were the original metaphor for the web. […]
The page you’re reading this on still mimics paper. We still call it a page or an HTML document. It follows the same typographic rules and conventions – black text on white backgrounds and a top-to-bottom / left-to-right heirarchical structure.
Over in the ShopTalk Discord, the idea of CSS custom properties named --ink and --paper came up the other day as abstractions for color and background-color and I kinda like it. There’s something more clear about the meanings of those terms to me.
But Maggie gets into some of the downsides of the paper-based metaphors, pointing out Ted Nelson’s critiques. This is interesting:
We treat the page as the smallest unit of linkable information, instead of the sentence or paragraph.
Will the main metaphor of the web as paper change in time? I’d say it’s highly likely. The interactivity and behavior we expect on the web today is a million miles different than we expected in the past and that’s going to keep happening. These updates accelerate the change. Perhaps someday the metaphors will have shifted to “alternate neighborhood,” “second brain,” or “dedicated assistant.”
I was killing some time browsing my CodePen feed for some eye candy and didn’t need to go past the first page before spotting a neat CSS hover effect by Adam Argyle.
I must’ve spent 10 minutes just staring at the demo in awe. There’s something about this that feels so app-like. I think it might be how contextually accurate it is in that the background color slides in from the left, then exits out through the right. It’s exactly the sort of behavior I’d expect from a mouse-in, mouse-out sort of interaction.
Whatever the case, I fired up a fresh pen and went to work recreating it. And it’s not super complex or anything, but rather a clever use of transitions and transforms paired with proper offsets. Quite elegant! I’m actually a little embarrassed how long it took me to realize how the mouse-out part works.
Here’s how I tackled it, warts and all.
“I bet that’s using a transition on a background.”
That was my first thought. Define the background-color, set the background-size and background-position, then transition the background-position. That’s how I’ve seen that “growing” background color thing done in the past. I’ve done that myself on some projects, like this:
If I could do the same thing, only from left-to-right, then all that’s left is the mouse-out, right? Nope. The problem is there’s nothing that can really make the background-position transition from left-to-right to left-to-right. I could make it do one or the other, but not both.
“Maybe it’s a transform instead.”
My next attempt was jump into transforms. The transform property provides a bunch of functions that can transition together for slightly more complex movement. For example, the background can “grow” or “shrink” by changing the element’s scale(). Or, in this case, just along the x-axis with scaleX().
But like I mentioned, there isn’t a way to isolate the element’s background to do that. Going from scaleX(0) to scaleX(1) scales the entire element, so that basically squishes the link — content and all — down to nothing, then stretches it back out to its natural size which is a totally different effect. Plus, it means starting with scaleX(0) which hides the whole dang thing by default making it unusable.
But a pseudo-element could work! It doesn’t matter if that gets squished or hidden because it isn’t part of the actual content. Gotta put the background on that instead and position it directly under the link.
a {
/* Keeps the pseudo-element contained to the element */
position: relative;
}
a::before {
background: #ff9800;
content: "";
inset: 0; /* Logical equivalent to physical offsets */
position: absolute;
transform: scaleX(0); /* Hide by default */
z-index: -1; /* Ensures the link is stacked on top */
}
“Now I need ::before to change on hover.”
I knew I could make ::before scale from 0 to 1 by chaining it to the link element’s :hover state.
a:hover::before {
transform: scaleX(1)
}
Nice! I was onto something.
Sprinkle a little transition fairy dust on it and things start to come to life.
Again, this is where I sorta got stuck. Something in my head just wasn’t clicking for some reason. As per usual, I ran over to the CSS-Tricks Almanac to see what property might’ve slipped my mind.
Ah, yes. That would be transform-origin. That allows me to set where the transform starts, which is not totally dissimilar from setting the background-position like I tried earlier. The transform could start from the left instead of its default 50% 50% position.
I was already transitioning ::before to scaleX(1) on link hover. If I reversed the transform-origin from left to right at the same time, then mayyyybe the highlight goes out the opposite of how it came in when the mouse exits?
Lea Verou made a Web Component for processing Markdown. Looks like there were a couple of others out there already, but I agree with Lea in that this is a good use case for the light DOM (as opposed to the shadow DOM that is normally quite useful for web components), and that’s what Lea’s does. The output is HTML so I can imagine it’s ideal you can style it on the page like any other type rather than have to deal with that shadow DOM. I still feel like the styling stories for shadow DOM all kinda suck.
The story of how it came to be is funny and highly relatable. You just want to build one simple thing and it turns out you have to do 15 other things and it takes the better part of a week.
Huh! I did not realize that CSS custom properties had their own resolution behavior for how !important works in their values. Uh, despite writing a guide about them. 😬 But hey it’s now updated.
div {
--color: red !important;
color: var(--color);
color: yellow;
}
It kinda feels like red should win, but because !important is ultimately stripped from the custom property value, yellow wins it out. And it’s not because the color declaration comes last — if color: red !important; was the first declaration, then red would win.
But it’s not like !important is just stripped and ignored; it’s used in a scoped way, affecting which custom property value wins. But then !important is gone when that value is applied. Stefan’s example:
div {
/*
`!important` overrules the
other `--color` definitions
*/
--color: red !important;
color: var(--color);
}
.class {
--color: blue;
}
#id {
--color: yellow;
}
This feels weird (to me) as you’d think yellow declared on #id would win because it has the highest specificity. But that’s the deal with the scoped behavior — !important makes red the winner, then is applied to the color as such.
Mary Dyson produces nitty gritty research on the long-accepted notion that shorter line lengths are more legible than longer ones. The study finds that shorter lines do not necessarily lead to faster reading. If you’re looking for a definitive answer to use in your next design review debate, though, no dice. The big finding is that long lines don’t slow things down as much as previously thought, not that they’re better or worse.
But there’s so much more meat in here that I found much more interesting, mostly because I’m largely ignorant on the topic and gained a dollop of context on writing, legibility, and behavior.
Things like…
There’s a term for transitioning between lines of text
It’s return sweeps. You know, like your eye hits the Return key at the end of the line and sweeps to the start of the next line. Then, there are undershoots. The idea is that eyes may not sweep to the exact start of the next line, instead stopping a bit short.
Those little rapid eye movements between words and phrases? Those are called saccades. I had to look that up.
The impact of undershoots is what’s being challenged
The previous research we’ve relied on for years comes from 1940(!), a time when we obviously were more concerned with paper pages than bright digital displays. Longer lines, it said, increased the likelihood that eyes undershoot during a return sweep, and undershooting results in a regression that results in a 130ms to 250ms delay where the brain needs to get its bearings. The report refers to that as undersweep-fixation.
We can still process words during undersweep-fixation
This report cites a 2019 study that tried to correct undershoots by bolding the first word at the start of each new line, sort of like an anchor that naturally draws the eye closer to the left margin.
The 2019 study did find that the bolded words did decrease undershot return sweeps But despite that, reading speed did not improve. That’s the impetus for challenging the long-held assumption that shorter is better.
Mary explains further:
In seeking to reconcile why longer line lengths may not slow down reading on screen but do so when reading print, I outlined some differences, e.g. visual angle, time spent scrolling. But although physical differences between reading from screen and reading print still exist, we now have direct evidence to explain why longer lines were read at least as fast as shorter lines. Readers can process words during the brief fixation when undershooting the start of the new line. This saves time in subsequent processing. Now we might also acknowledge that there is greater consistency between the range of optimal line lengths for print and screen.
Where does this leave us today?
Well, nowhere closer to a clear answer we can use in our day-to-day work. But it’s good to dust off our collection of design and copywriting best practices and know that line length is less of a constraint than perhaps it has been.
Again, none of this tells us whether long or short lines are better. Mary ends her report by saying she cannot definitely recommend using longer lines of text because there are clear because there are still some factors at play, including:
Shorter lines are more effective for people with dyslexia.
More questions about return sweeps and undershooting need to be answered.
Several other studies indicate that users prefer shorter lines and judge longer lines as more difficult to read.
Scott digs into the history of the <menu> element. He traced it as far back as HTML 2 (!) in a 1994 changelog. The vibe then, it seems, was to mark up a list. I would suspect the intention is much like <nav> is today, but I really don’t know.
Short story: HTML 4 deprecated it, HTML 5 revived it—this time as a “group of commands”—and then HTML 5.2 deprecated it again. Kind of a bummer since it has some clear use cases.
So, it’s been quite the roller coaster for ol’ <menu>! There never seems to be any easy wins for HTML evolution. As of now, it’s in “don’t bother” territory:
I really wrote this post as a sort of counter point to the often uttered phrase “use semantic HTML and you get accessibility for free!” That statement, on its surface, is largely true. And you should use semantic HTML wherever its use is appropriate. <menu>, unfortunately, doesn’t really give us all that much, even though it has clearly defined semantics. Its intended semantics and what we actually need in reality are better served by either just using the more robust <ul> element, or creating your own role=toolbar, menubar, etc.. Using this semantic element, for semantics sake, is just that.
Zach did that thing where each of his blog posts has a special URL with the design of social image card that is screenshat by a headless browser (like Puppeteer) and used as a true meta Open Graph image, meaning it’s displayed on Twitter, Facebook, iMessage, Slack, Discord, and whatever else supports that card look.
I like it. Even though I’ve got a pretty good solution cooking now (for WordPress), the templates aren’t controlled with HTML/CSS like I wish they were.
As bit of yang to the ying here, Jim has some thoughts on the not-so-great Aspects of Open Graph images:
I feel like they’ve been hijacked by auto-generated computer imagery serving as attention-grabbing filler more than supportive expression.
It’s kinda like… we can add Open Graph images, and we essentially get a totally free massive clickable target for hungry fingers, so we do add Open Graph images — even when that image is, well, boring. Just auto-generated computer barf of title text with branding. Jim’s post has examples.
I get where Jim is coming from, and I suppose I’m guilty to some degree. I feel like we’re a cut-above on CSS-Tricks though, if you’ll pardon a taste of defensiveness, because:
We have a variety of templates to choose from to switch it up, like a quote design.
We incorporate custom imagery into the final card, meaning most cards are somewhat visually unique.
We don’t just brand the cards, we usually incorporate the author for a little extra high five for the person, rather than just our brand.
Reflecting back on this time, I think there are a few key concepts that were vital to things finally all making sense and fitting together. These were:
• The Box Model (e.g. box-sizing, height, width, margin, padding) • Layout (e.g. display) • Document Flow and Positioning (e.g. position, top, left, etc.)
I think this is good advice from Silvestar Bistrović:
An enabling selector is what I call a selector that does a job without disabling the particular rule.
The classic example is applying margin to everything, only to have to remove it from the final element because it adds space in a place you don’t want.
.card {
margin-bottom: 1rem;
}
/* Wait but not on the last one!! */
.parent-of-cards :last-child {
margin-bottom: 0;
}
That’s what Silvestar refers to as “enabling” because you’re only ever applying this rule — not applying it and then removing it with another selector later. I agree that’s harder to understand and error-prone.
I mean… You use them every day, on every OS. Everybody knows they exist in every toolbox. All that’s left is to “just pave the cowpaths!” But when you get right down to it, it’s a lot more complicated than that.
Brian Kardell shares a bit about the progress of bringing “Tabs” to HTML. We kinda think we know what they are, but you have to be really specific when dealing with specs and defining them. It’s tricky. Then, even if you settle on a solid definition, an HTML expression of that isn’t exactly clear. There are all kinds of expressions of tabs that all make sense in their own way. Imagine marking up tabs where you put all the tabs as a row of links or buttons up top, and then a bunch of panels below that. They call that a “Table of Contents” style of markup, and it makes some kind of logical sense (“the markup looks like tabs already”). But it also has some problems, and it looks like sections-with-headers is more practical (“If you have the heading, you can build the TOC, but not vice-versa”). Spicy sections are a totally different pattern. And that’s just one problem they are facing.
I don’t envy the work, but I look forward to the progress in no small part because authoring tabs is tricky business. Not hard to do, but very hard to do right. I’ve talked in the past about how I’ve built tabs many times in jQuery where just a click handler on a row of links hides or shows some matching divs below. That “works” if you ignore accessibility entirely (e.g. how you navigate between tabs, focus management, ARIA expectations, etc).
Reminds me of a little feature I like in Notion where if you type dash-arrow (like ->) it turns into → — but intelligently — like it doesn’t do that with inline code or a code block.
Donnie D’Amato built a whole site around the thesis that “digital designers still expect to use the grid while experienced layout engineers have moved beyond it.” The idea isn’t that we should never literally use display: grid; but rather that strict adherence to an overall page grid isn’t necessary. Brad’s reaction was interesting, as someone in and out of a lot more projects than I am:
One of the most frequent, confusing conversations w/ designers is “No, the pink lines that overlay design comps aren’t all that helpful for how things actually work in the browser.”
[…] throw your transparent pink 12-column grids in the trash can.
Donnie feels this is all in the spirit of responsive design, and I’m inclined to agree, except that browser technology has evolved quite a bit since the coining of responsive design and it might be time to call it something new. “Content-driven design” is one of Donnie’s headers and that’s a nice phrase.
This all resonated with Michelle as well:
CSS layout features like flexbox and Grid enable us to build more flexible layouts that prioritise content. We talk about intrinsic and extrinsic sizing in CSS — sizing based on both content and context. The promised container queries specification will put even more power in the hands of developers. But it feels to me like the design process is still stuck in the past.
[…] you should truly consider all other options before using a [browser window size] breakpoint. Ask, is the component expected to always be related to the page size (headers, modals, etc.)? Then a breakpoint might be acceptable. However, components that are placed deep within the page should not be using breakpoints to inform their layout.
Here’s a really good ol’ post from way back in 2015 all about the nine states of design and how we should think all the edge cases whenever we’re building interfaces. Vince Speelman writes:
Modern UI teams are designing components first; Interfaces are merely the thoughtful composition of components. This leaves an often glaring hole for users on “the unhappy path” — The places where users may, intentionally or not, stray from your idealized flow. As we learn to craft systems rather than pages, we must invest effort into shaping these often missed states of design and create with a component lifecycle that can support everyone. Here’s the lifecycle as I see it:
Nothing state
Loading
None
One
Some
Too many
Incorrect
Correct
Done
During the design process I think everyone (including me!) tends to focus on the ideal state of a component or interface, often leaving the extremely important edge cases forgotten until the last moment. I think I need to stick this list to my screen so I don’t forget it in my next project.
The marketing for Core Web Vitals (CWV) has been a massive success. I guess that’s what happens when the world’s dominant search engine tells people that something’s going to be an SEO factor. Ya know what language can play a huge role in those CWV scores? I’ll wait five minutes for you to think of it. Just kidding, it’s CSS.
Absolutely positioning things takes them out of flow and prevents layout shifting (but please don’t read that as we should absolute position everything).
Don’t load images you don’t have to (e.g. use a CSS gradient instead), which lends a bit of credibility to this.
There are a bunch more practical ideas in the article and they’re all good ideas (because good performance is good for lots of reasons), even if the SEO angle isn’t compelling to you.
But Bramus took it the rest of the nine yards and looked at all of the table enhancements. Every one of these is great. The kind of thing that makes CSS ever-so-slightly less frustrating.
Una is calling it the new responsive. A nod to the era we were most certainly in, the era of responsive design. Where responsive design was fluid grids, flexible media, and media queries, the new responsive is those things too, but slotted into a wider scope: user preference queries, viewport and form factor, macro layouts, and container styles.
I like the thinking and grouping here and I kinda like the name. It eludes to an evolution and extension of responsive web design rather than a rejection and replacement.
This isn’t the first crack at identifying and naming a shift between eras. Back in 2018, Jen Simmons was doing a talked called “Everything You Know About Web Design Just Changed” where she identified that responsive design was a major shift in how we did layout on the web. And yet, it was firmly defined in an era where layout tools like flexbox and grid didn’t even exist. Now, they do exist, and with them a bevy of other new features that bring more capable graphic design to the web. She called itIntrinsic Design.
I almost like Intrinsic Design more now than I did in 2018, because now, if we attempt to lump in @container queries, the name makes more intuitive sense. We (hopefully will soon) make styling choices based on the intrinsic size of elements. We make styling choices based on the intrinsic nature of the individual users we serve. We make styling choices off the intrinsic qualities of the browser.
I wouldn’t say either of the terms have really caught on though. It’s hard to make a name stick. That little burst of ideating around CSS4 sure didn’t go anywhere.
If somebody says a comment isn’t adding any value, I would ask: to whom?
Personally, I’ve never liked the advice that writing obvious comments is bad practice—probably because I write obvious comments all the time.
Jim showed off some examples of “code comments that are at the same level of fidelity as the code itself.” Those are the hardest calls with code comments.
// this function adds two numbers
function add(a, b) {
return a + b;
}
Easy to point at that and call it not useful. I tend not to leave this type of comment, but it’s fair play for Jim to question that. Comments can be used for a wide swath of people whom may at some point interact with that code, so why gate-keep it?
[…] comments can serve a very different purpose when they’re being read vs. when they’re being written. Those are almost two different kinds of activities.
I’d add they serve a different purpose when re-visiting old code vs actively working. Also different when you’re trying to code review versus directly contribute.