DEV Community

Cover image for Typography: Separating Style from Semantics
Devin Witherspoon
Devin Witherspoon

Posted on

Typography: Separating Style from Semantics

Getting started with accessibility can be intimidating, especially when your project has complicated user flows and interactions. A great entry point into accessibility is fixing up your heading hierarchy. Fixing this simple issue can make navigating the site much easier for your users.

This article focuses on improving webpage accessibility in a small but meaningful way. For more information on headings and accessibility, consult the W3 Headings Accessibility Tutorial.

The intent of this Success Criterion is to ensure that information and relationships that are implied by visual or auditory formatting are preserved when the presentation format changes.

- WCAG Success Criterion 1.3.1: Info and Relationships

Heading Hierarchy

Correct heading hierarchy helps assistive technology like screen readers interpret your pages. Consistently applying styles with a 1:1 mapping between heading elements and size is the ideal scenario. On complex websites, though, design constraints often prevent us from consistently styling headings, so we're left with three options.

Styles > Hierarchy

If we prioritize styles, we conform to design constraints but lose the heading hierarchy. Even worse, this results in misleading headings that explicitly miscommunicate the structure to screen readers and other assistive technology.

This use of headings produces a document structure that implies the first h3 (Life) isn't a parent heading of the subsequent h2 despite Kingdom being a grouping within Life - this isn't the intended outcome.

This might not be a big deal for seeing users, but we want everyone to be able to fully navigate and experience the products we build, so we should avoid this practice.

Fight Work with Design to Make Styles = Hierarchy

This option isn't always available, but you should apply it wherever possible. While not universal, text size indicates a visual hierarchy within the document, and making an h3 larger than an h2 can be misleading to the user.

If your designer isn't familiar with accessibility standards, work with them to better understand heading structure and WCAG guidelines. Engineers can't own accessibility alone, and neither can designers - it's a shared responsibility for everyone to invest time and effort into. If everyone does their part, the product will be better for all users.

If you reach full alignment between engineers and designers, you end up with a heading structure that matches both visually and semantically.

Styling Elements Independent from Semantics

For the times we can't reach 100% alignment between engineering and design, we should still work to reach a helpful information hierarchy in our HTML. By separating appearance from the element tag, we can achieve the desired style while maintaining the correct heading hierarchy.

So how do we separate appearance from the element tag? The easiest solution is to use global classes that match the intended element:

/* classes match the intended element */
h1,
h2,
h3,
h4,
h5,
p,
.h1,
.h2,
.h3,
.h4,
.h5,
.body {
  margin: 0 0 0.5em 0;
  font-family: "Poppins", sans-serif;
}

h1,
.h1 {
  font-size: 3rem;
  font-weight: bold;
}

h2,
.h2 {
  font-size: 2.5rem;
  font-weight: bold;
}

h3,
.h3 {
  font-size: 1.75rem;
  font-weight: bold;
}

h4,
.h4 {
  font-size: 1.25rem;
  font-weight: 600;
}

h5,
.h5 {
  font-size: 1rem;
  font-weight: 600;
}

p,
.body {
  font-size: 1rem;
  font-weight: normal;
}
Enter fullscreen mode Exit fullscreen mode

Note: the styles here aren't specific recommendations, rather an example of what can be done.

Existing Implementations

While I've walked through a manual implementation with raw HTML and CSS, you may want a more robust solution. You could build one from scratch without much difficulty, but there are also existing solutions in component libraries:

Conclusion

Hopefully this tutorial helped you learn a bit more about accessibility in web development and how to incorporate it into your regular development practices. If you've come up with your own solution for this topic, please share it below.

Appendix: Automated Heading Tags

Still here? Okay, let's go a little further. What if I use a component in two separate parts of the app - one where the parent heading is an h1 and another where the parent heading is an h2 - what should be the child heading tag in this component?

I might argue that in the first scenario it should be an h2 and in the second it should be an h3. Should we do a custom heading prop? 🤢 Let's try solving it with React context instead.

// Heading.jsx
import React, { createContext, useContext } from "react";

const headingLevelContext = createContext(0);

function useHeadingLevelContext() {
  return useContext(headingLevelContext);
}

export function Heading({ size, as, children }) {
  const defaultComponent = `h${useHeadingLevelContext()}`;
  const HeadingComponent = as || defaultComponent;

  return (
    <HeadingComponent className={size || HeadingComponent}>
      {children}
    </HeadingComponent>
  );
}

export function HeadingBlock({ level, children }) {
  const defaultLevel = useHeadingLevelContext();

  return (
    <headingLevelContext.Provider value={(level || defaultLevel) + 1}>
      {children}
    </headingLevelContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

HeadingBlock keeps track of the heading level by incrementing the heading level context when nested inside another HeadingBlock and increasing the heading tag used by Heading. Let's look at it in action:

By capturing the heading level in the context, we can provide the size for styling, while the context determines the heading element used.

Abstracting heading semantics away from developers may also have downsides, and I haven't tested it in a production application or accessibility audit, but it's an interesting idea to explore further.

Top comments (0)