Styling prose on the web

This is part 1 of a series of posts on styling web prose. "Minimum viable style" is a play on the common phrase used by technology teams, "minimum viable product", which, confusingly, has the same abbreviation as "most valuable player".

By "minimum viable style" I mean the mimumum styles you need to make text readable. Everything beyond that is just cherries on top. If you like cherries, though, you can check out part 2 of this series when you finish this.

There's oceans of content on the web, much of which is like what you're reading now: text.

How text is styled on the web—its font, layout, color—is analogous to the clarity of the water. If it's too murky, you can't see much. But if it's perfectly clear, you won't even notice it. You'll be too busy marvelling at all the amazing stuff under the sea.

At least this is how I approach styling text on the web: styles serve to clarify and illuminate content, and when done right, should hardly be noticed.

Plain text

At first glance, making text readable shouldn't be too hard. It's just text. There aren't a whole lot of variables to work with... well besides the font-family, font-size, line-height, alignment, margin, font-weight, font-color, background-color, text-decoration. Okay, maybe that is a lot.

But you don't need to set all of those variables everywhere all the time. You just need to set a few of them once, in one place.

Just these few lines of CSS (Cascading style sheets, the primary way to style text on the web) can make text surprisingly readable.

        
  body {
    max-width: 65ch;
    padding: 1rem 1rem;
    margin: auto;
    font-size: 1.5em;
    line-height: 1.75;
    font-family: sans-serif;
  }
      

I adapted this ruleset from this post by swyx. Let's break it down line by line.

First, the selector: body

This defines the HTML elements that a ruleset applies to. I chose body since it is a body of text after all (or a body of water if we're still swimming in the ocean metaphor). Also text is almost always within the body tag of the HTML.

Now let's get into the 6 properties in this ruleset.

Max width: {`max-width: 65ch`}

First we have the max-width. According to this research by the Baymard Institute, the optimal "readable" range is measured in characters and should be about 50-75 characters wide.

Conveniently, CSS let's you express width in character units using the ch unit. (Meaning you don't have to worry about adjusting width whenever you adjust font-size.)

Padding: padding: 1rem 1rem

Next is padding (not to be confused with margin). The first number represents top/bottom and the second left/right padding.

Setting a small amount of left/right padding keeps the text from going all the way to the edges of a screen on devices smaller than the max-width, which can put the reader on edge.

A little top/bottom padding also gives you some breathing room at the top and bottom of the page.

The rem represents "root element units". These are like pixel units, except that they are relative. This means when zoomed in or out, they increase or decrease in size proportionally, as opposed to pixel units which are still a pixel no matter how much you zoom. em are similar to rem but relative to their parent instead of the :root element. This blog post does a great deep dive into the nuances of pixels vs. rem/em.

Margin: margin: auto

Next we have margin which is set as auto. This means the text automatically when the screen width is greater than the max-width.

Font size: font-size: 1.5rem

The font-size is set to 1.5rem. This can be set by feel, but body text should generally be between .9-1.5rem (14px-24px) according to this article.

Line height: line-height: 1.75

The line-height should be at least 1.5 for clarity. It's left unitless on purpose so it's relative to the font-size.

Font family: font-family: sans-serif

Finally, I made the font-family sans-serif (without the little "tails" and "feet") because it it looks cleaner to me.

Closing thoughts

And that's it! You don't have to agree with all my opinions--everyone should try on their own style!--but hopefully this shows that styling text doesn't have to be elaborate or complicated.

In fact, to me, the best kind of style is nearly invisible. It's the content itself you want the reader focusing on.

Okay, you caught me red-👋. There a few more styles present in this text than I described above. If you're interested in those, you can read about them in part 2 here.


Style is simplicity and transparency

But we don't have to stop there. We just have to be thoughtful about any more styles that are added.

I think a lot about this quote by David Byrne of the Talking Heads. In his book, How Music Works, he describes how,

Simplicity is a kind of transparency in which subtle nuances can have outsize effects. When everything is visible and appears to be dumb, that's when the details take on larger meanings.

I believe this applies to styling web content: you want the styles to be simple, and almost transparent, so the actually important details can stand out.

Styling HTML tags

So let's add a little more style, but do so in a way that enhances the meaning instead of distracting from it.

But how? Since I'm styling with CSS—the primary way to style on the Web—there's a a few ways you can add style to an alement of prose. You can use the HTML tag as a selector—like what I did above with the body and header tags. You can use classes. You can use inline styles. Or you can use a third-party library, like TailwindCSS, that uses semantic classes.

There are applications for all these methods of applying styles, but right now I'm just trying to stay basic web prose. So all I'm going to worry about is styling the various HTML elements—like p, h1, and blockquote—that make up that prose.

Why focus just on HTML tags? Because HTML tags should already carry the semantic meaning of the various elements of prose. The styles are just a way to help convey that semantic meaning already embedded in the document to a human reader.

This is part of the magic of the web, and what sets web prose apart from, say, writing on Microsoft Word or Google Docs or a notes app.

Write once, style forever

To understand the power of HTML tags, I think it's helpful to take a trip back to middle school (at least the middle school I attended circa 2005-2009).

In my day, we'd do our writing assignments in Microsoft Word. Alongside the writing assignment, there was usually a "style guide" which told you how the writing should be presented. These included document-wide requiriements, e.g. 12 point font, double spaced, certain margins, and the first line of each paragraph indented by a certain amount. Certain elements, like blockquotes, also had to have more margin, etc.

Implicit in these style guides were the fact that style informed semantic content, and not the other way around. You knew something was a blockquote because it was indented a certain amount. You knew something was a heading because of it's font.

This wasn't pointless. I'm sure the difficult job of being an English teacher was made a little easier when all papers were formatted the same way, and it taught us some basics in presentation and style.

But on the web, this is thankfully reversed. Elements of text—and other things, like images and video—are first placed into an element tag that corresponds with their semantic meaning. The title of a page is wrapped in a title tag. The main heading is wrapped in an h1 tag. Blockquotes are wrapped in blockquote tag.

Then you can decide how to style those specific elements—like how much bigger headings should be, and how much extra margin blockquotes should have. But the beauty is, you don't have to go into your document and manually change all the blockquotes if you decide they need more margin. You can style them at the tag-level, and can modify those styles for different contexts, different devices, and different users, all without ever having to touch the actual content again.

Letting the user-agent do the work

In fact, once elements are placed in their proper HTML tag, you barely have to do any styling at all. Browsers will take care of a lot of the basic styling for you. This is called the user-agent stylesheet (since it's an agent—also known as a browser—acting on behalf of the user).

You may have noticed that, despite only adding a few lines of CSS and not even mentioning strong (i.e. bold) or a (i.e. link) elements, the bold elements were still bolded, and the links underlined.

These styles were added automatically by your user-agent. My user-agent also makes heading elements bold, makes code elements monospace font, makes em or "emphasized" elements italic, and adds 40px of margin to blockquotes. All without me doing a thing. There's almost no CSS styles for me to worry about. That's simplicity.

Writing for machines and humans

Besides relieving some of the painstaking tedium of styling different elements of text, there's another benefit to wrapping text content in element tags that correspond to their semantic meaning: It makes the document machine-friendly.

That means things like search engines can parse and understand your content automatically. (Then again, with AI training on web content, if you don't want your ideas stolen and repackaged by AI, then maybe you want to make your content un-friendly to machines but that's beyond the scope of this article.)

Take, for example, a screen reader—an electornic assistant that reads the contents of a web page to someone with a visual impairment. Imagine if none of the text content on a page was encapsulated by an element, and you had to infer from its style what it was—like it was a middle schooler's essay.

The screen reader would have to say: "And here's a line that is bolded, centered and 16 point font" and you would have to infer that's a heading. And then it'd say, "Here's a few lines that have extra margin" and you'd infer that was a blockquote. Doesn't sound very easy, right? (Luckily, nothing I wrote back in middle school deserves to see the light of day, so that won't be an issue for anything I wrote).

The only drawback to writing text in HTML elements is that, while it's machine-friendly, it's not always human-friendly to place everything in ugly brackets like <p>. This was the impetus for inventing markdown, a more human-friendly way to write text for the web. Markdown uses simple indicators, like # symbol for headings, or - for lists, or surrounded by _ for italics, that corrsepond directly to HTML elements. Although you still have to use a markdown-parser or a plugin to convert the markdown to HTML.

The elements of style

Before adding more styles, let's first establish some of the different kinds of HTML elements that make-up a text-focused web page. If you want to see *all* HTML elements, you can browse them here.

A non-exhaustive list of HTML tags in typical web prose:

There are of course others but these are the main tags I focus on styling.

Text color

Okay, I have all the elements I want to style. But what styles should I add? Like I said earlier, the browser adds a lot of the basic styles for you, so it's only necessary to think about what you want to do in addition to those.

One of the things the user-agent doesn't set is text-color, since it's largely subjective.

I want to keep things simple, so I want the text to be black on white background. But this doesn't mean the text has to be totally black. It can be easier to read if most of the text softened a bit. Plus, then you can make certain elements stand out out by darkening them relative to the base text. Or you can soften certain elements ever so slightly.

I'll color the text using the HSL color system, which separates out the hue, saturation and lightness of the color so you can toggle each of those separately.

I'll use CSS variables to define each component of the HSL color so I only have to change the variables in one place, and then I'll define the colors by using those variables like this:


* {
  --main-text-hue: 0;
  --main-text-sat: 0%;
  --main-text-lightness: 11%;

  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--main-text-lightness)
    );

}

The "star" (*) selector means these apply all the elements in the document.

For the main text, i.e. paragraph elements, I want it to be slightly softer than total black. 0% lightness means total black, so I'll make the main text 11% lightness. I'll also include list elements in this color category since I consider them part of the main body of text.

So after setting the --main-text-lightness variable, the main text color ruleset looks like this:


p, ul, ol {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--main-text-lightness)
  );
}

Then there are some elements, like headings and bolded text, that I want to be darker than the main body of text. For these I'll set another lightness variable, called --bold-text-lightness, and set it to 3%. Then the ruleset for bold text colors will look like so:


strong, h1, h2, h3, h4, h5, h6 {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--bold-text-lightness)
  );
}

There are some other element I want to be bolder, but not quite that bold. So I'll set another lightness variable, --semibold-text-lightness, for elements like blockquotes and code, and set it to 5%.


blockquote, code {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--semibold-text-lightness)
  );
}

Finally, I want to make a few more text-lightness categories for things like figure captions and horizontal rules, which I want lighter than the rest of the text.

So I'll make a --lighter-text-lightness and --lightest-text-lightness CSS variables, and set those to 20% and 30% lightness respectively so they're lighter than everything else. I'll give figcaption elements the lighter text color, and horizontal rule's the lightest so those rulesets will look like this:


figcaption {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--lighter-text-lightness)
  );
}

hr {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--lightest-text-lightness)
  );
}

That's it for text color. You can barely even notice them—which is good—but these colors ever-so-slightly reinforce the semantic contents of their elements.

Final touches

I'm almost done, I just need to add a few finishing touches.

For one, I'll give pre and code elements a slightly gray background color. This just helps differentiate them from the main text a little bit more.


pre, code {
  background-color: hsl(0,0%,97%);
}

Next, I'll add just a little more margin for horizontal rules so they feel like they're separating sections of content.


hr {
  margin-top: 3rem;
}

Finally, blockquotes are important elements so I want them to stand out a little more than the 40px of left-margin that the user-agent gives them.

So I'll give them 3rem of margin on the top and bottom, and a slightly bigger font-size of 1.75rem.

But blockquotes I think deserve even a little more emphasis than that. So I'll add just a little border on the left by setting the margin-left to 0, adding 2.5rem of padding on the left side, and giving it a 4px solid left border. I'll also make the border a light green just to add a little bit of flare.

There's no other color in the article so this helps say that the blockquote, at least the blockquote I included in this article, is really important. In fact, if my reader remembers anything from this article, I want it to be the quote above from David Byrne. He articulated the point of this article better than I ever could. Everything else is plain and dumb.

Altogether, the ruleset for blockquote element looks like this:


blockquote {
    margin-top: 3rem;
    margin-bottom: 3rem;
    font-size: 1.75rem;
    margin-left: 0px;
    padding-left: 2.5rem;
    border-left: 4px solid rgb(95, 209, 150);
}

And that's it! Those are all the styles I used for this very article. It's not much, but the simplicity allows the few styles I did add to have outsize effect. And hopefully these styles let the reader focus on the content and forget about the style altogether.

Except, of course, the content is about the style, so I hope you paid attention to the style, too.

In any case, here's the full CSS stylesheet used to style this article if you're interested:


/* base styles */
body {
  max-width: 65ch;
  padding: 1rem 1rem;
  margin: auto;
  line-height: 1.65;
  font-size: 1.5rem;
  font-family: sans-serif;
}

h1,h2,h3,h4,h5,h6 {
  margin: 3rem 0 1rem;
}

/* text colors */
* {
  --main-text-hue: 0;
  --main-text-sat: 0%;
  --main-text-lightness: 11%;

  --bold-text-lightness: 3%;
  --semibold-text-lightness: 5%;
  --lighter-text-lightness: 20%;
  --lightest-text-lightness: 30%; 

  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--body-text-lightness));

}

strong, h1, h2, h3, h4, h5, h6 {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--bold-text-lightness));
}

blockquote, code {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--semibold-text-lightness));
}

p, ul, ol {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--main-text-lightness));
}

figcaption {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--lighter-text-lightness));
}

hr {
  color: hsl(
    var(--main-text-hue),
    var(--main-text-sat),
    var(--lightest-text-lightness));
}

/* extra touches */
pre, code {
  background-color: hsl(0,0%,97%);
}

blockquote {
  margin-top: 3rem;
  margin-bottom: 3rem;
  font-size: 1.75rem;
  margin-left: 0px;
  padding-left: 2.5rem;
  border-left: 4px solid rgb(95, 209, 150);
}

hr {
  margin-top: 3rem;
}