Explained: that very basic CSS
In my latest post, I mentioned I’d go into detail about what that very minimal CSS looked like and why. This is that post.
Here is the full CSS file:
/* @link https://utopia.fyi/type/calculator?c=320,16,1.414,1240,21,1.414,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
:root {
--measure: 65ch;
/* Step -2: 8.0024px → 10.5032px */
--font-step--2: clamp(0.5002rem, 0.4458rem + 0.2718vw, 0.6564rem);
/* Step -1: 11.3154px → 14.8515px */
--font-step--1: clamp(0.7072rem, 0.6303rem + 0.3844vw, 0.9282rem);
/* Step 0: 16px → 21px */
--font-step-0: clamp(1rem, 0.8913rem + 0.5435vw, 1.3125rem);
/* Step 1: 22.624px → 29.694px */
--font-step-1: clamp(1.414rem, 1.2603rem + 0.7685vw, 1.8559rem);
/* Step 2: 31.9903px → 41.9873px */
--font-step-2: clamp(1.9994rem, 1.7821rem + 1.0866vw, 2.6242rem);
/* Step 3: 45.2343px → 59.3701px */
--font-step-3: clamp(2.8271rem, 2.5198rem + 1.5365vw, 3.7106rem);
/* Step 4: 63.9613px → 83.9493px */
--font-step-4: clamp(3.9976rem, 3.5631rem + 2.1726vw, 5.2468rem);
/* Step 5: 90.4413px → 118.7043px */
--font-step-5: clamp(5.6526rem, 5.0382rem + 3.0721vw, 7.419rem);
--font-family-mono:
Consolas, Inconsolata, Menlo, Monaco, "Andale Mono", "Ubuntu Mono",
monospace;
}
@view-transition {
navigation: auto;
}
*,
*::after,
*::before {
box-sizing: border-box;
}
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
.visually-hidden:not(:focus):not(:active) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
body {
font-size: var(--font-step-0);
line-height: 1.6;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.2;
}
code,
pre {
font-family: var(--font-family-mono);
font-size: 0.825em;
}
.wrapper {
box-sizing: content-box;
margin-inline: auto;
max-inline-size: var(--measure);
padding-inline-start: 1rem;
padding-inline-end: 1rem;
}
.flow {
--flow-space: 1rem;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.flow > * {
margin-block: 0;
}
.flow > * + * {
margin-block-start: var(--flow-space);
}
Let’s break that down.
CSS custom properties (aka CSS variables)
:root {
--measure: 65ch;
/* Step -2: 8.0024px → 10.5032px */
--font-step--2: clamp(0.5002rem, 0.4458rem + 0.2718vw, 0.6564rem);
/* Step -1: 11.3154px → 14.8515px */
--font-step--1: clamp(0.7072rem, 0.6303rem + 0.3844vw, 0.9282rem);
/* Step 0: 16px → 21px */
--font-step-0: clamp(1rem, 0.8913rem + 0.5435vw, 1.3125rem);
/* Step 1: 22.624px → 29.694px */
--font-step-1: clamp(1.414rem, 1.2603rem + 0.7685vw, 1.8559rem);
/* Step 2: 31.9903px → 41.9873px */
--font-step-2: clamp(1.9994rem, 1.7821rem + 1.0866vw, 2.6242rem);
/* Step 3: 45.2343px → 59.3701px */
--font-step-3: clamp(2.8271rem, 2.5198rem + 1.5365vw, 3.7106rem);
/* Step 4: 63.9613px → 83.9493px */
--font-step-4: clamp(3.9976rem, 3.5631rem + 2.1726vw, 5.2468rem);
/* Step 5: 90.4413px → 118.7043px */
--font-step-5: clamp(5.6526rem, 5.0382rem + 3.0721vw, 7.419rem);
--font-family-mono:
Consolas, Inconsolata, Menlo, Monaco, "Andale Mono", "Ubuntu Mono",
monospace;
}
I don’t actually need all these font-steps right now, but they were generated using Utopia.fyi (which I discovered while working on Andy Bell’s Complete CSS course). For now, I wanted just the base font size set, with a minimum of 16 pixels and a maximum of 21 pixels.
The measure
is intended to be used to set max-inline-size
on elements to optimize readability. Sixty-five characters (ch
being the width of the 0
character in a font) is what I usually go with. There might be a need to make it smaller on smaller screens. I’ll have to think about how that might be achieved fluidly, using something like clamp
.
And there’s font-family-mono
. I could have left this to the system default, but with many old blog posts having code blocks, I wanted to put in the monospace font stack I used previously.
View transitions
This small block was part of the eleventy-base-blog template and I thought it looked nice and was a simple piece of progressive enhancement, so I kept it. Might play with view transitions more in the future.
@view-transition {
navigation: auto;
}
Box sizing
Because obviously. Border box is the sane way to go.
*,
*::after,
*::before {
box-sizing: border-box;
}
Visually hidden
This also came with the eleventy-base-blog template, and is one of the few classes I kept in the markup, to enable the skip navigation link.
/* https://www.a11yproject.com/posts/how-to-hide-content/ */
.visually-hidden:not(:focus):not(:active) {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
Minor typography improvements
body {
font-size: var(--font-step-0);
line-height: 1.6;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.2;
}
I wanted to get the base font size set up with the fluid type scale, so I set that on the body
, leaving headings alone for now. I did want to set the line heights for everything to be breathable, but with headings being more compact, so headings that go to multiple lines don’t look silly.
Inline code always looks large
Let’s change that by bumping the code
font size down a little. I didn’t use a type scale step here because using em
s will make the adjustment relative to the text it is alongside, which is what we want. There’s not mathematical logic here, 0.825em
was chosen purely by eyeing it up. I could have used a percentage value instead—they are equivalent in this scenario.
code,
pre {
font-family: var(--font-family-mono);
font-size: 0.825em;
}
Just a little bit more…
I could have left it at that. After all, the idea was to be very, very minimal to start with and build from there. But I couldn’t help myself. I didn’t want a 100% wide page for readability reasons. I added a simple wrapper utility:
.wrapper {
box-sizing: content-box;
margin-inline: auto;
max-inline-size: var(--measure);
padding-inline-start: 1rem;
padding-inline-end: 1rem;
}
This snippet comes from the best resource I’ve ever read about CSS layout (and perhaps CSS in general), Andy Bell and Heydon Pickering’s Every Layout. It’s your classic wrapper class. What might be new to you is using context-box
for the box sizing. If the box model was using border-box
, then setting a max-inline-size
with a padding-inline
would result in your actual content area being smaller than you intend with the measure
custom property. The padding-inline
is there to ensure you always have a bit of space on either side of your content, even when the width is smaller than your measure.
And finally, the one CSS utility class I always need
The first time I read about this pattern from Every Layout (where it’s called a Stack), my mind was blown. The whole concept of Every Layout is not only about thinking of layout in these composable, intrinsic components, but also rethinking the relationship between elements and how they fit together. Especially in modern component-driven front-end development and design systems, you never know where a particular component will be used. So why should it dictate how it’s laid out in its parent context? Why would you ever add a margin to it? The answer is you shouldn’t. That parent context should determine how children are laid out, and that’s what flow/stack does.
.flow {
--flow-space: 1rem;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.flow > * {
margin-block: 0;
}
.flow > * + * {
margin-block-start: var(--flow-space);
}
If you are unfamiliar with this pattern, you can read the full explanation from Every Layout for free. Basically, this allows you to provide consistent spacing between sibling elements via a parent. No need to target specific elements. No need for the children to handle that spacing themselves. It’s amazing when you think about it.
All for now
As I mentioned in this post already, I could have gotten away with less to start. The challenge now will be to build it up in a sensible and robust way. That comes next.