Widgets

Widgets are how markast carries rich, structured components through Markdown into the AST.

Syntax

:::widget-name key="value" key2=value
Body content with full **markdown** support.

# slot-name
Content of an additional slot.
:::

…lands in the AST as:

{
  "type":   "widget",
  "widget": "widget-name",
  "props":  { "key": "value", "key2": "value" },
  "slots":  {
    "default":   [ /* body */ ],
    "slot-name": [ /* slot */ ]
  }
}

Built-in widgets

WidgetPurposeNotable props
tipFriendly callouttitle, icon
noteNeutral asidetitle, icon
infoInformation callouttitle, icon
warningCaution callouttitle, icon
cautionStronger warningtitle, icon
dangerDestructive-action warningtitle, icon
cardContainer with header/body/footertitle, color, elevated
videoVideo embedsrc*, poster, controls, …
code-groupTabbed code blocksdefault_tab
code-collapseCollapsible blocksummary, open
tabsGeneric tabbed contentdefault, vertical
stepsNumbered step liststart
badgeInline label/pilllabel*, color

Writing a custom widget

from markast import BaseWidget, Parser, WidgetParam


class CalloutWidget(BaseWidget):
    """A simple coloured callout."""

    name = "callout"
    params = {
        "level": WidgetParam(str, default="info",
                             choices=["info", "warn", "error"],
                             description="Callout severity"),
        "title": WidgetParam(str, default=None),
    }


parser = Parser(widgets=[CalloutWidget])
doc = parser.parse(""":::callout level=warn title="Heads up"
Something **important**.
:::""")

print(doc.to_html())

That's the entire contract. The base class handles parsing the header into typed props, filling defaults, validating choices and emitting W004 diagnostics, required-prop checks (W005), Markdown roundtrip, and a default HTML render. You only override what you need.

Slots

A widget body can be split into named slots using bare # slot-name H1 headings at the root of the body:

:::card title="Profile"
Some default body content.

# header
*Custom* header content.

# footer
A footer.
:::

For a widget to acknowledge the extra slot names, declare them on the class:

class CardWidget(BaseWidget):
    name = "card"
    slots = ["header", "footer"]
    params = { "title": WidgetParam(str, default=None) }

If you want to accept any slot names (e.g. for tabs or steps), leave slots = [].

Param types

type_Markdown valueResult
strkey=hi"hi"
strkey="multi word""multi word"
intcount=33
floatratio=1.51.5
boolautoplay=trueTrue (also 1, yes, on)
listtags=a,b,c["a", "b", "c"]
listtags=[1,2,3][1, 2, 3] (JSON form)
dictmeta={"a":1}{"a": 1} (JSON only)
Enumlevel=highmatching Enum member

Custom validators:

WidgetParam(int, default=10,
            validator=lambda x: None if 1 <= x <= 100 else "must be 1..100")

Custom rendering

class BadgeWidget(BaseWidget):
    name = "badge"
    params = {
        "label": WidgetParam(str, required=True),
        "color": WidgetParam(str, default="gray"),
    }

    def to_html(self, node, render_children):
        p = node["props"]
        return f'<span class="badge badge-{p["color"]}">{p["label"]}</span>'

render_children is a callable handed to your widget. Pass any list of child nodes and you get back the rendered string in the renderer's target format. Use it to render slot contents recursively:

def to_html(self, node, render_children):
    body = render_children(node["slots"]["default"])
    return f'<aside class="callout">{body}</aside>'

Registration patterns

# 1. Per-parser (recommended) — keeps state local.
from markast import Parser
parser = Parser(widgets=[MyWidget])

# 2. Globally on the default registry — then the top-level parse() works.
from markast.widgets import default_registry
default_registry.register(MyWidget)

from markast import parse
parse(":::my-widget\n...\n:::")
i
Pattern 1 is preferred for libraries and servers — different parsers can hold different widget sets without cross-contamination.

Schema introspection

CalloutWidget.schema()
# {
#   "name": "callout",
#   "params": {
#     "level": {"type": "str", "required": False, "default": "info",
#               "description": "Callout severity",
#               "choices": ["info", "warn", "error"]},
#     "title": {"type": "str", "required": False}
#   },
#   "slots": ["default"],
#   "doc": "A simple coloured callout."
# }