‹ All posts

Ballad: Doctave's Markdown-aware component system

Doctave 2.0 is out, and the biggest new feature in this release was our new component system, which we’re calling Ballad. In this post we want to talk about the path we took to get here, why we felt none of the existing solutions worked well for us, and what our component system enabled.

A Ballad tab component for showing code samples on different platforms

Ballad brings interactive components like tabs to your Doctave docs

Why do we need components?

Ballad lets you intersperse UI components in between your Markdown content. While Markdown is fantastic for simple prose, most documentation projects inevitably need widgets like buttons, cards, and callouts (or admonitions), or more complex layouts to keep the content clear and engaging.

This is what we’re solving with Ballad.

How it works

In short, you can add HTML-like tags into your Markdown, which Doctave will render into various components.

Let’s look at a basic example: adding a Button component into your documentation:

Click below to read more.

<Button href="/details">Read More</Button>

This gets rendered like so:

A Ballad button component

Sometimes a regular Markdown link just doesn't cut it


Our components come in two flavors: UI components and layout components.

The former are components like <Button> or <Card>: visual elements that you’re adding to the page. Layout components instead (unsurprisingly) change the layout of your content. These are components like <Grid>, or <Flex> (a flexbox equivalent).

Combining these components lets you build interesting UIs and structure your content however you want on the page, without having to write any custom CSS.


You can use an MDX-inspired syntax to add components into your documentation:

## Getting started

Learn how to get started with our SDK.

<Grid cols="2">

In fact, the parser is an MDX parser. The component syntax rules are identical.

These HTML-style components work with your Markdown content, and you can nest and combine components and Markdown arbitrarily.


Beyond just static components, ballad also has its own expression language. This is needed because Doctave supports user preferences, which allows different readers to see different content based on, for example, what plan they’ve chosen.

If you for example would like to show a warning on a feature page when the user is not on an enterprise plan, you can do this:

// Only show this callout if the user is not on the enterprise plan
<Callout type="warning" if={@user_preferences.plan != "enterprise"}>
  This feature is only available on the **enterprise** plan

You can also show the currently selected user preference in your content:

You are currently on the {@user_preferences.plan | capitalize} plan.

Custom components

Not only does Ballad come with its own component library, you can build your own!

Let’s say I wanted to build a custom callout (admonition) component, because I want something more specialized. I can create a reusable component in my project under _components/callout.md:

  - title: type
    required: false
    default: "info"
        - "info"
        - "success"
        - "warning"
        - "error"

<Box class="custom-callout-class" data-type={@type} pad="2">
  <Slot />

This component can then be called with the following code anywhere in your project:

<Component.Callout type="warning">
  Remember to back up your data before proceeding

Let’s see what’s going on in this file:

  • We’ve created a custom component in the _components directory
  • We say that it takes a type attribute, and add some validation
  • We use a Box layout component and set some padding, and add a custom CSS class for targeting styling
  • Finally, we use the <Slot /> component which allows us to inject Markdown into the custom component when it is used

The result

Ballad lets you combine components fairly complex layouts with very little code, and without any custom CSS:

A grid of card components created with Ballad

No custom CSS needed

This is not a pre-built, hardcoded component in Doctave. It’s built by combining multiple components and regular Markdown into a complex layout.

This is really powerful, and we’re still discovering new ways to combine the components we’ve built!

Why not choose an existing technology?

Choosing an existing technology would have been preferable, but after looking around, we did not see a viable existing alternative that we could incorporate into Doctave.

The main blockers we ran into were:

  • Security issues
  • Usage complexity


Two options we looked at carefully were MDX and Markdoc. The former is a way to include arbitrary React components in Markdown documents, while the latter is a Markdown-aware templating/authoring system. Both are written in JavaScript and expect users to write JavaScript to define components.

We unfortunately had to dismiss both options because of security concerns.

Doctave builds and hosts your documentation for you, which means we would have to execute the JavaScript required to render the user’s MDX/Markdoc components. This opens a huge can of worms from a security perspective. Ask any security expert, and they will say executing arbitrary user submitted code is a security nightmare.

Here is a thread where one of MDX’s creators responds to a question asking if it’s ok to let their users write MDX. His response is pretty straightforward: “no, it’s not safe, it’s dangerous”.

Ballad on the other hand is fully isolated, has a limited API, does not support looping or unbounded recursion, and does not require a full JavaScript runtime to execute. It’s smaller, faster, and safer.

Usage complexity

Another issue we wanted to prevent was making your templates too complex. Content reuse is a known double edged sword by tech writers, and sometimes it can be hard to understand where some content is coming from.

This problem is amplified when you can put too much business login into your templates.

Markdoc has an excellent section in their FAQ under “why not MDX?” where they discuss why you should keep your templates simple:

Content can quickly become as complex as regular code, which can lead to maintainability complications or a more difficult authoring environment.

Stripe famously used Ruby’s ERB templates prior to Markdoc for their documentation, and Markdoc was created at least partly to address the complexity caused by having a full programming language available in your template engine.

We did not want to repeat the same mistakes, and have kept Ballad’s API deliberately limited. It is always possible to add features later, but removing capabilities is impossible once users are relying on them.

What Ballad enabled for us

We naturally eat our own dog food, and use Doctave for our own documentation.

Here are some things we realised once we started using Ballad:

  • Landing pages become easy
    Previously we spent a lot of time using custom CSS to make compelling landing pages. Now, we just use components to create call-to-actions and guide users to important articles.

  • More compelling tutorials
    Components Steps and CodeSelect to make it easy to create interesting multi-language tutorials and walkthroughs.

  • Quick one-off custom components are easy
    You can create small components inline in your articles by combining a few components. A clickable card with some text and an icon can be done in a couple lines.

But the overall feeling was a feeling of empowerment. These components add a very important and powerful tool into our docs toolbelt. And as we said, we’re still learning new ways to use Ballad and our component library!

What’s next?

You can expect a few things going forward:

  • More UI and layout components
  • Tutorials on how to effectively combine components
  • When we release more themes, all components will conform to the new theme

If you’re interested in staying up to date, sign up to the newsletter below!

Articles about documentation, technical writing, and Doctave into your inbox every month.