Rendering options
If one of the super-powers of a block-based architecture is that blocks can structurally adjust themselves based on their context, the other is that they can adjust themselves stylistically as well.
You can set rendering options on any block using .setRenderingOptions(). These options propagate through the block's entire subtree, so every nested block renders consistently without any extra effort.
Creating shared options
Use b.renderingOptions() to create a reusable options object. This is particularly useful for organizations that want to define a consistent Markdown style across all documents — create it once, share it via a module, and apply it everywhere.
const orgStyle = b.renderingOptions({
enforce: {
bold: { style: "__" },
italic: { style: "_" },
unorderedListItem: { style: "*" },
},
newlineStrategy: "between_blocks",
})
const doc = b.doc(
b.h`Style Guide`,
b.p`${b.b`Bold`} and ${b.i`italic`} text`,
b.list.ul("First item", "Second item"),
).setRenderingOptions(orgStyle)
String(doc)# Style Guide
__Bold__ and _italic_ text
* First item
* Second itemYou can also inline the options directly on a block. These persist across every render and are used automatically when calling String() or .toString().
const doc = b.doc(
b.h`Release Notes`,
b.p("Version ", b.b`2.0`),
b.p`Bug fixes and improvements.`,
).setRenderingOptions({
newlineStrategy: "between_blocks",
enforce: { bold: { style: "__" } },
})
String(doc)# Release Notes
Version __2.0__
Bug fixes and improvements.newlineStrategy
Controls how newlines are inserted between blocks in multiline containers like documents and sections.
| Strategy | Behavior |
|---|---|
"none" | Single newline between all blocks (default) |
"between_blocks" | Double newline between all blocks |
"before_and_after_heading" | Double newline before and after headings only |
"none" (default)
b.doc("one", "two", "three")
.setRenderingOptions({ newlineStrategy: "none" })one
two
three"between_blocks"
b.doc("one", "two", "three")
.setRenderingOptions({ newlineStrategy: "between_blocks" })one
two
three"before_and_after_heading"
Adds double newlines around headings while keeping single newlines between other blocks.
b.doc("intro", b.h`Title`, "body")
.setRenderingOptions({ newlineStrategy: "before_and_after_heading" })intro
# Title
bodyenforce
Style overrides that are applied to every matching block in the subtree, regardless of per-block settings. This is what makes shared rendering options powerful — individual contributors can write blocks however they like, and the organization's style is enforced at render time.
enforce.bold
Override the bold marker style.
| Style | Output |
|---|---|
"**" | **text** (default) |
"__" | __text__ |
b.b`important`
.setRenderingOptions({ enforce: { bold: { style: "__" } } })__important__Even per-block .style() calls are overridden:
b.b`important`.style("__")
.setRenderingOptions({ enforce: { bold: { style: "**" } } })**important**enforce.italic
Override the italic marker style.
| Style | Output |
|---|---|
"*" | *text* (default) |
"_" | _text_ |
b.i`emphasis`
.setRenderingOptions({ enforce: { italic: { style: "_" } } })_emphasis_enforce.unorderedListItem
Override the bullet marker for unordered list items.
| Style | Output |
|---|---|
"-" | - item (default) |
"*" | * item |
"+" | + item |
b.list.unordered("one", "two")
.setRenderingOptions({ enforce: { unorderedListItem: { style: "*" } } })* one
* twoenforce.horizontalRule
Override the horizontal rule character.
| Style | Output |
|---|---|
"-" | --- (default) |
"*" | *** |
"_" | ___ |
b.hr()
.setRenderingOptions({ enforce: { horizontalRule: { style: "*" } } })
***The .count() setting on horizontal rules is still respected:
b.hr().count(5)
.setRenderingOptions({ enforce: { horizontalRule: { style: "*" } } })
*****enforce.taskItem
Override the checkbox marker for checked task items. Unchecked tasks always render as [ ].
| Style | Output |
|---|---|
"x" | - [x] task (default) |
"X" | - [X] task |
b.list.tasks([true, "done"], [false, "pending"])
.setRenderingOptions({ enforce: { taskItem: { style: "X" } } })- [X] done
- [ ] pendingenforce.table
Override table column alignment for all columns.
| Style | Separator |
|---|---|
undefined | --- (default, no alignment) |
"left" | :--- |
"center" | :---: |
"right" | ---: |
b.table(
{ name: "Name", role: "Role" },
{ name: "Alice", role: "Admin" },
{ name: "Bob", role: "Viewer" },
).setRenderingOptions({ enforce: { table: { align: "center" } } })| Name | Role |
| :---: | :---: |
| Alice | Admin |
| Bob | Viewer |enforce.list
Override the indentation depth (in spaces) for nested lists.
b.list.unordered(
"top level",
b.list.unordered("nested"),
).setRenderingOptions({ enforce: { list: { indent: 4 } } })- top level
- nestedDefault indentation is 2 spaces.
lineJoinString
Controls the separator used when joining multiple content items within an inline block. Defaults to "" (empty string).
b.p("hello", "world")
.setRenderingOptions({ lineJoinString: " " })hello worldb.p("one", "two", "three")
.setRenderingOptions({ lineJoinString: ", " })one, two, threePropagates into nested inline blocks:
b.p("hello ", b.b("foo", "bar"))
.setRenderingOptions({ lineJoinString: "-" })hello -**foo-bar**renderNullish
Controls whether null and undefined values are rendered or silently filtered out. Defaults to false.
When false (default), nullish values are removed:
b.doc("hello", null, "world")
.setRenderingOptions({ renderNullish: false })hello
worldWhen true, nullish values are rendered as empty lines in multiline blocks:
b.doc("hello", null, "world")
.setRenderingOptions({ renderNullish: true })hello
worldAnd as literal strings in inline blocks:
b.p("hello", null, "world")
.setRenderingOptions({ renderNullish: true })hellonullworldPropagation
All rendering options propagate through the entire block subtree — documents, sections, block quotes, lists, and inline blocks all inherit the options from their parent.
const doc = b.doc(
b.h`Title`,
b.p(b.b`intro`, " ", b.i`text`),
b.list.unordered("item 1", "item 2"),
b.hr(),
).setRenderingOptions({
enforce: {
bold: { style: "__" },
italic: { style: "_" },
unorderedListItem: { style: "*" },
horizontalRule: { style: "*" },
},
newlineStrategy: "between_blocks",
})
String(doc)# Title
__intro__ _text_
* item 1
* item 2
***Priority order
Options are resolved in the following order (highest priority wins):
- Options passed to
.render() - Block-level options set via
.setRenderingOptions() - Default values