⌘K

Shortcodes

Shortcodes are tags inserted in page or article content and resolved server-side at render time. All of them are parsed inside render_content_html(), in core/render/tf-shortcodes.php.

Parsing order

  1. [gallery id="X"]renderGallery()
  2. [toc] → anchors on <h2>/<h3> + table of contents
  3. [callout type="…"]…[/callout]
  4. [quote author="…"]…[/quote]
  5. [button …]
  6. [recent_articles …]
  7. [recent_projects …]
  8. [articles_by_tag …]
  9. [contact_form]

Each parser is conditional — if its shortcode string isn't found in the HTML, the regex never runs.


Self-closing shortcodes

[toc]

Auto-generated table of contents from every <h2> and <h3> in the content.

[button url="…" label="…" style="…"]

AttributeValuesRequired
urlAny URLYes
labelButton textYes
styleprimary \\secondary \\outlineNo (default primary)

[recent_articles limit="3" tag="slug" category="slug"]

Grid of recent articles, optionally filtered by tag or category.

🚫

Reads from sl_load_index('article') directly — never from $GLOBALS['data']. On single-item pages $GLOBALS['data']['article'] only contains the current item; using it here would return empty results.

[recent_projects limit="3"]

Same idea, for projects.

[articles_by_tag tag="slug" limit="5"]

Alias of [recent_articles] with a mandatory tag filter.

[contact_form]

Full contact form: CSRF, honeypot, rate limiting, optional hCaptcha. Injects contact.css once per page.


Wrapping shortcodes

[callout type="info"]…[/callout]

TypeColor
infoBlue
warningOrange
tipGreen
dangerRed
⚠️

Core emits single-dash class names: sc-callout-info, sc-callout-icon, sc-callout-body, sc-callout-title. A theme stylesheet written with double-dash BEM-style names (sc-callout--info) will silently never match — check your theme's shortcodes.css uses single dashes if callouts render unstyled.

[quote author="Name"]…[/quote]

Styled blockquote with attribution.


Adding a custom shortcode

Core has no filter hook for third-party shortcodes — a theme adds one by extending render_content_html()'s own pipeline via a functions.php filter (see Hooks & Filters); a plugin instead wraps the whole page in an output buffer (see Front-End Integration) since it can't touch core files.

For a theme-owned shortcode:

  1. Add the parser inside your own render pipeline, following the same conditional-strpos() pattern as the built-ins.
  2. Use sl_load_index($type) for any data lookup — never $GLOBALS['data'].
  3. Prefix your CSS with sc- in the theme's shortcodes.css, single-dash for any sub-parts.
  4. Add the entry to the shortcode picker in both admin/assets/js/editor.js (WYSIWYG) and admin/assets/js/editor-markdown.js (Markdown mode) if you want it in the picker UI.
if (strpos($html, '[myshortcode') !== false) {
    $html = preg_replace_callback(
        '/\[myshortcode([^\]]*)\]/i',
        function ($m) {
            $attrs = _shortcode_parse_attrs($m[1]);
            return '<div class="sc-myshortcode">…</div>';
        },
        $html
    );
}