OVERHEAD

Styling with style

CSS

Learning the “basics”

CSS is both deceptively easy and deceptively hard to get right, it can quickly turn your own rules against you when you least expect it, however, many of the problems I’ve discovered turned out to have nice, but underutilised solutions that no one talks about.

So, here’s a couple of them.

Responsive font-size, while respecting browser defaults

Styling a website so font-size can be both responsive and respect browser defaults is quite challenging, quite a few resources I read stated you couldn’t have both, however, you can actually style the html element so that 1rem gains a new meaning:

Example of styling the html element with font to reset the value of 1rem.

html {
	font: calc(1rem + 0.22vw)/1.8 sans-serif;
}

p {
	font-size: 1rem;
}

Syntax: font: font-size/line-height font-family;

At first, it may look like the p rule overrides what the html just did, however, setting the font-size in the html element actually redefines 1rem to be equal to calc(1rem + 0.22vw). The 1rem inside the calculation is the browser default, and the 0.22vw allows text to scale with viewport width, so now, all text on the website is responsive and respects the browser default.

You can use the html or :root element for font, I used html since I personally only use :root for variables.

Math can be is great!

calc, min, max, and clamp have a ton of utility, but it can get challenging to work with once you start nesting them. I mean, look at this!

A mess of CSS calculations nested inside each other.

max((100vw - 2 * max(6vmin, 1.2rem) - min(48rem, 100vw - 2 * max(6vmin, 1.2rem))) / -2, -6vmax)

I am exaggerating a little, this is just the --gap-offset variable used for the “elements wider than article” magic when fully expanded, however, even in simpler forms, this stuff can get messy fast. So, here’s some tips:

  1. You don’t need to specify calc inside min, max, or clamp, min(a + b, c) is completely valid syntax, just thought I’d mention it.
  2. Use spaces inside calculations, so calc(a + b) becomes calc( a + b ), to help legibility when using variables. Speaking of which…
  3. Instead of using opaque, easy to forget values everywhere, use well-named variables to further help legibility.
  4. Also, break the calculations into meaningful chunks with variables too.
  5. Check for redundant calculations, (-1 * var(--var) ) / 2 can be simplified to var(--var) / -2 for instance.

Here’s what that same calculation looks like in my stylesheet (as of writing):

:root {
	--body-margin: max( 6vmin, 2 * var(--text-padding) );
	--body-width: calc( 100vw - 2 * var(--body-margin) );
	--article-width: 48rem;
	--text-padding: 0.6rem;
	--current-article-width: min( var(--article-width), var(--body-width) );
	--article-body-gap: calc( ( var(--body-width) - var(--current-article-width) ) / -2 );
	--gap-usage: -6vmax;
	--gap-offset: max( var(--article-body-gap), var(--gap-usage) );
}

Not only is it easier to parse, but, each sub-calculation is usable separately if desired… oh right, I AIN’T DONE WITH VARIABLES!

VARIABLES!!1!

I use variables extensively, just hit F12 and see for yourself, and with good reason(s). In no particular order:

  • Declaring variables in :root saves having to chase duplicate values when you want to change all of them, especially values for dimension calculations.
    • As a bonus, experimenting with different values is a lot easier.
    • But, remember to give your vars concise names; the more descriptive, the better.
  • Doing fun things with HSL colours is a lot easier with variables, like doing --alt-accent-h: calc( var(--accent-h) - 120 ); to generate new hues for colour palettes.
    • I’d recommend /* commenting */ the original HEX or RGB values next to the hsl values for future reference.
  • Alternate stylesheets intended to override the main stylesheet need to do a lot less if they just specify different variable values.
  • You may start noticing duplicate styling in seemingly unrelated items when you begin refactoring things into variables, this can lead to less rules overall (e.g. code, kbd, samp, var, a[rel="tag"] share a ruleset, but a[rel="tag"] overrides what it needs).

More is… the same?

Sometimes, an element declares a million different properties, most of which are already the default in spec-compliant browsers, however, you copy and pasted it from somewhere, it looked complicated, and you didn’t check… right?

But other times, those millions of properties are tasked with overriding less specific properties that shouldn’t even be there in the first place. As an example:

A simple example of an <a> rule, followed by many different rules overriding the styling later.

a {
	text-decoration: underline 0.06rem blue;
}

millions > of a, different[rules="1000000"] a {
	text-decoration-color: transparent;
}

All a elements are given underlines, but only a few elements should have underlines on their <a> elements; the rule is too broad. Inverting the rules, so only elements which want underlined links set the underline colour, addresses this:

a {
	text-decoration: underline 0.06rem transparent;
}

article a {
	text-decoration-color: blue;
}

This goes unnoticed constantly, so always be mindful of where you want styling to be applied whenever you’re fighting your own properties.

And there’s more than you think

Here’s a classic case:

A simple example showing an <element> being styled with margin, and then a more specific rule overriding all the margin properties again.

element {
	margin: 1px 2px;
}

element#something {
	margin: 1px 2px 3px;
}

Syntax: margin: top(+bottom) right(+left) bottom left;

We can avoid repeating the 2px value by only targeting the bottom value:

element#something {
	margin-bottom: 3px;
}

But, what if you wanted to rework this?

A less straightforward example, with both the top and bottom margin values being overridden.

element {
	margin: 1px 2px;
}

element#something {
	margin: 3px 2px 3px;
}

Most people jump straight to margin, however, the underutilised margin-block and margin-inline properties, for vertical and horizontal margins respectively, can assist you here:

element#something {
	margin-block: 3px;
}

“Horizontal” and “vertical” depends on the writing-mode of the browser, keep that in mind if you’re targeting multiples languages with different reading directions.

The point being, always consider the individual, shorthand, and -block & -inline variants of properties when styling elements, and you’ll save yourself a lot of typing.

Rapid fire

And here’s some misc advice I’ve thrown in:

  • If you use :hover, you should probably specify :focus or :focus-within too. If you want to see why, hit TAB a bunch to see that none of your animations work with keyboard navigation.
  • Order your properties consistently, e.g. Box Model: “display, position, height, width, margin, border, padding, font, etc”, it’s easier to compare rulesets across properties this way.
  • If your rules are short, consider inlining them for compactness, e.g. figure { margin: var(--text-padding) var(--gap-offset); }.
    • Or to counter this, consider putting transition values onto multiple lines if you’re specifying more than two properties (same goes for background), e.g.

An example of a transition property being spread across multiple lines.

element {
	transition:
		property-1: ... ,
		property-2: ... ,
		property-3: ... ;
}