🪵 block based markdown documents

The Markdown toolkit for writing complex, embeddable documents in Typescript

Get started

Bbmd lets you express Markdown documents which are context agnostic. Write a document, and it will adjust itself when embedded in a host to ensure its content and styles make sense in its new context.

Learn more about basic usage →
document.ts
import b from "bbmd"

const opts = b.renderingOptions({
  enforce: { bold: { style: "__" } },
})

const fn = b.footnote`See the full guide for details`
const userGuide = b.doc(
  b.h`User Guide`,
  b.p`Follow these steps ${fn} to get started.`,
  b.section(
    b.h`Installation`,
    b.p("Run ", b.code`npm install bbmd`),
  ),
)

const manual = b.doc(
  b.h`Developer Manual`,
  b.p`Welcome to the ${b.b`developer manual`}.`,
  userGuide,
).setRenderingOptions(opts)

String(manual)
rendered markdown
# Developer Manual
Welcome to the __developer manual__.
## User Guide
Follow these steps[^1] to get started.
### Installation
Run `npm install bbmd`

[^1]: See the full guide for details
✓Adjusted headings in userGuide
✓Moved footnote to bottom
✓Changed bold style to requested format

Because all Bbmd elements are blocks, you can quickly express conditions, defaults, and transformations declaratively using syntax that is natural and intuitive to read.

Learn more about expressing conditions →
with bbmd
const greeting = (name?: string, role?: string) =>
  b.doc(
    b.b(name).default("Unknown"),
    b.p`Role: ${role}`.emptyIf(!role)
      .change((block) => {
        if (role === "Admin") return block.bold()
        if (role === "Archived") return block.st()
        return block
      }),
    b.p`Welcome to the system.`,
  )

String(greeting("Alice", "Admin"))
without bbmd
const greeting = (name?: string, role?: string) => {
  // build the role line with styling
  let roleLine = ""
  if (role) {
    roleLine = `Role: ${role}`
    if (role === "Admin") roleLine = `**${roleLine}**`
    if (role === "Archived") roleLine = `~~${roleLine}~~`
  }

  // assemble and clean up
  return `
    **${name ?? "Unknown"}**
    ${roleLine}
    Welcome to the system.
  `
    .replace(/^\n/, "")          // leading newline
    .replace(/\n\s*$/, "")       // trailing newline
    .replace(/^    /gm, "")      // de-indent
    .replace(/^\s*\n/gm, "")     // remove empty lines
}

greeting("Alice", "Admin")

Adopt Bbmd incrementally. Start by adding b.md`` to clean up an existing template literal, then swap strings for blocks when you need conditions and composability.

Learn more about creating blocks →
before
const prompt = `
  # System Prompt
  You are a helpful assistant.
  ${role === "admin" ? "You have full access." : ""}
`
âš Leading/trailing newlines, indentation, and empty lines when condition is false.
quick adoption
const prompt = b.md`
  # System Prompt
  You are a helpful assistant.
  ${role === "admin" ? "You have full access." : ""}
`.parse()
✓De-indented, trimmed, empty lines removed. Parse intelligently creates blocks based on Markdown elements.
full adoption
const prompt = b.doc(
  b.h`System Prompt`,
  b.p`You are a helpful assistant.`,
  b.p`You have full access.`.if(role === "admin"),
)
✓Full control with tagged template blocks. Terse, composable, and conditionally rendered.

Bbmd was designed for AI prompting. Quickly define shareable context documents and embed them in prompts without worrying about formatting.

Learn more about dynamically creating blocks →
prompt.ts
const project = getProject(projectId)

const projectContext = (project: Project) =>
  b.doc(
    b.h(project.title),
    b.p(project.description),
    b.section(
      b.h`Rules`,
      b.list.ul(...project.rules),
    ),
  )

const prompt = b.doc(
  b.h`System Prompt`,
  b.p`You are an engineer working on a project.`,
  projectContext(project),
)

String(prompt) // pass to any LLM API
rendered markdown
# System Prompt
You are an engineer working on a project.
## Acme Deploy
A CLI tool for managing deployments.
### Rules
- Use TypeScript strict mode
- Prefer const over let
- No default exports