Custom Markdown Tags — Overview
This page introduces our custom Markdown tag system: block-level components delimited by !type and !endtype
markers, implemented on top of League CommonMark and wired into Docara’s parser.
What are custom tags?
Custom tags are reusable block components rendered from Markdown using a concise syntax:
!<type> [attributes]
...inner markdown content...
!end<type>
They’re ideal for callouts, examples, embeds, and any structured content that should remain author-friendly while producing consistent HTML.
Anatomy of a tag
- Open marker:
!<type>at the start of a line; may include inline attributes. - Close marker:
!end<type>at the start of a line. - Inner content: parsed as Markdown into HTML, then injected into the tag’s wrapper element.
- Wrapper: by default
<div>, configurable per tag viahtmlTag().
Nesting of the same tag type is allowed by default and can be disabled via
allowNestingSame().
Attribute syntax (short)
Attributes on the open line support:
- Key–value pairs with quotes or unquoted:
key="value",key:'value',key=value - Short-hands:
.classappends toclass,#idsetsid - Multiple classes are merged and deduplicated
- Unicode spaces and smart quotes are normalized for robust parsing
A per-tag
attrsFilter()can sanitize/transform attributes before rendering.
Quick start
- Create a tag class that extends
BaseTagand returns a uniquetype(). - Register the class name in
config.phpunder thetagsarray (Docara’s core provider builds the registry from this list). - Build the site so Docara picks up the registry and our custom parser.
Minimal example
PHP (tag definition)
namespace App\Helpers\CustomTags;
use Simai\Docara\CustomTags\BaseTag;
final class ExampleTag extends BaseTag
{
public function type(): string { return 'example'; }
public function baseAttrs(): array
{
return ['class' => 'example overflow-hidden radius-1/2 overflow-x-auto'];
}
}
Markdown (usage)
!example .mb-4.border
**Inside** the example tag.
!endexample
HTML (simplified result)
<div class="example overflow-hidden radius-1/2 overflow-x-auto mb-4 border">
<p><strong>Inside</strong> the example tag.</p>
</div>
Rendering flow (high level)
- Scan: universal parser matches
openRegex()/closeRegex()from each registered tag. - Capture: everything between markers becomes the tag’s inner Markdown.
- Parse: inner Markdown → HTML.
- Attributes: parse and merge inline attributes with
baseAttrs(); optionalattrsFilter()runs. - Render: use
renderer()if provided; otherwise emit<htmlTag ...attrs>innerHtml</htmlTag>.
Docara integration (summary)
- Tag classes live in your project namespace, e.g.
App\Helpers\CustomTags(extendBaseTag); short class names are listed inconfig.php => tags. CustomTagServiceProviderbuilds the runtime registry from those names and binds it into the container.- The provider also swaps
FrontMatterParserwithSimai\Docara\Parser, which installsCustomTagsExtensionand the rest of the CommonMark stack.
See the dedicated page Registering Custom Tags for the exact configuration snippets.
Do & Don’t
Do
- Keep
baseAttrs()semantic and minimal - Use
attrsFilter()to normalize/whitelist - Provide
renderer()only when you need full control
Don’t
- Hardcode presentation that authors may want to override
- Depend on fragile attribute formats—prefer tolerant parsing
FAQ (quick)
- Can I nest tags? Yes. Same-type nesting can be disabled with
allowNestingSame(). - How do I change the wrapper element? Override
htmlTag()in your tag class. - How do I add default classes? Return them from
baseAttrs(); author classes are merged and deduplicated.