This post covers how I built a typography grid for a design system using Vue render functions. Here’s the demo and the code. I used render functions because they allow you to create HTML with a greater level of control than regular Vue templates, yet surprisingly I couldn’t find very much when I web searched around for real-life, non-tutorial applications of them. I’m hoping this post will fill that void and provide a helpful and practical use case on using Vue render functions.
I’ve always found render functions to be a little out-of-character for Vue. While the rest of the framework emphasizes simplicity and separation of concerns, render functions are a strange and often difficult-to-read mix of HTML and JavaScript.
For example, to display:

Some cool text

…you need:
render(createElement) {
return createElement(div, { class: container }, [
createElement(p, { class: my-awesome-class }, Some cool text)
])
}
I suspect that this syntax turns some people off, since ease-of-use is a key reason to reach for Vue in the first place. This is a shame because render functions and functional components are capable of some pretty cool, powerful stuff. In the spirit of demonstrating their value, here’s how they solved an actual business problem for me.
Quick disclaimer: It will be super helpful to have the demo open in another tab to reference throughout this post.
Defining criteria for a design system
My team wanted to include a page in our VuePress-powered design system showcasing different typography options. This is part of a mockup that I got from our designer.
AA
And here’s a sample of some of the corresponding CSS:
h1, h2, h3, h4, h5, h6 {
font-family: balboa, sans-serif;
font-weight: 300;
margin: 0;
}
h4 {
font-size: calc(1rem – 2px);
}
.body-text {
font-family: proxima-nova, sans-serif;
}
.body-text–lg {
font-size: calc(1rem + 4px);
}
.body-text–md {
font-size: 1rem;
}
.body-text–bold {
font-weight: 700;
}
.body-text–semibold {
font-weight: 600;
}
Headings are targeted with tag names. Other items use class names, and there are separate classes for weight and size.
Before writing any code, I created some ground rules:
Since this is really a data visualization, the data should be stored in a separate file.
Headings should use semantic heading tags (e.g.

,

, etc.) instead of having to rely on a class.
Body content should use paragraph (

) tags with the class name (e.g.

).
Content types that have variations should be grouped together by wrapping them in the root paragraph tag, or corresponding root element, without a styling class. Children should be wrapped with and the class name.

Thing 1
Thing 2

Any content that’s not demonstrating special styling should use a paragraph tag with the correct class name and for any child nodes.

Thing 1
Thing 2

Class names should only need to be written once for each cell that’s demonstrating styling.
Why render functions make sense
I considered a few options before starting:
Hardcoding
I like hardcoding when appropriate, but writing my HTML by hand would have meant typing out different combinations of the markup, which seemed unpleasant and repetitive. It also meant that data couldn’t be kept in a separate file, so I ruled out this approach.
Here’s what I mean:

Heading 1

h1

Balboa Light, 30px

Product title (once on a page)
Illustration headline

Using a traditional Vue template
This would normally be the go-to option. However, consider the following:
See the Pen
Different Styles Example by Salomone Baquis (@soluhmin)
on CodePen.
In the first column, we have:
– An

> tag rendered as-is.
– A

tag that groups some children with text, each with a class (but no special class on the

tag).
– A

tag with a class and no children.
The result would have meant many instances of v-if and v-if-else, which I knew would get confusing fast. I also disliked all of that conditional logic inside the markup.
Because of these reasons, I chose render functions. Render functions use JavaScript to conditionally create child nodes based on all of the criteria that’s been defined, which seemed perfect for this situation.
Data model
As I mentioned earlier, I’d like to keep typography data in a separate JSON file so I can easily make changes later without touching markup. Here’s the raw data.
Each object in the file represents a different row.
{
text: Heading 1,
element: h1, // Root wrapping element.
properties: Balboa Light, 30px, // Third column text.
usage: [Product title (once on a page), Illustration headline] // Fourth column text. Each item is a child node.
}
The object above renders the following HTML:

Heading 1

h1

Balboa Light, 30px

Product title (once on a page)
Illustration headline

Let’s look at a more involved example. Arrays represent groups of children. A classes object can store classes. The base property contains classes that are common to every node in the cell grouping. Each class in variants is applied to a different item in the grouping.
{
text: Body Text – Large,
element: p,
classes: {
base: body-text body-text–lg, // Applied to every child node
variants: [body-text–bold, body-text–regular] // Looped through, one class applied to each example. Each item in the array is its own node.
},
properties: Proxima Nova Bold and Regular, 20px,
usage: [Large button title, Form label, Large modal text]
}
Here’s how that renders:

Body Text – Large
Body Text – Large

body-text body-text–lg body-text–bold
body-text body-text–lg body-text–regular

Proxima Nova Bold and Regular, 20px

Large button title
Form label
Large modal text

The basic setup
We have a parent component, TypographyTable.vue, which contains the markup for the wrapper table element, and a child component, TypographyRow.vue, which creates a row and contains our render function.
I loop through the row component, passing the row data as props.


One neat thing to point out: the typography data can be a property on the Vue instance and be accessed using $options.typographyData since it doesn’t change and doesn’t need to be reactive. (Hat tip to Anton Kosykh.)
Making a functional component
The TypographyRow component that passes data is a functional component. Functional components are stateless and instanceless, which means that they have no this and don’t have access to any Vue lifecycle methods.
The empty starting component looks like this:
// No