Seven Snippets of Modern CSS I Used To Rebuild My Site

Display mode

Back to Articles

Back in the depths of time (we're talking perhaps 2008), I set up a site on the imrannazar.com domain with a set of articles lifted from an even older page, and some CSS based on what seemed like a good idea at the time. This is how that came out in the Way Back When:

2008-vintage screenshot of this place
Figure 1: Screenshot of this place before the redesign
(Courtesy of the Wayback Machine)

Fifteen years later, this build was starting to really show its age: built in an era before mobile viewports were common, it would always render as a desktop site, and it had various vestiges of Internet Explorer compatibility, such as this excellent hack that was common at the time to allow for transparent PNG support.

#wrapper #foot {
  filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(
    sizingMethod=crop,
    src='../img/foot.png'
  );
}
#wrapper > #foot {
  background: url(../img/foot.png) no-repeat top left;
}

When the idea of rebuilding this place came about towards the end of 2023, it seemed high time to make use of some of the more modern CSS features that have slowly crept into common usage and good browser support in the intervening span. Let's pick through the stylesheet and see which CSS techniques are being used that weren't at the time of the previous design.

Variables

At the top of the new stylesheet, we find:

:root {
  --g-bg: #e5dacc;
  --g-text: black;
  --g-fig: #d4c1aa;
  ...
}
body {
  background: var(--g-bg);
  color: var(--g-text);
}

Defining these variables on the :root pseudo-class makes them available throughout the stylesheet, meaning we can define colours or other values in one place. This makes switching out and testing of colour themes and palettes much easier; I tried a few palettes before settling on the current values, which was made simple by not having to hunt for usages of the particular colour values to swap them out.

It also makes colour usage more semantic: the page background isn't an arbitrary hex string, it's --g-bg and that's much more readable when scanning through the rest of the styling. This means that such features as, for example, switching the site to dark mode are made possible simply by switching out the definitions in this block.

Now there's an idea... Let me get back to that another time.

Webfonts

Immediately below the variable definition, we find a couple of webfont definitions for the header (Over the Rainbow) and code blocks (Inconsolata):

@font-face {
  font-family: 'OverTheRainbow';
  font-weight: normal;
  font-style: normal;
  src: url(/assets/overtherainbow.ttf) format('truetype');
 }
 @font-face {
   font-family: 'Inconsolata';
   font-weight: normal;
   font-style: normal;
   src: url(/assets/inconsolata.ttf) format('truetype');
 }

With these fonts defined, their names can be used in any following rule. For example, code blocks have this styling:

main samp pre {
  font: 16pt Inconsolata, monospace;
  background: var(--g-fig);
}

Webfont definitions support multiple file formats; for simplicity and highest compatibility, I've used the TTF files here.

Now, webfonts had been a thing for some time when the previous site design was put together, but the main thing preventing their use in the design at the time was...

transform: rotate

A stylistic choice for the page header was to rotate the text slightly, but back in '08 this wasn't possible in a well-supported way. Modern browser support for the transform rule is fairly complete, so this is finally useful. The syntax of the rule itself is fairly simple:

h1 {
  ...
  text-align: center;
  transform: rotate(-3deg);
}

This also means I don't have to implement the headers of each page as a transparent PNG laid over the header graphic, which significantly reduces page weight and helps not only with load speed, but with accessibility: having the page's main header be an image can be confusing when passed through a screenreader or other accessibility tools, but as text the header's existence is made clear.

font-display: fallback

The main disadvantage of webfonts is that they need to be loaded in by the browser, and that can only occur after the CSS has been downloaded:

For the period between the CSS being available, and the webfonts loading in, the default behaviour is to blank out the element being rendered for lack of a font to use. You can, however, specify a fallback behaviour which is to use the second font defined against the element (and if that's also a webfont, fall back further until the browser reaches a standard font that's on the machine).

Having these elements visible (in the wrong font) before their fonts load in may seem weird from a design perspective, but it's good for accessibility and page responsiveness: if you can see all the text on the page immediately, a screenreader (or a search engine's bot, for that matter) can too, and it won't have to wait for everything to load.

Fallback behaviour can be configured on the @font-face:

@font-face {
  font-family: 'OverTheRainbow';
  font-weight: normal;
  font-style: normal;
  src: url(/assets/overtherainbow.ttf) format('truetype');
  font-display: fallback;
}
h1 {
  ...
  font-family: OverTheRainbow, sans-serif;
}

There's one more piece of the puzzle regarding the behaviour of the page heading on this site, and it's how the header responds to resizing and different sizes of viewport. For that, we come to perhaps the best new addition to CSS of all:

font-size: clamp

With clamp, one is able to specify a flexible measurement with a range above/below which the measurement won't go. For example, the h1 on this page is defined in full as below:

h1 {
  font: clamp(24px, 3.5vw, 60px)/1.4 OverTheRainbow, sans-serif;
}

Let's break this down:

And if you're thinking these figures look a little arbitrary, that's because they are: some experimentation was needed to arrive at values which work sensibly with the design.

Clamping the font size handles responsiveness of the heading text, but the other component that resizes is the scrap-of-paper design, which is an element background. For this, we need...

background-size: contain and aspect-ratio

In the way back when, you could define image backgrounds on elements in CSS, but your options were to have them repeat horizontally/vertically or ...not. Modern CSS gives you two new tools as options to background-size: there's cover, which tries to completely cover the element by sizing up the background and cropping the edges, and there's contain which tries to fit the image into the element in full, perhaps leaving uncovered gaps in either the vertical or the horizontal.

For our use-case, we need contain because we want the whole image visible:

header {
  background: url(/assets/headback.webp) no-repeat top left;
  background-size: contain;

  width: 100%;
  min-width: 900px;
  max-width: 1450px;

  aspect-ratio: 145/35;
  max-height: 350px;
}

With this set of rules, we're giving the browser significant contraints in how it's to render the header: full-width, but within a particular range, and always maintaining a 145/35 aspect ratio. In addition, when the browser comes to fill in the background, contain means it will lean towards keeping the full image visible rather than trying to crop any edges.

Things not covered here

We've made it as far as defining the header, and already modern CSS has come in very useful for creating a design that was simply not possible in the dark days of the late oughts. As we come down the page, there are more CSS features in use that we won't cover in detail here:

And there are features coming to wider support in CSS that haven't yet been employed here, the most compelling of which is rule nesting. To take a sample from further down the stylesheet:

main samp {
  font: 16pt Inconsolata, monospace;
  background: var(--g-fig);
  display: block;
}
main samp pre { margin: 0; padding: 1em; overflow: auto; }
main samp kbd { color: var(--g-code-keyword); }
main samp var { color: var(--g-code-var); }
main samp s { color: var(--g-code-comment); text-decoration: none; }

There's a bunch of redundancy in the match specifications here, and one of the more compelling reasons to use CSS pre-processors like Sass has, in the past, been that one can nest rules like this to make for cleaner code to work with:

main samp {
  font: 16pt Inconsolata, monospace;
  background: var(--g-fig);
  display: block;
  
  pre { margin: 0; padding: 1em; overflow: auto; }
  kbd { color: var(--g-code-keyword); }
  var { color: var(--g-code-var); }
  s { color: var(--g-code-comment); text-decoration: none; }
}

A pre-processor means a build step, which means a build process, and suddenly your plain HTML/CSS site has become a Whole Thing. With CSS nesting coming to browsers, this will soon be natively supported client-side, and will no longer be a reason to have a build process at all.

Overall, I'm happy with how the refresh has come out; here's to another fifteen years. Let's see how CSS advances in the interim, and what's possible (and widely supported) the next time I get bored and decide to rebuild this place.