O’Reilly Report: Decoupled Applications and Composable Web Architectures - Download Now

Empower your teams & get a 582% ROI: See Storyblok's CMS in action

Skip to main content

Optimizing Headline Hierarchies for Improved Web Accessibility with Storyblok

Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.

  • Home
  • Tutorials
  • Optimizing Headline Hierarchies for Improved Web Accessibility with Storyblok

Using headlines, so the <h> elements, in a meaningful way is part of writing semantic code. Generally speaking, there should only be one <h1> per page, ranks should not be skipped, and the respective h tags should be used for structuring. The <h> tag's value conveys the content's relevance to a screen reader. People who use assistive tech might use the headlines to quickly browse an article to find what they are looking for. 

TLDR:

Would you like to dive right in? Here is a link to the Github Repo with the examples we will discuss in this article.


VoiceOver Rotor showing an overview of different headline levels

VoiceOver Rotor showing headline hierarchies in an example project

Similarly, Google’s crawlers will fall back on the headlines to check whether content is relevant for a search request - making headline hierarchies crucial for search engine optimization. 

Headline Hierarchies in a Headless CMS

A headless CMS like Storyblok gives you a lot of flexibility - both as a developer as well as a marketeer or editor. This is a great benefit of the system when we use it wisely. All the flexibility aside, you want to ensure the headline structure remains. Regardless of what bloks editors might use in a specific content type - you want to make sure this does not get in the way of semantically conveying the importance of different pieces of content. Let’s have a look at our different options.

We will have a look at: 

  • Manually determined headline hierarchies
    • Headline text field
    • Formatting headlines in rich text fields or markdown
  • Automatically determined headline hierarchy

Using Markdown or Richtext for headline structures

In the rich text field type in Storyblok, you have many different options to style your text - one of them being adding markdown, another choosing between headlines from <h1> to <h6>. 

This is the least recommended option from an accessibility perspective - even though it might be feasible under specific circumstances. Let’s explore a little bit, why: The freedom to use markdown or the headline options in the rich text field brings the risk of changing the markup in ways that negatively impact web accessibility. Since you want to make sure you stick to a meaningful landmark structure and headline hierarchy, it's only possible to keep a sufficient overview in a very small team or on a project with very little content. 

Headline Hierarchies in a RichText field in the Visual Editor
1

Headline Hierarchies in a richtext field in the Storyblok Visual Editor

For most teams, especially interdisciplinary, larger teams, it would be better to disable writing markdown altogether and strictly limit where headlines can be added through rich text fields. This way, you as a developer can ensure the website follows best practices and is optimized for all users and headline hierarchies follow the recommended guidelines. 

Even with proper training, a small risk remains that headline structures are not maintained in the way they were intended. In Storyblok, you can leave a description below the field to highlight the importance of headline hierarchies, but human error can never fully be avoided.  

Adding the description to the Richtext field in the Blocklibrary.
1

Adding a description can provide context for editors. 

Richtext field in the Visual Editor with a Description below, notifying editors to add meaningful headline hierachies.
1

Description displayed below richtext field. 

Headline Text Fields

You could also approach the headline hierarchies from atomic design perspective: by adding an additional field to the bloks using headlines. You can use the “single option” field type and refer to an internal data source where you can define your headline levels.

Adding a single-option field to the Teaser component in the Blocklibrary
1
2

Teaser component with single-option field type.

Adding a source and default value to the single-option field.
1
2
3

In the field type settings, you can define a default value. 

Data source set of headline tags except h1.
1

Headline levels as data sources

In SvelteKit, we can set the tag dynamically by using the <svelte:element>. To do so, we are defining headline_level as a single-option field in our Teaser component, referencing the different headline levels as a data source (skipping H1, as we want to make sure there is only one H1 element per page) and then setting it dynamically through the this prop. 

Teaser.svelte
        
      <script>
	import { storyblokEditable } from '@storyblok/svelte';

	export let blok;
</script>

<svelte:element
	this={blok.headline_level}
	use:storyblokEditable={blok}
	class="py-32 text-6xl text-[#50b0ae] font-bold text-center"
>
	{blok.headline}
</svelte:element>
    

This way, the headline rank will be controlled manually in Storyblok. Without pre-defined headline levels getting in the way, we can determine for each page what hierarchy a certain headline should have. 

Automatically determined headline hierarchy

The option that would most securely ensure a semantically correct headline hierarchy would be to implement the logic automatically in the frontend, moving the control away from Storyblok, if you will. This way, you can make sure there is only one <h1> per page, followed by <h2> headings for sub-sections and so on. 

This could be done by checking in the code whether the headline you are rendering is the first headline on the page - if it is, make sure it’s an <h1> , else, make it an <h2>. So first, we create our little helper function to check whether the element is the first one rendered on the page: 

getHeadlineTag.js
        
      export function getHeadlineTag(index) {
	return index === 0 ? 'h1' : 'h2';
}
    

In the Page.svelte, we need to pass along the index in the <StoryblokComponent>

Page.svelte
        
      <script>
	import { storyblokEditable, StoryblokComponent } from '@storyblok/svelte';

	export let blok;
</script>

{#key blok}
	<div use:storyblokEditable={blok} class="px-6">
		{#each blok.body as blok, index}
			<StoryblokComponent {blok} {index} />
		{/each}
	</div>
{/key}
    

Then, in the individual components, we can import our helper function, pass in the index and by doing so, set the tag dynamically: 

Teaser.svelte
        
      <script>
	import { storyblokEditable } from '@storyblok/svelte';
	import { getHeadlineTag } from '../utils/getHeadlineTag';

	export let blok;
	export let index;

	const tag = getHeadlineTag(index);
</script>

<svelte:element
	this={tag}
	use:storyblokEditable={blok}
	class="py-32 text-6xl text-[#50b0ae] font-bold text-center"
>
	{blok.headline}</svelte:element
>
    

This automated approach gives you as a developer the most control over which headline level is set where. Of course, determining between <h1> and <h2> is only the tip of the iceberg, if you will. In a larger scale project, this set-up would have to be a little more complex, for example, by restricting what component can be used where and then for those components, which headlines can be used. 

Conclusion

As you see, there are multiple solutions as to how you structure your headlines in a headless CMS - it’s a careful balance between the editors’ freedom to build their own pages and restrictions to avoid accessibility challenges and other pitfalls. As always, the best approach depends on your requirements but is definitely worth exploring. 

Author

Josefine Schaefer

Josefine Schaefer

Josefine is a frontend engineer with a passion for JavaScript, web accessibility and community-building. Before entering the tech industry, she worked as a communications specialist - nowadays, she combines these skills with her curiosity for technology and works as a Developer Relations Engineer at Storyblok.