My CSS Foundation
Now that I am done talking about CSS resets, I want to follow up with CSS foundation—the starter kit for all my web projects. As I mentioned at the end of my last post, there are a lot of other interesting things in my CodePen template—namely the CSS layers and CSS vars. I will walk through those very opinionated things in this post. Friendly reminder that I am a graphic designer, and you are getting into very nerdy design territory.
Organized by CSS layers
@layer config, resets, custom;
@layer custom { ... }
@layer config { ... }
@layer resets { ... }
At the top of the CSS, I start with the layer order. With a defined order I can then write styles however I want—as long as I use @layer—and they would be organized in this order, preventing any specificity issues.
The custom layer would contain all the styles I write for the particular project and I would write this layer immediately following the layer order. This makes it convenient for myself, knowing that the top of the CSS contains the styles that I would actively work on, and the bottom contains styles that likely would not change.
Most of my one shots—single file websites[1][2][3][4][5]—utilize this approach.
Powered by CSS custom properties
@layer config {
/* CSS custom properties go here */
}
All the CSS resets I talked about in my last post[6] go into the resets layer. Then for things that I am a lot more opinionated about, they would go into the config layer. The config layer essentially contains a set of CSS custom properties (CSS vars) that I would use to build out components and layouts.
Colors
:root {
--color-fg: #c9d4de;
--color-bg: #040014;
--color-accent: #6161ff;
}
I don’t use a lot of colors. Just black and white plus a splash of accent here and there. So I only build components using 3 vars: fg (foreground), bg (background), and accent.
Spacing
:root {
--gutter: clamp(3ch, 2.5vmax, 3.75ch);
--stack: clamp(1.25ex, 2.5vmax, 1.75ex);
}
Gutter is the spacing in between columns, but here it is used for general horizontal spacing. I use a clamp of ch values because I think spacing should scale with the particular font’s width. The same goes for Stack, which handles general vertical spacing, except that its clamp uses ex values—dependent on x-height.
I am not a fan of creating spacing scales. When I apply spacing to components, I just use calc() to do math with these 2 base values.
:root {
--line-length: 75ch;
--page-max-inline-size: min(var(--line-length), 80dvi);
--page-padding-inline: calc((100dvi - var(--page-max-inline-size)) / 2);
}
The optimal line length on the web is between 45 to 80 characters. It helps to keep paragraphs of text more readable in long form content. I picked 75 because it feels right with most fonts.
Then I use the line length to define the page max width (inline-size). The min fuction here means: let the width be 80% of the viewport width but don’t let it go wider than 75ch.
Setting a page max width and centering the page content traditionally requires an extra .wrapper element to wrap the content, however, I like writing as less HTML as possible so I used math for the --page-padding-inline var. This var allows me to achieve the same centered content with a max width effect by defining just padding directly on the content, removing the need for a .wrapper element.
Typesetting
:root {
--font: system-ui, sans-serif;
--font-italic: "Georgia", serif;
--font-weight: 400;
--font-weight-semibold: 600;
--font-weight-bold: 900;
--font-size-min: 100%;
--font-size-max: 150%;
--font-size-clamp: clamp(
var(--font-size-min),
var(--font-size-min) * 0.9 + 0.5dvi,
var(--font-size-max)
);
--leading: 1.4;
}
These vars reflect my opinions on typography.
I do product design mostly and sans-serif is the familiar type in user interface design. Setting sans-serif the default rather than serif makes a lot more sense, but for italic text, I like changing it back to serif, because most italic sans-serifs are…ugly.
Variable fonts are cool, but having too many font-weights makes your design busy. I limit myself to just 3 weights. Depending on the font I choose to work with, I would redefine the values for these 3 weights. For example, in an elegant design, I might choose to use 300 for regular, 400 for semibold, and 600 for bold.
Most people mistaken a leading (line-height) of 1.5 as a standard or accessibility requirement and use it blindly, but I think 1.5 is actually too much for most sans-serif fonts. 1.4 is a better starting point. Typography is a visual judgment, you should be setting a leading that works best with the fonts you use. It should be different from project to project.
I also define vars for min and max font-sizes to make the typography fluid. Basically, the clamp I set up means this: all text will scale from 100% (16px) to 150% (24px) based on the inline-size of the viewport. Using the % unit allows for proper font-size and browser zoom—the --font-size-clamp would be used at the :root level. Read more about that in another one of my blog posts.[7]
Type scale
:root {
--pt-double-canon: 4.666rem;
--pt-canon: 3.999rem;
--pt-double-great-primer: 2.999rem;
--pt-double-english: 2.333rem;
--pt-double-pica: 2rem;
--pt-paragon: 1.666rem;
--pt-great-primer: 1.5rem;
--pt-english: 1.166rem;
--pt-pica: 1rem;
--pt-small-pica: 0.916rem;
--pt-long-primer: 0.833rem;
--pt-bourgeois: 0.75rem;
--pt-minion: 0.583rem;
}
Well, these are definitely the nerdiest vars in my foundation. In my Typography Manual, I recommend traditional point sizes for setting up a type scale to work with. These vars are equivalent to those traditional point sizes. You really can’t go wrong with these oldies but goodies.
Base styles, Assemble!
:root {
font-size: var(--font-size-clamp);
font: -apple-system-body;
font-family: var(--font);
font-weight: var(--font-weight);
line-height: var(--leading);
color: var(--color-fg);
background-color: var(--color-bg);
accent-color: var(--color-accent);
}
@supports (font: -apple-system-body) and (not (-webkit-touch-callout: default)) {
:root {
font-size: var(--font-size-clamp);
}
}
Lastly, I have some styles that apply the vars to define the baseline typography and colors. Note the font: -apple-system-body and @supports query, they are for the text to scale with iOS device font-size setting. It seems hacky, I might remove it later but now I am experimenting with it.
Welcome to the dark side
:root {
color-scheme: dark light;
}
@media (prefers-color-scheme: light) {
:root {
--color-fg: #404040;
--color-bg: #f4f4f4;
--color-accent: crimson;
}
}
@media (prefers-contrast: more) {
:root {
--color-fg: white;
--color-bg: black;
--color-accent: cyan;
}
}
@media (prefers-color-scheme: light) and (prefers-contrast: more) {
:root {
--color-fg: black;
--color-bg: white;
--color-accent: firebrick;
}
}
Yes, I love dark mode and I make sure to enable it by explicitly declaring color-scheme. This means my sites will show dark mode if the user enables dark mode in their device settings.
As for high contrast modes, I define the foreground and background to be true black and white.
That is all
Hope you got something out of this nerdy blog post. Feel free to use my CodePen teamplate[8] to try out your next web project. Happy coding.
Footnotes
- [1] Typography Manual
- [2] A11y Manual
- [3] Grid Manual
- [4] Command X
- [5] MCSS
- [6] You are not a CSS dev if you have not made a CSS reset
- [7] The Case for Defining Base Font-size
- [8] Super Minimal Resets