Block shortcodes

A block shortcode is one you’ve described in site.yaml: its name, the parameters it takes and, optionally, what it should look like in the preview. In the visual editor it shows up as a card with a form, so whoever is writing doesn’t need to remember the parameter names or the quoting rules. In the file it’s the same {{< name >}} text Hugo has always read.

Configuration#

Define them under shortcodes:

shortcodes:
  youtube:
    label: YouTube video
    icon: "🎬"
    category: media
    fields:
      - name: id
        type: string
        label: Video ID
        required: true
    preview: |
      <div class="video">YouTube video {{id}}</div>
    hugo: '{{< youtube id="{{id}}" >}}'

The key (youtube) is the shortcode’s name in Hugo, i.e. the filename in layouts/shortcodes/ without the extension.

Property What it does
label The name shown in the insert menu and in the preview pane
icon An emoji or character shown next to the label in those two places
category Groups shortcodes into a submenu of the insert menu
fields The parameters, using the same field types as frontmatter
preview An HTML template for the preview pane; see Preview templates
hugo An example of the shortcode’s syntax. Emet reads it to decide whether the shortcode is paired and whether it takes positional or named parameters, and shows it to the AI as the canonical form. See below
children Names of shortcodes allowed inside this one, which makes it a container
parent Marks a shortcode as child-only, so it’s left out of the main insert menu
min_children A container shows a warning while it has fewer children than this
max_children A container’s Add button is disabled once it has this many
inline Insert the shortcode as a chip inside the current paragraph rather than as a block card. See Shortcodes inside a sentence
allow_text_content Accepted for documentation’s sake. Text before a container’s first child is preserved whether or not you set it

Note that label and icon don’t appear on the editing card itself, which shows the raw shortcode name. I’d like to change that; for now it’s how it is.

Fields#

Each field has a name, a type and the usual optional label, required, default and hint. The name is the parameter name Hugo will see, so it has to match what your template reads with .Get. Defaults are filled in when you insert the shortcode from the menu.

One field name is special. A field called content is the text between the opening and closing tags rather than a parameter, and it’s edited in a textarea (rows sets its height). Declaring it also tells Emet the shortcode is paired:

shortcodes:
  callout:
    label: Callout
    fields:
      - name: type
        type: select
        options: [info, warning, tip]
        default: info
      - name: content
        type: text
        rows: 4
    preview: '<div class="callout callout-{{type}}">{{content}}</div>'

Paired and positional shortcodes#

Hugo is strict about two things, and the editor follows the same rules when it writes a file.

Closing tags. A shortcode whose template reads .Inner must be closed: {{< callout >}}...{{< /callout >}}. A shortcode whose template doesn’t read .Inner (the built-in youtube and figure, for instance) must not have a closing tag or a trailing /, or Hugo fails the build. Emet writes a closing tag only when it considers the shortcode paired, which is when any of these is true: it was read from the file with a closing tag; its config declares a content field; or its hugo example shows a closing tag or a {{content}} placeholder. Type something into a content field and the shortcode becomes paired on the spot.

Parameter style. A call uses either positional parameters ({{< gist "user" "id" >}}) or named ones ({{< figure src="a.png" >}}), never both. Bare words are positional: {{< highlight go >}} passes go as .Get 0, and Emet writes it back as {{< highlight "go" >}}, which Hugo reads identically. When you edit a positional shortcode in a card, the fields map onto positions in the order they’re declared under fields, and the style is preserved.

The hugo property is how you tell Emet which form a shortcode takes before anyone has used it. Write it with {{fieldName}} placeholders matching your field names:

hugo: '{{< figure src="{{src}}" caption="{{caption}}" >}}'           # named, not paired
hugo: '{{< gist "{{user}}" "{{id}}" >}}'                             # positional, not paired
hugo: '{{< callout type="{{type}}" >}}{{content}}{{< /callout >}}' # named, paired

It’s an example, not an output template: Emet doesn’t substitute into it when writing the file, it just reads the shape. The setup wizard fills it in from your shortcode templates.

In the editor#

Insert a shortcode from the braces menu in the toolbar, which lists everything that isn’t child-only, grouped by category. It appears as a card where your cursor was (or as a chip in the paragraph, for inline: true shortcodes). The menu only appears in sections with body.shortcodes: true; the cards appear regardless. See Shortcodes.

The card’s header has a drag handle, a collapse chevron, the shortcode name and, on hover, a delete button. Expanded, the card shows a field for each parameter and the content textarea if there is one. Collapsed, it shows only the header. A freshly inserted shortcode with empty fields opens expanded; one loaded from the file with values in it starts collapsed. Click the header, or double-click anywhere on the card, to toggle.

Drag the handle to move a card up or down the document. The delete button removes it straight away, except for a container with children, which asks first.

Shortcodes inside a sentence#

A shortcode that shares a line with other text (Hello {{< param "site.title" >}} world., or a {{< ref >}} in a list item) is shown as a small blue chip inside the paragraph rather than as a block card, and the paragraph stays in one piece when you save. The chip shows the preview text if the shortcode has a preview, and the raw shortcode otherwise. Click it to open a little form with the fields; click away to close it. Shortcodes configured with inline: true are inserted this way from the toolbar.

Markdown shortcodes#

Both delimiters are supported. A {{% name %}} shortcode gets the same card and is written back with its % delimiter, so Hugo still renders its output as Markdown.

What the editor never touches#

Escaped shortcodes (the slash-star form Hugo documents for showing shortcode syntax literally, which this page is full of), shortcodes inside fenced code blocks and shortcodes inside inline code spans are left exactly as written. You can document shortcodes with Emet without Emet trying to render them.

Containers#

A shortcode with children can hold other shortcodes:

shortcodes:
  tabs:
    label: Tabs
    fields: []
    children: [tab]
    min_children: 1
    preview: '<div class="tabs">{{children}}</div>'
  tab:
    label: Tab
    parent: tabs
    fields:
      - name: title
        type: string
        required: true
      - name: content
        type: text
    preview: '<section><h4>{{title}}</h4>{{content}}</section>'

A container card has an Add Tab button at the bottom of its children area (or Add item with a menu, if more than one child type is allowed), shows the child count in its header and, when collapsed, says how many items are hidden. Each level of nesting gets a different coloured left border so you can see what’s inside what, and nesting works to any depth. With min_children set, the card says “Needs at least N items” while it’s short; with max_children, the add button greys out once you’re there.

A child shortcode’s content textarea only appears on leaf shortcodes. A child that’s itself a container edits its own children instead. Any text between the container’s opening tag and its first child is kept and written back first.

What gets written#

Every block shortcode is serialised the same way: the delimiter it came in with, positional values first, then key="value" pairs with quotes escaped, then the content and a closing tag if it’s paired:

{{< youtube id="abc123" >}}
{{< callout type="warning" >}}Mind the gap.{{< /callout >}}

Containers are written with each child on its own line, indented. Unknown shortcodes keep whichever form they were read in. Multi-line parameter lists are collapsed onto one line, which is the one normalisation you’ll notice in a diff.

In Markdown mode#

In the Markdown editor a block shortcode is just highlighted text, and you edit it like any other text.