This documentation is also published as Markdown for efficient machine reading: the whole site is indexed at /llms.txt, and every page has a clean Markdown copy at the same URL with .md appended. These are generated from the same source and cost far fewer tokens to read than this rendered HTML.

Skip to main content Skip to navigation
Guides

Drop a Razor component into a markdown page

Embed Pennington.UI components (and your own Razor components) inside a `.md` file through Mdazor-backed rendering.

To place a Razor component tag — <Badge>, <Card>, or one of your own — directly inside a .md file instead of authoring raw HTML, write the tag where CommonMark allows an HTML block. Mdazor matches the tag against registered component types and binds attribute values to [Parameter] properties. To author a brand-new component from scratch, see Author a custom Razor component for markdown.

Before you begin

  • A working Pennington site that renders markdown (see Create your first Pennington site if not).
  • The host calls AddDocSite, AddBlogSite, or AddPennington. The first two pre-register the Pennington.UI components meant for markdown use; bare AddPennington requires the registration shown under "Register components on a bare host" below.
  • Component tag names start with an uppercase letter and match the Razor component type name — case-sensitive on the leading character (<Card>, not <card>).

Authoring shapes

AddDocSite pre-registers nine components — <Badge>, <BigTable>, <Card>, <CardGrid>, <Checkpoint>, <LinkCard>, <RenderedFixture>, <Step>, and <Steps>. AddBlogSite pre-registers the same set minus <RenderedFixture> (eight). The H3s below cover the three most common authoring patterns; for the full parameters of each component, see Content components.

Inline a built-in tag

Place the tag anywhere CommonMark allows an HTML block. Attribute values bind to [Parameter] properties by case-insensitive name match.

markdown
<Badge Text="Preview" />
Preview

Pass markdown as ChildContent

Whatever appears between the open and close tags becomes the component's ChildContent render fragment and is parsed as markdown — **bold**, links, and nested components all work inside the body.

markdown
<Card Title="New in v2">
The **v2 pipeline** ships with [unified dev and build](xref:explanation.core.dev-vs-build).
</Card>

New in v2

The v2 pipeline ships with unified dev and build.

Bind primitive attributes

Only primitive parameter types (strings, numbers, booleans) bind from markdown attributes — the value arrives as a raw string and Mdazor converts it via reflection.

markdown
<Card Title="Fast" Color="accent">
Pages render in a single SSR pass.
</Card>

Fast

Pages render in a single SSR pass.

For complex data, pack it into a delimited string and parse inside the component, or use ChildContent for rich content. Content components shows the same pattern in the parameter tables.

Register components on a bare host

AddPennington wires the component registry via AddMdazor() but does not register any components. Chain one AddMdazorComponent<T>() call per component that should be available in markdown — see Content components for the registration block DocSite and BlogSite use.

csharp
builder.Services.AddMdazorComponent<Badge>()
                .AddMdazorComponent<Card>()
                // ... one line per component
                ;

What the renderer emits

Mdazor parses the component tag out of the HTML block, instantiates the matching Razor component, binds attribute values to [Parameter] properties, and renders the result inline. The original tag literal disappears from the output. An unregistered tag on a bare AddPennington host falls through unchanged and renders as literal text — the fastest way to confirm whether the registration is what activates a tag.

See Content components for the parameters of each built-in component.

Read page context in a component

Attributes carry what the author types on the tag. For facts about the page — the source file, the canonical URL, the front matter — a component reads the ambient MdazorContext that Pennington supplies for every rendered page. Declare a [CascadingParameter] of type MdazorContext; nothing goes on the tag.

razor
@using Mdazor
@using Pennington.FrontMatter
  
<p>Rendered from <code>@Context?["FileName"]</code> at <code>@Context?["Url"]</code>.</p>
  
@code {
    [CascadingParameter] public MdazorContext? Context { get; set; }
}

MdazorContext exposes the bag through Values, an indexer (Context["FileName"]), TryGet, and Get<T>. Pennington fills it with these keys, matched case-insensitively:

Key Value
SourceFile Source path on disk for the page
FileName / FileNameWithoutExtension The source file name, with and without extension
Url / CanonicalPath Canonical URL path for the page
OutputFile Static output path written during build
Locale Locale code; empty for the default locale
Metadata The page's front matter as an IFrontMatter (Title, Description, Uid, …)
Derived Enricher-contributed values (reading time, git last-modified, …) keyed by enricher name

The context is delivered as a cascading value, so it reaches the component and any components nested inside it. It does not cross into an interactive (WebAssembly/Server) island, so read it from the statically rendered components that make up the page body. The BeyondCustomRazorComponentExample PageFacts component shows the full pattern.

Verify

  • The <Badge Text="Preview" /> example renders as a styled pill — a rounded, ring-bordered chip — not the literal text <Badge Text="Preview" />. Seeing the raw tag means the component is not registered on the host.
  • View source — the badge is a <span class="not-prose inline-flex ...">, and no <Badge> literal survives in the HTML.
  • On a bare AddPennington host, an unregistered tag passes through unchanged and shows as literal text. Add the AddMdazorComponent<Badge>() call and the pill appears, confirming the registration is what activates the tag.