Building Vue.js UI Components With HTML Semantics in Mind

  You block advertising 😢
Would you like to buy me a ☕️ instead?

When building modern, component-based client-side applications, we often tend to forget about the foundations of web development: HTML and CSS. Sometimes we act as if the rules of writing semantic HTML are somehow no longer relevant. But the opposite is true. More and more people use web applications every day, and the reasons why we should strive to write semantic and accessible HTML are at least as valid today as they were in the past.

Inspired by this Full Stack Radio podcast episode in which Adam Wathan talks to Aaron Gustafson about writing semantic HTML, I thought about how we could make it a little easier to use semantic HTML with modern web frameworks. In this article, we explore how we can build our Vue.js components in a way that makes it easier to create applications that render semantic HTML.

The problem

Modern single-page web applications are based on reusable, generic components. But often, when we create these generic, low-level building blocks of our applications, we don’t know where and how these components will later be used. This means that in most cases, we fall back to elements without semantic meaning (most notably divs and spans).

Examples

Let’s take a look at a few examples of how we can build components that can be easily adapted so that they render the appropriate HTML tag.

Semantic Media Object component

The Media Object is a rather simple yet powerful pattern consisting of three parts: a wrapper, a body and a figure element.

<template>
  <Component
    :is="tag"
    class="UiMedia"
  >
    <slot/>
  </Component>
</template>

<script>
// src/components/UiMedia.vue
export default {
  name: 'UiMedia',
  props: {
    // Pass the name of the HTML
    // element you want to render.
    tag: {
      default: 'div',
      type: String
    }
  }
};
</script>

<style>
.UiMedia {
  display: flex;
}
</style>

Above, you can see the wrapper component of our Media Object implementation. The component (and all our other UI components) declare a tag property that allows us to change which HTML element is rendered. If you want to see all parts of our Vue.js Media Object implementation, you can check out the CodeSandbox for this article.

<template>
  <UiMedia tag="figure">
    <UiMediaFigure
      tag="img"
      src="https://via.placeholder.com/100x100"
      alt="Gray rectangle with text 100x100."
    />
    <UiMediaBody tag="figcaption">
      A gray rectangle
    </UiMediaBody>
  </UiMedia>
</template>

Here you can see how the UiMedia component can be used to render semantic <figure> and <figcaption> elements to describe an <img> element.


Do you want to learn more about advanced Vue.js techniques?

Register for the Newsletter of my upcoming book: Advanced Vue.js Application Architecture.


Semantic headline component

In many cases, the visual hierarchy of the headlines of an application does not correspond to their semantic hierarchy. In such cases, it can be handy to have a headline component that allows you to set the visual size independently of the rendered tag.

Furthermore, I’m pretty sure you know the feeling when you get handed over a new design with some huge text (h1 or h2 style) that simply isn’t a headline in the semantic meaning. In such situations, it can be useful to overwrite the tag which is used to render our headline component.

<template>
  <Component
    :is="tag || `h${level}`"
    :class="`UiHeadline UiHeadline--${size || level}`"
  >
    <slot/>
  </Component>
</template>

<script>
export default {
  name: 'UiHeadline',
  props: {
    level: {
      default: '1',
      type: String
    },
    size: {
      default: null,
      type: String
    },
    // Pass the name of the HTML
    // element you want to render.
    tag: {
      default: null,
      type: String
    }
  }
};
</script>

<style>
.UiHeadline {
  font-size: 1.25em;
  font-weight: bold;
}

.UiHeadline--1 {
  font-size: 2em;
}

.UiHeadline--2 {
  font-size: 1.75em;
}

.UiHeadline--3 {
  font-size: 1.5em;
}
</style>

Three properties can control this UiHeadline component. The level property controls which tag (h1-h6) is rendered. Changing the size property affects the CSS class applied to the heading, thus changing the visual size of the headline. Last but not least, you can choose to render a completely different HTML element by changing the tag property.

<template>
  <UiHeadline level="3" size="2">
    h3 headline looking like h2
  </UiHeadline>
  <UiHeadline level="2" size="3">
    h2 headline looking like h3
  </UiHeadline>
  <UiHeadline tag="p" size="1">
    Fake headline looking like h1
  </UiHeadline>
</template>

Here you can see how to use the UiHeadline component. Additionally you can take a look at the following CodeSandbox to see all of the examples live.

Wrapping it up

Modern component based web applications don’t have to be a completely inaccessible mess. There is no good reason why we should not reach for semantic HTML elements when building our applications.

Note that this only scratches the surface. Writing semantic HTML is merely the basis for creating truly accessible web applications.

Resources


Do you want to learn how to build advanced Vue.js applications?

Register for the Newsletter of my upcoming book: Advanced Vue.js Application Architecture.



Do you enjoy reading my blog?

You can buy me a ☕️ on Ko-fi!

☕️ Support Me on Ko-fi