AUTHORING

Markdoc

Write content with Markdoc — variables, conditionals, typed tags, and custom functions evaluated at build time.

Markdoc

Markdoc is a Markdown-based authoring format that adds variables, conditionals, loops, functions, and typed tags — all evaluated at build time. In Docyard, .mdoc files are processed before the normal remark pipeline runs: Markdoc transforms the document into Markdown with MDC component syntax, then citations, cross-references, and math are handled exactly as they are in .md and .qmd files.

File extensions

ExtensionNotes
.mdocRecommended short form; recognized by Markdoc editor plugins.
.markdocExplicit Markdoc extension used in some toolchains.
.markdoc.mdUseful when your editor requires a .md suffix for Markdown features.

Plain .md files are never processed by Markdoc.

Variables

Every frontmatter key is automatically available as a $-prefixed Markdoc variable.

---
title: My Page
version: 2
draft: false
---

# {% $title %}

Version {% $version %} — draft: {% $draft %}

Docyard also injects $docyard.version, populated from the first entry of the DOCYARD_VERSIONS environment variable (an empty string when the variable is not set):

Built with docyard {% $docyard.version %}.

Conditionals

Use {% if %}, {% elsif %}, and {% else %} to control which content appears at build time. The chosen branch is the only content present in the rendered HTML — discarded branches produce no output and no hidden elements.

{% if $draft %}
This page is a draft and will not ship.
{% elsif equals($docyard.version, "") %}
No version information is available.
{% else %}
Published with docyard {% $docyard.version %}.
{% /if %}
Build-time evaluation

Conditionals are resolved when the site is built. A branch whose condition evaluates to false is completely absent from the rendered HTML — it does not rely on JavaScript or hidden elements.

Loops

Iterate over an array variable with {% for %}:

{% for item in $items %}
- {% $item.name %}
{% /for %}

The loop variable is scoped to the loop body. $items must be declared in markdoc.config.ts under variables.

Built-in functions

Docyard ships a standard set of functions usable in tag attributes, conditionals, and inline expressions (Markdoc itself has no built-in function library — these are registered by Docyard's Markdoc config).

FunctionDescription
upper(str)Convert a string to uppercase
lower(str)Convert a string to lowercase
trim(str)Strip leading and trailing whitespace
contains(arr, val)True if an array or string contains val
equals(a, b)Strict equality check
default(val, fallback)Return val unless it is undefined
and(a, b)Logical AND
or(a, b)Logical OR
not(val)Logical NOT
concat(a, b)Concatenate two strings
plus(a, b)Add two numbers
minus(a, b)Subtract two numbers
multiply(a, b)Multiply two numbers
divide(a, b)Divide two numbers
mod(a, b)Remainder after division
length(val)Length of a string or array
Uppercase title: {% upper($title) %}
Description length: {% length($description) %} characters
Fallback value: {% default($subtitle, "Untitled") %}

Built-in tags

callout

Renders a DocyardCallout admonition block.

AttributeTypeDefaultValues
typestringnotenote, tip, warning, caution, important
titlestringCustom heading text
collapsestringAny non-empty value makes the callout collapsible
{% callout type="tip" title="Quick tip" %}
Content goes here. You can use **Markdown** inside a callout.
{% /callout %}

Rendered output for each type:

Note

A note callout — for supplementary information.

Quick tip

A tip callout with a custom title.

Check this first

A warning callout.

Caution

A caution callout that starts collapsed. Any non-empty value for collapse enables this behavior.

Important

An important callout.

figure

Renders a DocyardFigure component. This tag is self-closing — use /%} to close it.

AttributeTypeRequiredDescription
srcstringYesImage URL or path
altstringNoAlternative text for accessibility
captionstringNoCaption displayed below the image
idstringNoCross-reference id (e.g. fig-my-image)
{% figure src="/image.svg" alt="Placeholder" caption="A sample figure." id="fig-sample" /%}

See @fig-sample for the rendered output.
Placeholder image
Figure 1: Placeholder image

See Figure 1 for the rendered output above.

table

Renders a DocyardTable component wrapping a Markdown pipe table. Write the pipe table directly inside the tag body.

AttributeTypeRequiredDescription
captionstringNoCaption displayed with the table
idstringNoCross-reference id (e.g. tbl-my-table)
{% table caption="Browser support matrix." id="tbl-browsers" %}
| Browser | ES2022 |
| ------- | ------ |
| Chrome  | Yes    |
| Firefox | Yes    |
{% /table %}

See @tbl-browsers for the matrix.
Table 1: A simple two-column table.
Column AColumn B
Value 1Value 2
Value 3Value 4

See Table 1 for the rendered output above.

steps / step

Renders a DocyardSteps container with DocyardStep children, used for numbered procedural instructions.

{% step %} attributes:

AttributeTypeRequiredDescription
titlestringNoStep heading
{% steps %}
{% step title="Install" %}
Add the dependency to your project.
{% /step %}
{% step title="Configure" %}
Edit your configuration file.
{% /step %}
{% /steps %}

Add the dependency

Install @markdoc/markdoc in your site's package.json.

Write a .mdoc file

Create a file with a .mdoc extension in your content directory and use Markdoc syntax.

Build

Run the normal docyard build. Markdoc files are processed automatically before the remark pipeline.

equation

Renders a DocyardEquation component from a LaTeX source string. This tag is self-closing — use /%} to close it.

AttributeTypeRequiredDescription
srcstringYesLaTeX expression for the display equation
idstringNoCross-reference id (e.g. eq-my-equation)
{% equation src="E = mc^2" id="eq-einstein" /%}

See @eq-einstein for mass-energy equivalence.
(1)

See Equation 1 for the rendered output above.

Crossrefs and citations

Cross-references (@fig-, @tbl-, @eq-) and citations (@key, [@a; @b]) are handled by Docyard's remark pipeline, which runs after the Markdoc transform. Use them in any .mdoc file exactly as you would in .md or .qmd — Markdoc passes @-references through as plain text and remark resolves them normally.

{% figure src="/diagram.svg" caption="Architecture overview." id="fig-arch" /%}

As shown in @fig-arch, the pipeline is straightforward.

User config

Create markdoc.config.ts in your site's root directory to add custom tags, site-wide variables, and custom functions. The file is loaded automatically at build time. User-defined entries take precedence over built-in Docyard tags of the same name.

import type { MarkdocUserConfig } from "@docyard/theme/remark/markdoc";

export default {
  tags: {
    badge: {
      render: "Badge",
      selfClosing: true,
      attributes: {
        label: { type: String, required: true },
        color: { type: String, default: "blue" },
      },
    },
  },
  variables: {
    site: {
      name: "My Docs",
      repo: "https://github.com/example/my-docs",
    },
  },
  functions: {
    acronym: {
      parameters: { value: { type: String } },
      transform(params) {
        return String(params["value"] ?? "")
          .split(/\s+/)
          .map((w) => w[0])
          .join("")
          .toUpperCase();
      },
    },
  },
} satisfies MarkdocUserConfig;

Custom tags with a render name (e.g. Badge) must be registered as global Vue components in your site app. Docyard does not auto-register user tag renderers.

Use the custom tag in any .mdoc file:

{% badge label="New" color="green" /%}