Inline component tags
Component tags used mid-prose (outside a fence) become real Blazor components at render time. This is how you drop a <Callout> between two paragraphs, a <CardGrid> at the end of a page, or a <Steps> inside a section : without switching to a live-demo fence.
The pattern
Some prose.
<Callout Variant="tip" Title="Something worth knowing">
Body text. Markdown inside is fully parsed.
</Callout>
More prose.
Renders as:
Some prose.
Body text. Markdown inside is fully parsed.
More prose.
The tag looks like Razor / JSX. Behind the scenes, SlotExtractor finds the tag (pattern: opening <Uppercase…> up to a matching close or self-close), resolves the name against TypeRegistry, and replaces the region with a placeholder slot. MarkdownContent swaps the placeholder for a <DynamicComponent> at render time.
Attribute coercion
Same rules as razor:preview : string attributes get coerced to the target property's type via reflection. Variant="warning" → enum, Columns="3" → int, External="true" → bool.
Body content
Non-self-closing tags carry their inner text as ChildContent. It's Markdig-parsed so you get full markdown inside : lists, code blocks, nested tags.
<Steps>
<Step Title="Install">
Run `dotnet tool install -g ShellDocs.CLI --prerelease`.
</Step>
<Step Title="Scaffold">
`shelldocs init MyDocs` creates a working site.
</Step>
</Steps>
Self-closing tags
Tags with no body use the JSX-style self-close />.
<LinkCard Title="Quick start" Description="Scaffold a fresh site." Href="/docs/getting-started/quick-start" />
Renders as:
Naming rules
- Tag name must start with an uppercase letter (matches
<[A-Z][A-Za-z0-9]*>). This is how the parser tells component tags apart from HTML :<span>stays raw,<Span>gets resolved. - Attribute names are case-sensitive : they must match the target's
[Parameter]property name. - Attribute values must be double-quoted. Single quotes and unquoted values aren't recognized.
Nested tags
Tags nest to any depth. The parser tracks open/close balance per tag name, so mismatched or unbalanced tags produce a warning and pass through as raw markup.
<Card>
Body of the card, which itself contains:
<Callout Variant="info">A callout nested inside a card.</Callout>
</Card>
Escaping SVG in attributes
Attribute values containing < or > need HTML-entity encoding : otherwise Markdig's block parser eats the raw markup.
<Card IconSvg="<svg viewBox='0 0 24 24'><path d='...'/></svg>" Title="…" />
In a .razor page you'd write the SVG literal : only markdown authoring requires escaping.
Unknown components
An unrecognized tag emits a warning ("Unknown component
builder.Services.AddShellDocs(o =>
{
o.RegisterComponentsFromAssembly<MyMarker>();
});
Inline vs razor:preview
| Prop | Type | Default | Description |
|---|---|---|---|
Inline tag |
prose |
— | Renders the component ONLY. No source view. Use for chrome-in-prose (callouts, cards, steps) where the reader doesn't need to see the code. |
razor:preview |
fence |
— | Renders component + reveal-on-click source view. Use for docs OF a component : the reader IS learning the tag. |
Rule of thumb: primitives in prose (Callout, Card, LinkCard, Steps, CardGrid, FileTree, TypeTable) → inline tag. Live demo of a component the reader is learning → razor:preview.