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:
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 forfont
, I usedhtml
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!
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:
- You don’t need to specify
calc
insidemin
,max
, orclamp
,min(a + b, c)
is completely valid syntax, just thought I’d mention it. - Use spaces inside calculations, so
calc(a + b)
becomescalc( a + b )
, to help legibility when using variables. Speaking of which… - Instead of using opaque, easy to forget values everywhere, use well-named variables to further help legibility.
- Also, break the calculations into meaningful chunks with variables too.
- Check for redundant calculations,
(-1 * var(--var) ) / 2
can be simplified tovar(--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 thehsl
values for future reference.
- I’d recommend
- 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, buta[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:
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:
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?
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 forbackground
), e.g.
- Or to counter this, consider putting