Handling headings in a UI component library

Building a highly consumable UI component library is no easy feat and this article will focus on one particular aspect of it: headings (<hx> elements).

This article follows on from my Handling spacing in a UI component library article so rather than repeat the whole “What type of UI component library” explanation, shoot over to that article and have a read, then come back 🙂.

However, what I’m about to cover doesn’t only apply to a UI component library, it works well for any UI build.

The HTML5 document outline

Before I get stuck in I wanted to make it clear that the HTML5 document outline does not apply to any of what I’m about to cover, why? Because it’s not implemented in any browsers or assistive technologies.

So please read on knowing that the less optimal HTML4 document outline is at play here.

What’s the problem?

Styling headings at the base of your stylesheet using element selectors is a fairly common approach, for example:

h1 {
color: #333;
font-size: 1.6em;
font-weight: 900;
line-height: 1.4;
margin-bottom: 1.2em;
}
h2 {
color: #666;
font-size: 1.45em;
font-weight: 800;
margin-bottom: 1em;
}
h3 {
font-size: 1.3em;
font-weight: 400;
margin-bottom: 0.5em;
text-transform: uppercase;
}
// and the rest… h4, h5, h6

When you style headings this way you’re tightly coupling styling with semantics, in computer science this would be like imperative programming.

As soon as you’re in a situation where your next heading has a ranking of 4 (a <h4> element) but it should look like a heading of rank 2 (a <h2> element) then you’re in a bit of a pickle.

This is relatively easy to fix though and there are a few ways you can do it, for example:

HTML:

<!-- Main content -->
<main>
<h1>Some heading</h1>


<h2>Some heading</h2>


<h3>Some heading</h3>

</main>

<!-- Sidebar -->
<div class="sidebar">
<h4>Some heading</h4> ⟸ I need to look like a H2

</div>

CSS:

.sidebar h4 {
… // h2 styles
}

or:

h2,
.sidebar h4 {
… // h2 styles
}

We want to be DRY in our stylesheets so the first solution should be avoided, it’s a smell and we can do better, even on small UI builds. The second solution may be manageable enough when working on UIs that are fairly simple and text heavy, such as:

  • Blogs
  • Publications
  • Documentation

These types of sites typically follow a set heading hierarchy that is consistent across the entire UI, for example:

Examples of UIs (Mark Boulton’s blog, Google’s Web Fundamentals documentation, and the 24 Ways publication) that demonstrate consistent heading hierarchies where heading styles and semantics can be tied together.

However, once you start working on more complex and rich UIs that are made up of many different UI components that can be configured in many different ways, for example:

Examples of complex UIs (Booking.com, Ebay, and YouTube) that demonstrate how hard it is to use heading styles when tied to semantics.

Then trying to use headings that tie styles and semantics together can quickly become inflexible and unmaintainable, in other words, it doesn’t scale.

We could look to Sass mixins to make things easier, for example:

@mixin h2 {
… // h2 styles
}
.sidebar h4 {
@include h2;
}

This can be quite scalable, however, on really large UI builds, we’ll end up with a lot of repeated CSS, however, this isn’t necessarily a bad thing for the following reasons:

  • We’re still following the DRY principle as there is one single source of truth for our heading styles (I highly recommend reading: “When to use @extend; when to use a mixin” from Harry Roberts which covers this really well).
  • Compression, such as gzip, will make short work of all that repeated CSS as that’s what it does extremely well.

Aside from CSS being repeated there is the issue that this technique can only be applied within the CSS meaning in some cases we have to write CSS in order to apply it:

.sidebar h4 {
@include h2;
}
A little aside; I’m excited to move away from CSS preprocessors now that CSS is becoming more and more powerful and browsers are relatively quick to implement new CSS specs. Things like CSS custom properties and CSS Grid are real game changers. In my personal work I no longer use CSS preprocessors and have instead fully embraced native CSS, with a sprinkling of PostCSS.

But really when we’re in the context of a highly consumable UI component library where you have zero control over the authoring of a document’s structure or outline, a more flexible and maintainable approach is needed, back to computer science we need a more declarative approach.

A better approach

We can achieve highly scalable headings by completely divorcing styles from semantics and to achieve this we simply scope all heading styles to classes, for example:

.heading-headline {

}
.heading-title {

}
.heading-subtitle {

}

To extend on our core heading classes we can use BEM’s concept of modifiers, for example:

.heading-subtitle--large {

}
.heading-subtitle--small {

}

However, we should be keeping an eye on this as the amount of headings used in a UI should be kept to a minimum as we want a UI’s typography system to be highly consistent.

A further enhancement is to make all of the base heading elements have the same style as the body copy, for example:

html {
color: #434d5d;
font-family: "Helvetica Neue", sans-serif;
font-size: 1em;
font-weight: 400;
line-height: 1.5;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: inherit;
font-size: inherit;
font-weight: inherit;
line-height: inherit;
margin: 0; // might come from a global reset
}

This ensures most user-agent styles won’t be leaking in and provides us with a nice base to build on top of.

With this approach we now have much greater flexibility, we no longer have to worry ourselves about the aforementioned situation: where your next heading has a ranking of 4 but it should look like a heading of rank 2, because we can simply do this:

<!-- Main content -->
<main>
<h1 class="heading-headline">Some heading</h1>


<h2 class="heading-title">Some heading</h2>


<h3 class="heading-subtitle">Some heading</h3>

</main>
<!-- Sidebar -->
<div class="sidebar">
<h4 class="heading-title">Some heading</h4>

</div>

We’re free to always apply the correct heading rank no matter where the heading is used in the UI. And of course this approach greatly improves the maintainability of our codebase as we’re encapsulating all heading styles to a core set of classes located in the same place.

A heading component

It makes sense to have headings be wrapped up into its own component making it available to be imported into any component that needs a heading, and is also available to be used as a standalone component. Using the React library such a component could look like this:

import React, {PropTypes} from 'react';

const Heading = ({
rank = 2,
text,
type = 'headline'
}) => {
const Tag = rank > 6 ? 'h6' : `h${rank}`;
    return (
<Tag className={`heading-${type}`}>
{text}
</Tag>
);
};
Heading.propTypes = {
rank: PropTypes.oneOf([
1,
2,
3,
4,
5,
6
]),
text: PropTypes.string.isRequired,
type: PropTypes.oneOf([
'headline',
'subtitle',
'title'
])
};

export default Heading;

When used within another component we simply import it, for example:


import Heading from './../Heading';

<header className={classList}>
    <Heading
rank={1}
text={heading}
type="headline"
/>
</header>

All our headings are nicely self contained and super easy to consume.

That’s a wrap

For a long time I coupled heading styles and semantics even on large UI builds always causing headaches. Now having divorced styles and semantics in the UI component library I’ve been working on for a few months now, dealing with headings has been like a nice tropical breeze 🏝.