A Bi Disaster

CSS tips for better theming

Here lately, I've been spending more time on my blog's theme than I have on actually blogging. But the result of that effort is a theme that I am absolutely in love with. And I've also really grown to love CSS much more than I did previously.

It's unfortunate that CSS seems to have a very bad marketing department. People really tend to hate working with it. And that's sad because modern CSS is such a wonderful language. But I don't see a lot of people using modern CSS to make themes.

If you do theming, I think there are some features you should think about using. I'm definitely not saying you have to use these features. I just want to make you aware of their existence.

text-wrap: pretty

It's a small nicety, but I think we should generally be using text-wrap: pretty when theming. It currently is not widely supported as of this writing, but I think it's still ok to use because the fallback behavior is basically the same as what you already have.

What exactly does text-wrap: pretty do? Well that's really the question. According to the standard:

The user agent may among other things attempt to avoid excessively short last lines… but it should also improve the layout in additional ways. The precise set of improvements is user agent dependent, and may include things such as: reducing the variation in length between lines; avoiding typographic rivers; prioritizing different classes of soft wrap opportunities, hyphenation opportunities, or justification opportunities; avoiding hyphenation on too many consecutive lines.

So essentially, it really does mean "take this text and make it prettier". Not a very great standard in my opinion.

That noted, the text wrapping features I think are generally good. Safari is implementing a wider set of features that make for better typography. Chrome essentially just added support for removing orphans and called it a day.

Another feature you might see is support for text-wrap: balance, which is generally meant for headers. I personally don't like it because it leads to weird wrapping for headlines and compresses them to the left side of the page (or I suppose the right side if you're using rtl). That noted, it's a matter of taste, and you might like it better, so give it a shot.

clamp(), max(), and min()

Making your page responsive is easier than it's ever been. You can plug some numbers into a calculator and get a one-liner to make things size fluidly.

I've used these methods on this blog. Try resizing the window to make it narrower and wider and see how it looks.

Traditional methods of making things responsive have used media queries like below:

@media (width <= 1250px) {
  p {
    font-size: 1rem;
  }
}

@media (width <= 900) {
  p {
    font-size: 0.9rem;
  }
}

(I probably got this wrong because I almost never use media queries)

But this has a couple of problems:

  1. Yuck! Who wants to sit down and come up with 4 different width/font-size combinations?
  2. It ends up with a jankier user experience because font sizes will grow or shrink very suddenly if the user resizes their screen

But take a look at what you can do now:

p {
  /* 18px at 320px viewport */
  /* 21px at 1920px viewport */
  font-size: clamp(1.125rem, 1.0875rem + 0.1875vw, 1.3125rem);
}

I used this fluid type size generator to come up with this, but there are a zillion of these out there. It's hard to say if any of them are better than the others.

I promise it's not as scary as it looks. What this is doing is saying "At a width of 320px and below, the font size should be 1.125rem. At a width of 1440px and above, the font size should be 1.3125rem. In between use the screen width to calculate a size."

The difference between min() and max() is a bit counter-intuitive. If you want to give something a maximum value, you use min(). If you want to give it a minimum value, you use max(). I honestly haven't found too many uses for setting a minimum size on things, though I'm sure they exist.

I have found max() useful for setting body width, though with width it's admittedly syntactic sugar for using width and max-width. I've been setting screen width with this one-liner I found on MDN and it hasn't steered me wrong (though I've tweaked the max value a number of times).

body {
  width: min(70ch, calc(70% + 100px));
}

This is what I'm currently using, but it might change by the time you read this. This tells the browser to keep the body width to 70% screen width plus 100px, up to a maximum of 70ch1.

oklch

Yeah, I know. An acronym like oklch sounds intimidating. But it really does make coming up with color themes much easier. There's a full write-up about why it's better, but this is a shorter version.

It's well-supported in all major browsers, but if you have people visiting your site using KaiOS, QQ browser, or IE, they're going to just see black-and-white. I don't have anyone using those browsers looking at my blog, so it's not a big deal for me.

I think part of the reason we often have difficulty coming up with color schemes is that the way we format colors is not very human-friendly.

Pop quiz, and no cheating! What color is this text?

p {
  color: #b8b5fc;
}

Hard to tell by just looking at it, isn't it? RGB might be a little bit better, but I still don't find it much easier to interpret rgb(184 181 252)2. More importantly, how do I make this color a little bit darker or more saturated?

Let's take a look at this:

p {
  color: oklch(0.8 0.1 286);
}

It helps if you know what oklch stands for. The ok stands for the colorspace "Oklab", which isn't incredibly relevant for our purposes. The lch stands for lightness, chroma3, and hue. So right away, I can tell three things about this:

The only thing that is less obvious is what hue it is unless you have the hue factor memorized. If you haven't figured it out (and haven't cheated), the color is light purple.

It's also easier to modify. If I want to make it darker, I just lower the first number. If I want to make it more vibrant, I crank up the second number, though I did have to also make it darker to get higher chroma. If I want to make it clearer, I append the opacity to the end of the hue. Easy.

You can even make color changes dynamically using calc. So if you're making a theme and want to modify a color that's given as a CSS variable, you can do things like make it lighter. This is useful for situations like taking a link color and making it a touch lighter for visited links or hovered links. So you can do this:

a {
  color: var(--link-color);
}

a:visited {
  /* Make the link color a touch lighter */
  color: oklch(from var(--link-color) calc(l + 0.05) c h);
}

Another thing is that oklch can represent a wider variety of colors, but this is both a blessing and a curse.

It turns out that the number of colors monitors are capable of representing is very low. The common sRGB spec is limited to something like 30% of the visible spectrum. Newer gamuts like p3 basically double the amount of colors that can be represented. That allows you to support newer monitors if you so choose.

But there's a catch. It also allows you to represent colors that no monitor is currently capable of supporting. Color pickers like the one I linked to above help you get around this issue. Really though, if you choose something that's not supported by someone's monitor, it will find a fallback color.

But all of this is something new you have to worry about that you don't have to worry about using hex or rgb that are essentially supported everywhere.

I personally find that oklch makes theming a lot easier, so I'm using it.

CSS reset

Modern browsers have converged a lot in terms of compatibility, so full-featured CSS resets like normalize.css aren't really necessary anymore. But I still recommend a more minimal CSS reset. I'm currently using Josh Comeau's CSS Reset, which is very lightweight.

Minification

This decision is situational, and I think most themes probably don't need it. I minified the default theme, and it only saved 500 bytes. I am using it for my theme though, because the minified version is almost half the size of the unminified version. There is definitely some bloat I could remove though.

  1. 70ch is 70 characters, at least in theory. It should be noted that this size doesn't really describe what the user sees on the screen. The width of a ch is based on the size of your font's 0 character, so unless you're writing lines full of 0s it won't actually reflect the actual number of characters per line.

  2. Actually, I find it easy to interpret because some neovim plugin I have installed automatically colorizes it for me.

  3. Chroma is kinda sorta similar to saturation

#css #web apps