We’re living in somewhat of a CSS renaissance with new features, techniques, experiments, and ideas coming at us to an extent we haven’t seen since “CSS3”. It’s easy to feel overwhelmed when your profession seems to be advancing at breakneck speed, but it’s just as easy to be excited and inspired by what’s possible today that wasn’t even one year ago. While the complexity of CSS seems to be an ongoing discussion, Geoff Graham considers the ways “modern” CSS in 2023 has actually made CSS “easier” to write.
A little while back, I poked at a number of “modern” CSS features and openly evaluated whether or not they have really influenced the way I write styles.
Spoiler alert: The answer is not much. Some, but not to the extent that the styles I write today would look foreign when held side-by-side with a stylesheet from two or three years ago.
That was a fun thought process but more academic than practicum. As I continue thinking about how I approach CSS today, I’m realizing that the differences are a lot more subtle than I may have expected — or have even noticed.
CSS has gotten easier to write than it is different to write.
“
And that’s not because of one hot screaming new feature that changes everything — say, Cascade Layers or new color spaces — but how many of the new features work together to make my styles more succinct, resilient, and even slightly defensive.
Let me explain.
Efficient Style Groups #
Here’s a quick hit. Rather than chaining :hover
and :focus
states together with comma separation, using the newer :is()
pseudo-class makes it a more readable one-liner:
I say “more readable” because it’s not exactly more efficient. I simply like how it reads as normal conversation: An anchor that is in hover or in focus is styled like this…
Of course, :is()
can most definitely make for a more efficient selector. Rather than make up some crazy example, you can check out MDN’s example to see the efficiency powers of :is()
and rejoice.
Centering #
This
is a classic example, right? The “traditional” approach for aligning an
element in the center of its parent container was usually a no-brainer,
so to speak. We reached for some variety of margin: auto
to push an element from all sides inward until it sits plumb in the middle.
That’s still an extremely effective solution for centering, as the margin
shorthand looks at every direction. But say we only need to work in the inline
direction, as in left and right, when working in a default horizontal
left-to-write writing mode. That’s where the “traditional” approach
falls apart a bit.
Maybe “falls apart” is heavy-handed. It’s more that it requires dropping the versatile margin
shorthand and reaching specifically for two of its constituent
properties, adding up to one more line of overhead. But, thanks to the
concept of logical properties, we get two more shorthands of the margin
variety: one for the block direction and one for the inline
direction. So, going back to a situation where centering only needs to
happen in the inline direction, we now have this to keep things
efficient:
And
you know what else? The simple fact that this example makes the subtle
transition from physical properties to logical ones means that this
little snippet is both as equally efficient as throwing margin: auto
out there and resilient to changes in the writing mode. If the page
suddenly finds itself in a vertical right-to-left mode, it still holds
up by automatically centering the element in the inline direction when
the inline direction flows up and down rather than left and right.
Adjusting For Writing Modes, In General #
I’ve already extolled the virtues of logical properties. They actually may influence how I write CSS today more than any other CSS feature since Flexbox and CSS Grid.
“
Traditionally,
we might write one set of styles for whatever is the “normal” writing
direction, then target the writing mode on the HTML level using [dir="rtl"]
or whatever. Today, though, forget all that and use logical properties instead. That way, the layout follows the writing mode!
So, where we may normally need to reset a physical margin when changing writing modes like this:
… there’s no longer a need to rest things as long as we’re working with logical properties:
Trimming Superfluous Spacing #
Here’s another common pattern. I’m sure you’ve used an unordered list of links inside of a <nav>
for the main or global navigation of a project.
And
in those cases, I’m sure you’ve been asked to display those links
side-by-side rather than allowing them to stack vertically as an
unordered list is naturally wont to do. Some of us who have been writing
styles for some years may have muscle memory for changing the display
of those list items from default block-level elements into inline
elements while preserving the box model properties of block elements:
You’re
going to need space between those list items. After all, they no longer
take up the full available width of their parent since inline-block
elements are only as wide as the content they contain, plus whatever
borders, padding, margin, and offsets we add. Traditionally, that meant
reaching for margin
as we do for centering, but only the constituent margin
property that applies the margin in the inline direction we want, whether that is margin-left
/margin-inline-start
or margin-right
/margin-inline-end
.
Let’s assume we’re working with logical properties and want a margin at the end of the list of items in the inline direction:
But wait! Now we have margin on all of the list items. There’s really no need for a margin on the last list item because, well, there are no other items after it.
That
may be cool in the vast majority of situations, but it leaves the
layout susceptible. What if, later, we decide to display another element
next to the <nav>
? Suddenly, we’re dealing with superfluous spacing that might affect how we decide to style that new element. It’s a form of technical debt.
It
would be better to clean that up and tackle spacing for reals without
that worry. We could reach for a more modern feature like the :not()
pseudo-class. That way, we can exclude the last list item from participating in the margin party.
Even easier? Even more modern? We could reach for the margin-trim
property, which, when applied to the parent element, chops off
superfluous spacing like a good haircut, effectively collapsing margins
that prevent the child elements from sitting flush with the parent’s
edges.
Before any pitchforks are raised, let’s note that margin-trim
is experimental and only supported by Safari at the time I’m writing this. So, yes, this is bleeding-edge
modern stuff and not exactly the sort of thing you want to ship
straight to production. Just because something is “modern” doesn’t mean
it’s the right tool for the job!
In fact, there’s probably an even better solution without all the caveats, and it’s been sitting right under our noses: Flexbox.
Turning the unordered list into a flexible container overrides the
default block-level flow of the list items without changing their
display, giving us the side-by-side layout we want. Plus, we gain access
to the gap
property, which you might think of as margin
with margin-trim
built right in because it only applies space between the children rather than all sides of them.
This is what I love about CSS. It’s poetic in the sense that there are many ways to say the same thing — some are more elegant than others — but the “best” approach is the one that fits your thinking model. Don’t let anyone tell you you’re wrong if the output is what you’re expecting.
Just
because we’re on the topic of styling lists that don’t look like lists,
it’s worth noting that the common task of removing list styles on both
ordered and unordered lists (list-style-type: none
) has a side effect in Safari that strips the list items of its default accessible role. One way to “fix” it (if you consider it a breaking change) is to add the role back in HTML a là <ul role="list>
. Manuel Matuzović has another approach that allows us to stay in CSS by removing the list style type with a value of empty quotes:
I appreciate that Manuel not only shared the idea but has provided the results of light testing as well while noting that more testing might be needed to ensure it doesn’t introduce other latent consequences.
Maintaining Proportions #
There’s
no need to dwell on this one. We used to have very few options for
maintaining an element’s physical proportions. For example, if you want a
perfect square, you could rely on fixed pixel units explicitly declared
on the element’s width
and height
:
Or,
perhaps you need the element’s size to flex a bit, so you prefer
relative units. In that case, something like percentages is difficult
because a value like 50%
is relative to the size of the
element’s parent container rather than the element itself. The parent
element then needs fixed dimensions or something else that’s completely
predictable. It’s almost an infinite loop of trying to maintain the 1:1
proportion of one element by setting the proportion of another
containing element.
The so-called “Padding Hack” sure was a clever workaround and not really much of a “hack” as much as a display of masterclass-level command of the CSS Box Model. Its origins date back to 2009, but Chris Coyier explained it nicely in 2017:
“If we force the height of the element to zero (`height: 0;`) and don’t have any borders, then the padding will be the only part of the box model affecting the height, and we’ll have our square.”
— Chris Coyier
Anyway,
it took a lot of ingenious CSS to pull it off. Let’s hear it for the
CSS Working Group, which came up with a much more elegant solution: an aspect-ratio
property.
Now, we have a perfect square no matter how the element’s width responds to its surroundings, providing us with an easier and more efficient ruleset that’s more resilient to change. I often find myself using aspect-ratio
in place of an explicit height
or width
in my styles these days.
Card Hover Effects #
Not
really CSS-specific, but styling a hover effect on a card has
traditionally been a convoluted process where we wrap the element in an <a>
and hook into it to style the card accordingly on hover. But with :has()
— now supported in all major browsers as of Firefox 121! — we can put the link in the card as a child how it should be and style the card as a parent element when it *has* hover.
That’s way super cool, awesome, and easier to read than, say:
Creating And Maintaining Color Palettes #
A long, long time ago, I shared how I name color variables in my Sass files. The point is that I defined variables with hexadecimal values, sort of like this in a more modern context using CSS variables instead of Sass:
There’s nothing inherently wrong with this. Define colors how you want! But notice that what I was doing up there was manually setting a range of grayscale colors and doing so with inflexible color values. As you might have guessed by this point, there is a more efficient way to set this up so that it is much more maintainable and even easier to read.
Those aren’t exactly 1:1 conversions. I’m too lazy to do it for real, but you get the idea, right? Right?! The “easier” way may *look* more complicated, but if you want to change the main color, update the --primary-color
variable and call it a day.
Perhaps a better approach would be to change the name --primary-color
to --grayscale-palette-base
. This way, we can use the same sort of approach across many other color scales for a robust color system.
Managing color systems is a science unto itself, so please don’t take any of this as a prescription for how it’s done. The point is that we have easier ways to approach them these days, whereas we were forced to reach for non-CSS tooling to even get access to variables.
Managing Line Lengths #
Two things that are pretty new to CSS that I’m absolutely loving:
- Character length units (
ch
); text-wrap: balance
.
As
far as the former goes, I love it for establishing the maximum width of
containers, particularly those meant to hold long-form content.
Conventional wisdom tells us that an ideal length for a line of text is somewhere between 50-75 characters per line, depending on your source. In a world where font sizing can adapt to the container size or the viewport size,
predicting how many characters will wind up on a line is a guessing
game with a moving target. But if we set the container to a maximum
width that never exceeds 75 characters via the ch
unit and a
minimum width that fills most, if not all, of the containing width in
smaller contexts, that’s no longer an issue, and we can ensure a
comfortable reading space at any breakpoint — without media, to boot.
Same
sort of thing with headings. We don’t always have the information we
need — font size, container size, writing mode, and so on — to produce a
well-balanced heading. But you know who does? The browser! Using the
new text-wrap: balance
value lets the browser decide when to wrap text in a way that prevents
orphaned words or grossly unbalanced line lengths in a multi-line
heading. This is another one of those cases where we’re waiting on
complete browser support (Safari, in this instance).
Still, it’s also one of those things I’m comfortable dropping into
production now as a progressive enhancement since there’s no negative
consequence with or without it.
A word of caution, however, for those of you who may be tempted to apply this in a heavy-handed way across the board for all text:
Not only is that an un-performant decision, but the balance
value is specced in a way that ignores any text that is longer than ten lines. The exact algorithm, according to the spec, is up to the user agent and could be treated as the auto
value if the maximum number of lines is exceeded.
text-wrap: pretty
is another one in experimentation at the moment. It sounds like it’s similar to balance
but in a way that allows the browser to sacrifice some performance
gains for layout considerations. However, I have not played with it, and
support for it is even more limited than balance
.
How About You? #
These are merely the things that CSS offers here in late 2023 that I feel are having the most influence on how I write styles today versus how I may have approached similar situations back in the day when, during writing, I had to walk uphill both ways to produce a stylesheet.
I can think of other features that I’ve used but haven’t fully adopted in my toolset. Those would include things like the following:
- Cascade Layers used mostly in CodePen demos.
- Container Queries in isolated cases as I continue to wrap my head around them.
<selectmenu>
which, OK, is HTML; but HTML that extends CSS powers.- CSS Nesting, but I’m sure that’s bound to make its way into my work.
What say you? I know there was a period of time when some of us were openly questioning whether there’s “too much” CSS these days and opining that the learning curve for getting into CSS is becoming a difficult barrier to entry for new front-enders. What new features are you finding yourself using, and are they helping you write CSS in new and different ways that make your code easier to read and maintain or perhaps “re-learning” how you think about styles?
No comments:
Post a Comment