A Friendly Introduction to SVG • Josh W. Comeau
A fantastic explanation of the building blocks of SVG, illustrated—as always—with Josh’s interactive examples.
A fantastic explanation of the building blocks of SVG, illustrated—as always—with Josh’s interactive examples.
This is a really thoughtful look at the evolution of CSS and the ever-present need to balance power with learnability.
A UI library for people who love HTML, powered by modern CSS and Web Components.
A great talk by Matthias on what you can do with web standards today!
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 em
s or rem
s. 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.
I should be using the lh
and rlh
units more enough—they’re supported across the board!
Everything you ever wanted to know about text-wrap: pretty
in CSS.
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 legend
s 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.
This looks like a really interesting proposal for allowing developers more control over styling inputs. Based on the work being done the customisable select
element, it starts with a declaration of appearance: base
.
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.
- Springy easing with
linear()
- Typed custom properties
- View transitions for page navigation
- Transition animation for
dialog
andpopover
- Transition animation for
details
- Animated adaptive gradient text
Some interesting experiments in web typography here.
It’s great to see the evolution of HTML happening in response to real use-cases—the turbo-charging of the select
element just gets better and better!
CSS wants you to build a system with it. It wants styles to build up, not flatten down.
Truth!
I like the approach here: logical properties and sensible default type and spacing.
I’m very glad to see that work has moved away from a separate selectmenu
element to instead enhancing the existing select
element—I could never see an upgrade path for selectmenu
, but now there are plenty of opportunities for progressive enhancement.
Laying out sheet music with CSS grid—sounds extreme until you see it abstracted into a web component.
We need fluid and responsive music rendering for the web!
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.
This is a great thought exercise in progressive enhancement …that Scott then turns into a real exercise!
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.
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!