Post Widgets & Includes
Post widgets and includes let you extend Markdown posts with custom HTML views and embed external files—all while keeping your content readable and portable.
Post Widgets
Post widgets inject custom HTML views directly into your posts. They’re a structured, type-safe way to add rich or interactive elements without cluttering your Markdown.
A post widget is a Swift type conforming to PostWidget that produces HTML content rendered and injected during post preprocessing:
struct ErrorWidget: PostWidget {
var body: some HTML {
Text("Something went wrong...")
.background(.red)
}
}
Registering Post Widgets
Register post widgets with your site before using them in posts:
struct MySite: Site {
var name = "Example"
var url = URL(static: "https://example.com")
var homePage = Home()
var layout = MainLayout()
var postPages: [any PostPage] {
MainPost()
}
var postWidgets: [any PostWidget] {
MyWidget()
AlertWidget()
}
}
Only registered widgets are eligible for injection.
Using Widgets in Markdown
Reference post widgets using token syntax:
@{MyWidget}
Each token gets replaced with the rendered HTML output of the matching PostWidget type.
To render a token verbatim without processing, prefix it with $:
$@{MyWidget}
Widget tokens work anywhere in Markdown content, but aren’t recognized inside elements that do their own Markdown parsing, like Text(markdown:).
If your widget contains HTML that should pass through the markup processor untouched, use opaqueMarkupGroup() to ensure the rendered widget is used verbatim.
Widget Content
To pass content to your widget via its token, follow the widget name with a colon and the content:
@{AlertWidget:This feature is experimental}
Then use the @WidgetContent property wrapper to accept this text content:
struct AlertWidget: PostWidget {
@WidgetContent(default: "Default alert message") var content
var body: some HTML {
Text(content)
.background(.red)
}
}
The text after the colon is injected into the widget’s @WidgetContent property. If no content is provided, the default value is used.
Post widgets are resolved by type name, not initializer arguments—so MyWidget() and MyWidget(value: "2") both match @{MyWidget}.
Think of post widgets as stable insertion points, not highly parameterized components. Extract shared logic into reusable HTML views, create a unique widget type per insertion point, and keep Markdown-provided content simple and text-based.
Post Includes
Beyond widgets, you can embed external files directly into Markdown.
Text Includes
Inline a file’s contents as plain text using the text token:
@{text:AlertWidget.swift}
Files are loaded relative to your project’s Sources folder and injected verbatim.
- Missing files don’t fail the build
- A warning is logged if the file can’t be opened
Code Includes
Embed source files as fenced Markdown code blocks with syntax highlighting using the code token:
@{code:AlertWidget.swift}
The language is inferred from the file extension and rendered using your site’s post processor.
Stripping Imports
Strip leading import statements with the noimports modifier:
@{code:noimports:AlertWidget.swift}
This removes only leading import-related lines (like import, use, or #include), stopping as soon as real code is encountered. Everything else is preserved exactly as written.