Fluid type sizes and spacing — Piper Haywood
Prompted by Utopia, Piper shares her methodology for fluid type in Sass.
Prompted by Utopia, Piper shares her methodology for fluid type in Sass.
I like this high-level view of the state of CSS today. There are two main takeaways:
This is exactly the direction we should be going in! More and more power from the native web technologies (while still remaining learnable), with less and less reliance on tooling. For CSS, the tools have been like polyfills that we can now start to remove.
Alas, while the same should be true of JavaScript (there’s so much you can do in native JavaScript now), people seem to have tied their entire identities to the tooling they use.
They could learn a thing or two from the trajectory of CSS: treat your frameworks as cattle, not pets.
There’s a really interesting discussion here, kicked off by Lea, about balancing long-term standards with short-term pragmatism. Specifically, it’s about naming things.
Naming things is hard. Naming things in standards, doubly so.
Elise Hein documents what it was like to build a website (or web app, if you prefer) the stackless way:
- use custom elements (for modular HTML without frameworks)
- use the in-browser package manager (for JavaScript packages without build tools)
- match pages with files (to avoid routing and simplify architecture)
- stick to standards (to avoid obsolescence and framework fatigue)
Her conclusions are similar to my own: ES6 modules mean you can kiss your bundler goodbye; web components are a mixed bag—it’s frustrating that Apple are refusing to allow native elements to be extended. Interestingly, Elise feels that a CSS preprocessor is still needed for her because she wants to be able to nest selectors …but even that’s on its way now!
Perhaps we might get to the stage where it isn’t an automatic default to assume you’ll need bundling, concatenation, transpiling, preprocessing, and all those other tasks that we’ve become dependent on build tools for.
I have a special disdain for beginner JavaScript tutorials that have you run
create-react-app
as the first step, and this exercise has only strengthened my conviction that every beginner programmer should get to grips with HTML, CSS and vanilla JS before delving into frameworks. Features native to the web are what all frameworks share, and knowing the platform makes for a stronger foundation in the face of change.
I’m not the only one swapping out Sass with CSS for colour functions:
Because of the declarative nature of CSS, you’re never going to get something as terse as what you could get in Sass. So sure, you’re typing more characters. But you know what you’re not doing? Wrangling build plugins and updating dependencies to get Sass to build. What you write gets shipped directly to the browser and works as-is, now and for eternity. It’s hard to say that about your Sass code.
I wrote recently about moving away from Sass to using native CSS features. I had this to say on the topic of mixins in Sass:
These can be very useful, but now there’s a lot that you can do just in CSS with
calc()
. The built-indarken()
andlighten()
mixins are handy though when it comes to colours.
I know we will be getting these in the future but we’re not there yet with CSS.
Anyway, I had all this in the back of my mind when I was reading Lea’s excellent feature in this month’s Increment: A user’s guide to CSS variables. She’s written about a really clever technique of combining custom properites with hsl()
colour values for creating colour palettes. (See also: Una’s post on dynamic colour theming with pure CSS.)
As so often happens when I’m reading something written by Lea—or seeing her give a talk—light bulbs started popping over my head (my usual response to Lea’s knowledge bombs is either “I didn’t know you could do that!” or “I never thought of doing that!”).
I immediately set about implementing this technique over on The Session. The trick here is to use separate custom properties for the hue, saturation, and lightness parts of hsl()
colour values. Then, when you want to lighten or darken the colour—say, on hover—you can update the lightness part.
I’ve made a Codepen to show what I’m doing.
Let’s say I’m styling a button
element. I make custom propertes for hsl()
values:
button {
--button-colour-hue: 19;
--button-colour-saturation: 82%;
--button-colour-lightness: 38%;
background-color: hsl(
var(--button-colour-hue),
var(--button-colour-saturation),
var(--button-colour-lightness)
);
}
For my buttons, I want the borders to be slightly darker than the background colour. When I was using Sass, I used the darken()
function to this. Now I use calc()
. Here’s how I make the borders 10% darker:
border-color: hsl(
var(--button-colour-hue),
var(--button-colour-saturation),
calc(var(--button-colour-lightness) - 10%)
);
That calc()
function is substracting a percentage from a percentage: 38% minus 10% in this case. The borders will have a lightness of 28%.
I make the bottom border even darker and the top border lighter to give a feeling of depth.
On The Session there’s a “cancel” button style that’s deep red.
Here’s how I set its colour:
.cancel {
--button-colour-hue: 0;
--button-colour-saturation: 100%;
--button-colour-lightness: 40%;
}
That’s it. The existing button declarations take care of assigning the right shades for the border colours.
Here’s another example. Site admins see buttons for some actions only available to them. I want those buttons to have their own colour:
.admin {
--button-colour-hue: 45;
--button-colour-saturation: 100%;
--button-colour-lightness: 40%;
}
You get the idea. It doesn’t matter how many differently-coloured buttons I create, the effect of darkening or lightening their borders is all taken care of.
So it turns out that the lighten()
and darken()
functions from Sass are available to us in CSS by using a combination of custom properties, hsl()
, and calc()
.
I’m also using this combination to lighten or darken background and border colours on :hover
. You can poke around the Codepen if you want to see that in action.
I love seeing the combinatorial power of these different bits of CSS coming together. It really is a remarkably powerful programming language.
CSS got some pretty nifty features recently. There’s the min()
and max()
functions. If you use them for, say, width
you can use one rule where previously you would’ve needed to use two (a width
declaration followed by either min-width
or max-width
). But they can also be applied to font-size
! That’s very nifty—we’ve never had min-font-size
or max-font-size
properties.
There’s also the clamp()
function. That allows you to set a minimum size, a default size, and a maximum size. Again, it can be used for lengths, like width
, or for font-size
.
Over on thesession.org, I’ve had some media queries in place for a while now that would increase the font-size
for larger screens. It’s nothing crucial, just a nice-to-have so that on wide screens, the font is bumped up accordingly. I realised I could replace all those media queries with one clamp()
statement, thanks to the vw
(viewport width) unit:
font-size: clamp(1rem, 1.333vw, 1.5rem);
By default, the font-size is 1.333vw
(1.333% of the viewport width), but it will never get smaller than 1rem
and it will never get larger than 1.5rem
.
That works, but there’s a bit of an issue with using raw vw
units like that. If someone is on a wide screen and they try to adjust the font size, nothing will happen. The viewport width doesn’t change when you bump the font size up or down.
The solution is to mix in some kind of unit that does respond to the font size being bumped up or down (like, say, the rem
unit). Handily, clamp()
allows you to combine units, just like calc()
. So I can do this:
font-size: clamp(1rem, 0.5rem + 0.666vw, 1.5rem);
The result is much the same as my previous rule, but now—thanks to the presence of that 0.5rem
value—the font size responds to being adjusted by the user.
You could use a full 1rem
in that default value:
font-size: clamp(1rem, 1rem + 0.333vw, 1.5rem);
…but if you do that, the minimum size (1rem
) will never be reached—the default value will always be larger. So in effect it’s no different than saying:
font-size: min(1.rem + 0.333vw, 1.5rem);
I mentioned this to Chris just the other day.
Anyway, I got the result I wanted. I wanted the font size to stay at the browser default size (usually 16 pixels) until the screen was larger than around 1200 pixels. From there, the font size gets gradually bigger, until it hits one and a half times the browser default (which would be 24 pixels if the default size started at 16). I decided to apply it to the :root
element (which is html
) using percentages:
:root {
font-size: clamp(100%, 50% + 0.666vw, 150%);
}
(My thinking goes like this: if we take a screen width of 1200 pixels, then 1vw
would be 12 pixels: 1200 divided by 100. So for a font size of 16 pixels, that would be 1.333vw
. But because I’m combining it with half of the default font size—50% of 16 pixels = 8 pixels—I need to cut the vw
value in half as well: 50% of 1.333vw
= 0.666vw
.)
So I’ve got the CSS rule I want. I dropped it in to the top of my file and…
I got an error.
There was nothing wrong with my CSS. The problem was that I was dropping it into a Sass file (.scss
).
Perhaps I am showing my age. Do people even use Sass any more? I hear that post-processors usurped Sass’s dominance (although no-one’s ever been able to explain to me why they’re different to pre-processers like Sass; they both process something you’ve written into something else). Or maybe everyone’s just writing their CSS in JS now. I hear that’s a thing.
The Session is a looooong-term project so I’m very hesitant to use any technology that won’t stand the test of time. When I added Sass into the mix, back in—I think—2012 or so, I wasn’t sure whether it was the right thing to do, from a long-term perspective. But it did offer some useful functionality so I went ahead and used it.
Now, eight years later, it was having a hard time dealing with the new clamp()
function. Specifically, it didn’t like the values being calculated through the addition of multiple units. I think it was clashing with Sass’s in-built ability to add units together.
I started to ask myself whether I should still be using Sass. I looked at which features I was using…
Variables. Well, now we’ve got CSS custom properties, which are even more powerful than Sass variables because they can be updated in real time. Sass variables are like const
. CSS custom properties are like let
.
Mixins. These can be very useful, but now there’s a lot that you can do just in CSS with calc()
. The built-in darken()
and lighten()
mixins are handy though when it comes to colours.
Nesting. I’ve never been a fan. I know it can make the source files look tidier but I find it can sometimes obfuscate what you’re final selectors are going to look like. So this wasn’t something I was using much any way.
Multiple files. Ah! This is the thing I would miss most. Having separate .scss
files for separate interface elements is very handy!
But globbing a bunch of separate .scss
files into one .css
file isn’t really a Sass task. That’s what build tools are for. In fact, that’s what I was already doing with my JavaScript files; I write them as individual .js
files that then get concatenated into one .js
file using Grunt.
(Yes, this project uses Grunt. I told you I was showing my age. But, you know what? It works. Though seeing as I’m mostly using it for concatenation, I could probably replace it with a makefile. If I’m going to use old technology, I might as well go all the way.)
I swapped out Sass variables for CSS custom properties, mixins for calc()
, and removed what little nesting I was doing. Then I stripped the Sass parts out of my Grunt file and replaced them with some concatenation and minification tasks. All of this makes no difference to the actual website, but it means I’ve got one less dependency …and I can use clamp()
!
Remember a little while back when I was making a dark mode for my site? I made this observation:
Let’s just take a moment here to pause and reflect on the fact that we can now use CSS to create all sorts of effects that previously required a graphic design tool like Photoshop.
It feels like something similar has happened with tools like Sass. Sass was the hare. CSS is the tortoise. Sass blazed the trail, but now native CSS can achieve much the same result.
It’s like when we used to need something like jQuery to do DOM Scripting succinctly using CSS selectors. Then we got things like querySelector()
in JavaScript so we no longer needed the trailblazer.
I’ve said it before and I’ll say it again, the goal of any good library should be to get so successful as to make itself redundant. That is, the ideas and functionality provided by the tool are so useful and widely adopted that the native technologies—HTML, CSS, and JavaScript—take their cue from those tools.
You could argue that this is what happened with Flash. It certainly happened with jQuery and Sass. I’m pretty sure we’ll see the same cycle play out with frameworks like React.
I think we’re often guilty of assuming that because our tools are great solutions for some things, they’re automatically the solution for everything.
I think Cathy might’ve buried the lede:
The knock on effect of this was removing media queries. As I moved towards some of the more modern features of CSS the need to target specific screen sizes with unique code was removed.
But on the topic of Sass, layout is now taken care of with CSS grid, variables are taken care of with CSS custom properties, and mixins for typography are taken care of with calc()
.
Personally, I’ve always found the most useful feature of Sass to simply be that you can have lots of separate Sass files that get combined into one CSS file—very handy for component libraries.
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.
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.
A lightweight style guide generator. This one uses SassDoc to parse out the documentation for colours, type, etc.
Rob walks us through the typographic choices for his recent redesign:
Most of what I design that incorporates type has a typographic scale as its foundation, which informs the typeface choices and layout proportions. The process of creating that scale begins by asking what the type needs to do, and what role contrasting sizes will play in that.
Time for another video from Patterns Day. Here’s Sareh Heidari walking us through Grandstand, the CSS framework at the BBC.
Dan describes his approach to maintainable CSS. It’s a nice balance between semantic naming and reusable styles.
Warning: the analogies used here might make you very, very hungry.
Tom wrote a post on Ev’s blog a while back called JavaScript Frameworks: Distribution Channels for Good Ideas (I’ve been hoping he’d publish it on his own site so I’d have a more permanent URL to point to, but so far, no joy). It’s well worth a read.
I don’t really have much of an opinion on his central point that browser makers should work more closely with framework makers. I’m not so sure I agree with the central premise that frameworks are going to be around for the long haul. I think good frameworks—like jQuery—should aim to make themselves redundant.
But anyway, along the way, Tom makes this observation:
Google has an institutional tendency to go it alone.
JavaScript not good enough? Let’s create Dart to replace it. HTML not good enough? Let’s create AMP to replace it. I’m just waiting for them to announce Google Style Sheets.
I don’t really mind these inventions. We’re not forced to adopt them, and generally, we don’t. Tom again:
They poured enormous time and money into Dart, even building an entire IDE, without much to show for it. Contrast Dart’s adoption with the adoption of TypeScript and Flow, which layer improvements on top of JavaScript instead of trying to replace it.
See, that’s a really, really good point. It’s so much easier to get people to adjust their behaviour than to change it completely.
Sass is a really good example of this. You can take any .css
file, save it as a .scss
file, and now you’re using Sass. Then you can start using features (or not) as needed. Very smart.
Incidentally, I’m very curious to know how many people use the scss syntax (which is the same as CSS) compared to how many people use the sass indented syntax (the one with significant whitespace). In his brilliant Sass for Web Designers book, I don’t think Dan even mentioned the indented syntax.
Or compare the adoption of Sass to the adoption of HAML. Now, admittedly, the disparity there might be because Sass adds new features, whereas HAML is a purely stylistic choice. But I think the more fundamental difference is that Sass—with its scss syntax—only requires you to slightly adjust your behaviour, whereas something like HAML requires you to go all in right from the start.
This is something that has been on my mind a lately while I’ve been preparing my new talk on evaluating technology (the talk went down very well at An Event Apart San Francisco, by the way—that’s a relief). In the talk, I made a reference to one of Grace Hopper’s famous quotes:
Humans are allergic to change.
Now, Grace Hopper subsequently says:
I try to fight that.
I contrast that with the approach that Tim Berners-Lee and Robert Cailliau took with their World Wide Web project. The individual pieces were built on what people were already familiar with. URLs use slashes so they’d be feel similar to UNIX file paths. And the first fledging version of HTML took its vocabulary almost wholesale from a version of SGML already in use at CERN. In fact, you could pretty much take an existing CERN SGML file and open it as an HTML file in a web browser.
Oh, and that browser would ignore any tags it didn’t understand—behaviour that, in my opinion, would prove crucial to the growth and success of HTML. Because of its familiarity, its simplicity, and its forgiving error handling, HTML turned to be more successful than Tim Berners-Lee expected, as he wrote in his book Weaving The Web:
I expected HTML to be the basic waft and weft of the Web but documents of all types: video, computer aided design, sound, animation and executable programs to be the colored threads that would contain much of the content. It would turn out that HTML would become amazingly popular for the content as well.
HTML and SGML; Sass and CSS; TypeScript and JavaScript. The new technology builds on top of the existing technology instead of wiping the slate clean and starting from scratch.
Humans are allergic to change. And that’s okay.
A good overview of ideas and techniques for structuring CSS and naming classes.
There’s a lot I disagree with here. I don’t think this pattern library process is very elegant or scalable, and it certainly wouldn’t work for me.
But I’m still linking to it. Why? Because I think it’s absolutely wonderful that people share their processes like this. It doesn’t matter one whit whether or not it would work for me.
Frontend development may have gotten a lot more complicated, but the simple premise of sharing what you’ve learned hasn’t.
I couldn’t agree more!
Linting CSS seems like a very good idea, especially if you’re not the only one writing the CSS. This guide is going to come in very handy when I give it a try.
Advice for writing Sass. I don’t necessarily agree with everything, but on the whole, this is a solid approach.
It’s worth bearing Chris’s advice in mind.