Functional documentation

Some collected thoughts and ideas around applying a functional approach to writing and maintaining documentation, especially for design systems.


In programming, you have these things called functions. Functions are chunks of code, typically identified by name.

How you write functions varies between programming languages but why you write them does not. Functions define code you can recall and reuse. They stop you repeating yourself.

Any code that isn’t functional is only used in one place, at one time, under one set of conditions. Non-functional code is fleeting.

All programming is somewhat functional but functional programming embraces functions. Functional programmers believe the more functional your code, the better.

I share this view. But I wonder if programming is the only context in which you should write functionally. While programming is divided up into discrete functions, other kinds of writing remain monolithic, measured in pages and documents. We’re not equipped to reuse or remix our content.

The installation documentation function

When it comes to documentation, we sometimes want to say similar things in different places. A design system might contain lots of components, each needing to be installed in a similar way.

Most docs sites take one of two approaches:

  1. Write out the installation instructions for each component (what if the installation process changes? There will be a lot of editing to do...)
  2. Maintain a general component installation guide (requires a context change and some filling in of blanks on the reader’s part)

Instead, we can turn installation guidance into a function. Here is a simplistic example:

function install (name, path) {
  return `
    The ${name} component is installed by running <code>install ${path}</code>.
  `;
}

(If you’re a content editor, you might be wincing at that passive phrasing; we’ll fix that in a minute.)

Similar but different

Functions can take parameters. In the previous example, these are name and path. Parameters let us produce different results in specific and controlled ways. It’s like Mad Libs but (even) less funny.

The name might be “button” or “input” but the sentence structure will be the same.

If you decide to change that structure, the change is propagated to all the components, via the function. Let’s give it a more active voice:

function install (name, path) {
  return `
    Run <code>install ${path}</code> to install the ${name} component.
  `;
}

Chase McCoy talks about transclusion for documentation in their excellent piece Systems as Knowledge Graphs. That is, expanding pieces of content inside other pieces of content, in preference to relying on links. With function parameters you can adapt the transcluded content, tailoring it to the parent document.

Editors aren’t (always) programmers

It’s at this point someone will say, “but you can't expect content editors to program functions.” In practice, the function keyword, return statement, curly braces, and semicolons could all be obfuscated, along with the build process around the function.

Eleventy is a static site generator. In an Eleventy project, a function might be a markdown file, with Nunjucks syntax to do the Mad Libs substitution part. Here’s installation.md:

## Installation

Run `install {{path}}` to install the {{name}} component.

That’s just markdown with placeholders—scarcely even programming.

In component.njk (a Nunjucks template), we can include this installation section using Eleventy’s render plugin:


{% renderFile "./path/to/installation.md", component, 'njk,md' %}

We’re using parameters again. It’s inside the component parameter where name and path would be defined:

{
  name: 'button',
  path: 'path/to/button.js'
}

The further 'njk,md' parameter tells Eleventy to render the markdown using Nunjucks to substitute the {{placeholder}} parts.

A programmer would have to set this part up, I grant you, but it’s in the service of making editing a lot less time consuming.

Am I talking about structured content?

You may have heard of structured content. Structured content is content that is broken down into small pieces, to enable composition and reuse. In data terms, it might mean the difference between one description property and multiple height, width, depth, price, release date, and creator properties that together describe a product.

When these individual properties are defined as functions, taking parameters, they can be used to define more than one instance or category of content. Functional content is dynamic structured content.

Composition

Imagine we have functions for Installation, Contribution, and Related components. These can be combined to build a documentation page for a component. But perhaps we’re not settled on the order of these sections.

Using a simple configuration file, we can allow reordering. Here’s component.yml.

order: 
  - installation
  - related
  - contribution

YAML (.yml) files are a popular configuration syntax because there’s little punctuation to worry about. The list defined here uses a format similar to markdown.

Related components should come before Installation because you want to make sure you have the right component before installing it. Switching these around is trivial:

order: 
  - related
  - installation
  - contribution

In component.njk, we honour the order defined in component.yml when we execute our functions (“execute” is a weird programming term meaning to use).


{% for section in order %}
  {% renderFile `./path/to/{{section}}.md`, component, 'njk,md' %}
{% endfor %}

See how section is now a placeholder! The first time we use renderFile, section equals “related”, the second time it equals “installation”, and the third time it equals “contribute”. We are rendering our documentation sections one after another, in the order prescribed.

This is trivial programming stuff, but I like the idea of applying it to structuring documentation. It makes writing documentation more modular.

Individual component documents

With all these provisions in place, editors (who could be content editors by role, programmers, or anyone else) can focus on what makes that component that component.

The components/button.md file might look like this:

---
name: button
path: path/to/button.js
related:
  - switch
  - link
  - call-to-action
---

Listen, the thing you need to know is buttons and links are _different_ things, with different expected _behaviours_ and [...]

The part between the --- lines is called front matter. Not a programming term, originally, but a publishing one.

In traditional publishing, it includes meta information about the work, starting with the name/title. In less traditional publishing it’s no different. It uses the same syntax as our component.yml file.

The mistake some make is to put any old data in the front matter. Only things that are specific to a single item of content should live there. The rest should be abstracted into functions and configurations for shared use. That’s the principle we’re borrowing from functional programming.

(One of Eleventy’s conventions for sharing common data is assigning directory-specific data files.)

Combining human and machine generated documentation

You may have heard of “self-documenting” code (and the scare quotes here are no mistake). No code is self-documenting because the code itself can only tell you what it does, not what it doesn’t do, or why it exists.

Important parts of documentation need to be written by humans (or, theoretically, by a sufficiently advanced AI—I won’t hold my breath). But other parts can be gleaned directly from the code (including from code comments, which are written by humans anyway).

Tools like custom-elements-manifest read your code and describe it back to you in the JSON data format.

[...]
"members": [
  {
    "kind": "field",
    "name": "disabled"
  },
  {
    "kind": "method",
    "name": "fire"
  }
  ],
  "events": [
  {
    "name": "disabled-changed",
    "type": {
      "text": "Event"
    }
  }
]
[...]

This data can be used to define a documentation function. For example, the documentation function event (describing JavaScript events emitted by the custom element) could live alongside a hand-written introduction to the component’s events, including why and when you should use them. The former is gleaned directly from the code, the latter taken from a functional documentation template.

events
  - preface
  - API

Abstract syntax trees

The format of the manifest file from the last example is that of an AST (abstract syntax tree).

ASTs will be familiar to programmers who have worked on compilers. To compile code, you first have to break it down into its constituent parts. An AST is a tree (object) representation of those parts.

But it’s not just for code compilation that ASTs are useful and it is not just code that can be represented as an AST. Using mdast, a paragraph of markdown can be broken down into its constituent nodes:

{
  type: 'paragraph',
  children: [
    {
      type: 'text', 
      value: 'You may also be interested in the '
    },
    {
      type: 'link', 
      url: '/components/accordion',
      children: [{type: 'text', value: 'Accordion'}]
    },
    {
      type: 'text', 
      value: ' component.'
    }
  ]
}

Earlier, I wrote about front matter—a tool for adding meta information to a piece of content. For example, you can say which components the given component are related to:

---
related:
  - switch
  - link
  - call-to-action
---

This being manual makes it a maintenance burden. Instead, we could use AST representations of our writing (both programmatic and prosaic) to automatically detect related components.

Perhaps we do something simple like look for component names in the hyperlinks of the document (the AST representation could help with that). Or maybe we analyse the component dependencies to find out what uses what.

In any case, the goal is to restrict manual documentation to things only human beings can express.

Dissemination

With our documentation converted into tree/object data, it’s primed for consumption by different platforms and their APIs. As Amy suggests in her article Modular design system documentation you should be able to have design libraries like Figma and Sketch, as well as developer tools like Github or Storybook, all draw from the same documentation source.

Differing presentations

As Amy writes, documentation has variants too.

Because functional content is structured content, you can not only use the same functions to document different things, but create different presentations of the same thing by combining the functions in different ways.

For example, you could create long-form and short-form versions of component documentation.

component: 
  longform:
    - related
    - demo
    - installation
    - usage
    - contribute 
  shortform:
    - installation
    - contribute

You could also maintain different versions of the same function for different presentations.

installation.short.md

`install {{path}}`

installation.long.md

## Installation

1. Make sure you already have the {{installVersion}} version of the **install** tool on your computer.
2. Open your terminal and run `install {{path}}` from the root of the project folder

Contextually, you might set a length variable (short or long) and use this to find the right template for each section. For instance:

{% set length = ('short' if someCondition else 'long') %}
{% for section in order %}
  {% renderFile `./path/to/{{section}}.{{length}}.md`, component, 'njk,md' %}
{% endfor %}

Nothing programmatically complex here; just harnessing agreed naming conventions.

Making a little go a long way

Concepts like relationships, contribution, and installation don’t just apply to components. They’re generic functions of documentation. With care, these can be defined for use across any documentation pages and any documentation projects. That’s where a functional approach to documentation is powerful.

Let’s look again at the order configuration we defined earlier.

order: 
  - related
  - installation
  - contribute

This doesn’t just define the order of the sections but which sections are included in the first place. The contribute part could be taken away, or a demo section could be included.

With a library of generic functions at our disposal, new documentation structures can be created from vocabularies and grammars already agreed. You just have to set out the structure.

In the following example, the component and pattern categories share many of the same functions. Why not reuse those functions across category lines?

component:
  - related
  - demo
  - installation
  - usage
  - contribute 
pattern:
  - related
  - illustration
  - usage
  - contribute

In a world of “how ChatGPT can automatically write your docs / insulate your house / get you into astronaut school” these ideas and proposals are modest and not entirely original. Some of you might be thinking, “I do that already!” That's okay, I’m just a fan.

As with programming, all documentation is somewhat functional. Language itself has functional/reusable parts, like expressions and idioms. But I want to make the case for functional documentation, where the functional part is embraced. And I'm always looking for new ways to do that.

Amy Hupe and I are currently exploring this space and are looking for engagements with organisations interested in taking an ambitious approach to their design system documentation. Together we can offer a lot of strategic and technical expertise.

You can find Amy and I on Mastodon. Alternatively, you can use the emails heydon[at]heydonworks.com or amy.l.hupe[at]gmail.com.