🪵 bbmd

Basic usage

This page will walk you through the basics of creating composable markdown blocks, expressing conditions, and keeping types simple. For complete documentation on bbmd's API, refer to Creating blocks.

Creating a block

In Bbmd, everything in Markdown is a block, including entire documents. You can create blocks using template literals, functional composition, or by parsing existing markdown strings.

import b from "bbmd"

const userName = "John"
const userAge = 30

const userDetailsDoc = b.doc(
  b.h`User details`.id("user-details"),
  b.p(b.b(userName), b.fn(`Age: ${userAge}`)),
);
import b from "bbmd"

const userName = "John"
const userAge = 30

const userDetailsDoc = b.md`
  # User details {#user-details}
  **${userName}**[^1]

  [^1]: Age: ${userAge}
`.parse();
import b from "bbmd"

const userName = "John"
const userAge = 30

const userDetailsDoc = b.md`
  ${b.h`User details`.id("user-details")}
  ${b.b(userName)}${b.fn(`Age: ${userAge}`)}
`;
# User details {#user-details}
**John**[^1]

[^1]: Age: 30

Composing blocks

Because blocks are composable, you can easily reuse them across documents and contexts. Simply create a block and then include it in other blocks as needed.

const promptDoc = b.doc(
  b.h`Greeting prompt`,
  userDetailsDoc, // 👈 here's our block from earlier
  b.p`Use the above details to generate a personalized greeting message.`
).setRenderingOptions({
  enforce: { bold: { style: "__" } },
  newlineStrategy: "between_blocks",
});
# Greeting prompt

## User details {#user-details}

__John__[^1]

Use the above details to generate a personalized greeting message.

[^1]: Age: 30
MarkdownDocument
├── MarkdownHeadingBlock [level=1]
│   └── "Greeting prompt"
├── MarkdownDocument
│   ├── MarkdownHeadingBlock [identifier=user-details, level=2]
│   │   └── "User details"
│   └── MarkdownParagraphBlock
│       ├── MarkdownBoldBlock
│       │   └── "John"
│       └── MarkdownFootnoteBlock [identifier=1]
│           └── footer
│               └── "Age: 30"
└── MarkdownParagraphBlock
    └── "Use the above details to generate a personalized greeting..."

Notice that when embedded in promptDoc, the userDetailsDoc automatically adjusted it's heading levels and moved it's footnotes to the end of the document. Sub-blocks even respect their's hosts requesting styling.


Expressing conditions

Bbmd provides several helper methods for expressing conditions in your markdown documents. These can be used to set default values, conditionally include content, and more.

type PullRequest = { title: string; reviewer?: string; approved: boolean };

const createPrDoc = (pr: PullRequest): MarkdownDocument => {
  const reviewer = b.b(pr.reviewer)
    .default("Unknown reviewer")
    .change((block) => {
      if (pr.approved) return block.strikethrough();
      return block;
    });
  return b.doc(
    b.b(pr.title).h(),
    b.p`Reviewer: ${reviewer}`,
    b.p`Requires review`.if(!pr.approved)
  );
};

createPrDoc({ title: "100", approved: true })
# **100**
Reviewer: ~~**Unknown reviewer**~~
MarkdownDocument
├── MarkdownHeadingBlock [level=1]
│   └── MarkdownBoldBlock
│       └── "100"
├── MarkdownParagraphBlock
│   ├── "Reviewer: "
│   └── MarkdownStrikethroughBlock
│       └── MarkdownBoldBlock
│           └── "Unknown reviewer"
└── MarkdownParagraphBlock

Rendering a block

To render a block, simply convert it to a string however you would for other objects in Typescript.

String(document);
`${document}`;
document.toString();
# Example document {#example-document}
**Important text**[^1]

[^1]: example footnote
MarkdownDocument
├── MarkdownHeadingBlock [identifier=example-document, level=1]
│   └── "Example document"
└── MarkdownParagraphBlock
    ├── MarkdownBoldBlock
    │   └── "Important text"
    └── MarkdownFootnoteBlock [identifier=1]
        └── footer
            └── "example footnote"

On this page