home chevron_right
Registering Custom Tags chevron_right
Overview

Custom Markdown Tags — Overviewlink

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?link

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 taglink

  • 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 via htmlTag().

Nesting of the same tag type is allowed by default and can be disabled via allowNestingSame().


Attribute syntax (short)link

Attributes on the open line support:

  • Key–value pairs with quotes or unquoted: key="value", key:'value', key=value
  • Short-hands: .class appends to class, #id sets id
  • 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 startlink

  1. Create a tag class that extends BaseTag and returns a unique type().
  2. Register the class name in config.php under the tags array (Docara’s core provider builds the registry from this list).
  3. Build the site so Docara picks up the registry and our custom parser.

Minimal examplelink

PHP (tag definition)link

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)link

!example .mb-4.border
**Inside** the example tag.
!endexample

HTML (simplified result)link

<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)link

  1. Scan: universal parser matches openRegex() / closeRegex() from each registered tag.
  2. Capture: everything between markers becomes the tag’s inner Markdown.
  3. Parse: inner Markdown → HTML.
  4. Attributes: parse and merge inline attributes with baseAttrs(); optional attrsFilter() runs.
  5. Render: use renderer() if provided; otherwise emit <htmlTag ...attrs>innerHtml</htmlTag>.

Docara integration (summary)link

  • Tag classes live in your project namespace, e.g. App\Helpers\CustomTags (extend BaseTag); short class names are listed in config.php => tags.
  • CustomTagServiceProvider builds the runtime registry from those names and binds it into the container.
  • The provider also swaps FrontMatterParser with Simai\Docara\Parser, which installs CustomTagsExtension and the rest of the CommonMark stack.

See the dedicated page Registering Custom Tags for the exact configuration snippets.


Do & Don’tlink

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)link

  • 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.