ContentImage schemes

Image schemes

Standard Markdown image syntax — ![alt](src) — gains two custom URL schemes in the src, resolved during the transform into inline SVG:

SyntaxResolves to
![Portrait](placeholder:portrait)a generated, theme-tinted placeholder
![Star](icon:star)an inline icon from the theme's icon set
![Photo](/images/real.png)an ordinary <img> (unchanged)

Anything that isn't a recognised scheme — a relative path, an absolute URL, a data: URI — falls through to the normal <img> path untouched. Because the schemes resolve to an <svg> element (not an image URL), they're scalable, theme-aware, and need no network request.

placeholder:<shape> — generated placeholders

A stand-in image for drafts, fixtures, and templates: deterministic, offline, and self-contained. Pick the shape that matches the slot's aspect.

{% gallery %}
![A wide cover](placeholder:cover)
![A square tile](placeholder:square)
![A tall portrait](placeholder:portrait)
{% /gallery %}
<figure data-rune="gallery" typeof="ImageGallery" data-rune-fields="{&quot;layout&quot;:&quot;grid&quot;,&quot;lightbox&quot;:&quot;true&quot;}">
  <div data-name="items">
    <figure data-name="item">
      <svg class="rf-placeholder" data-shape="cover" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice" width="100%" fill="none" role="img" aria-label="A wide cover">
        <rect x="0" y="0" width="1600" height="900" fill="var(--rf-color-surface)"></rect>
        <circle cx="1216" cy="270" r="90" fill="var(--rf-color-border)"></circle>
        <path d="M0 648 Q 448 495 800 630 T 1600 576 L 1600 900 L 0 900 Z" fill="var(--rf-color-muted)"></path>
        <rect x="4" y="4" width="1593" height="893" fill="none" stroke="var(--rf-color-border)" stroke-width="7"></rect>
      </svg>
      <figcaption>A wide cover</figcaption>
    </figure>
    <figure data-name="item">
      <svg class="rf-placeholder" data-shape="square" viewBox="0 0 1000 1000" preserveAspectRatio="xMidYMid slice" width="100%" fill="none" role="img" aria-label="A square tile">
        <rect x="0" y="0" width="1000" height="1000" fill="var(--rf-color-surface)"></rect>
        <circle cx="760" cy="300" r="100" fill="var(--rf-color-border)"></circle>
        <path d="M0 720 Q 280 550 500 700 T 1000 640 L 1000 1000 L 0 1000 Z" fill="var(--rf-color-muted)"></path>
        <rect x="4" y="4" width="992" height="992" fill="none" stroke="var(--rf-color-border)" stroke-width="8"></rect>
      </svg>
      <figcaption>A square tile</figcaption>
    </figure>
    <figure data-name="item">
      <svg class="rf-placeholder" data-shape="portrait" viewBox="0 0 750 1000" preserveAspectRatio="xMidYMid slice" width="100%" fill="none" role="img" aria-label="A tall portrait">
        <rect x="0" y="0" width="750" height="1000" fill="var(--rf-color-surface)"></rect>
        <circle cx="570" cy="300" r="75" fill="var(--rf-color-border)"></circle>
        <path d="M0 720 Q 210 550 375 700 T 750 640 L 750 1000 L 0 1000 Z" fill="var(--rf-color-muted)"></path>
        <rect x="3" y="3" width="744" height="994" fill="none" stroke="var(--rf-color-border)" stroke-width="6"></rect>
      </svg>
      <figcaption>A tall portrait</figcaption>
    </figure>
  </div>
</figure>
<figure typeof="ImageGallery" class="rf-gallery rf-gallery--grid" data-layout="grid" data-lightbox="true" data-rune="gallery" data-density="full">
  <div data-name="items" class="rf-gallery__items">
    <figure data-name="item" class="rf-gallery__item">
      <svg class="rf-placeholder" data-shape="cover" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice" width="100%" fill="none" role="img" aria-label="A wide cover">
        <rect x="0" y="0" width="1600" height="900" fill="var(--rf-color-surface)"></rect>
        <circle cx="1216" cy="270" r="90" fill="var(--rf-color-border)"></circle>
        <path d="M0 648 Q 448 495 800 630 T 1600 576 L 1600 900 L 0 900 Z" fill="var(--rf-color-muted)"></path>
        <rect x="4" y="4" width="1593" height="893" fill="none" stroke="var(--rf-color-border)" stroke-width="7"></rect>
      </svg>
      <figcaption>A wide cover</figcaption>
    </figure>
    <figure data-name="item" class="rf-gallery__item">
      <svg class="rf-placeholder" data-shape="square" viewBox="0 0 1000 1000" preserveAspectRatio="xMidYMid slice" width="100%" fill="none" role="img" aria-label="A square tile">
        <rect x="0" y="0" width="1000" height="1000" fill="var(--rf-color-surface)"></rect>
        <circle cx="760" cy="300" r="100" fill="var(--rf-color-border)"></circle>
        <path d="M0 720 Q 280 550 500 700 T 1000 640 L 1000 1000 L 0 1000 Z" fill="var(--rf-color-muted)"></path>
        <rect x="4" y="4" width="992" height="992" fill="none" stroke="var(--rf-color-border)" stroke-width="8"></rect>
      </svg>
      <figcaption>A square tile</figcaption>
    </figure>
    <figure data-name="item" class="rf-gallery__item">
      <svg class="rf-placeholder" data-shape="portrait" viewBox="0 0 750 1000" preserveAspectRatio="xMidYMid slice" width="100%" fill="none" role="img" aria-label="A tall portrait">
        <rect x="0" y="0" width="750" height="1000" fill="var(--rf-color-surface)"></rect>
        <circle cx="570" cy="300" r="75" fill="var(--rf-color-border)"></circle>
        <path d="M0 720 Q 210 550 375 700 T 750 640 L 750 1000 L 0 1000 Z" fill="var(--rf-color-muted)"></path>
        <rect x="3" y="3" width="744" height="994" fill="none" stroke="var(--rf-color-border)" stroke-width="6"></rect>
      </svg>
      <figcaption>A tall portrait</figcaption>
    </figure>
  </div>
</figure>

The placeholder is a neutral scene drawn with theme tokens (--rf-color-surface / --rf-color-muted / --rf-color-border), so it picks up the active tint and dark mode automatically. Output is deterministic — the same shape always renders identically, so it's safe for screenshot tests.

Shapes

ShapeAspectUse
cover16:9hero / banner / gallery tiles
wide12:5wide banners
banner3:1thin page banners
square1:1even tiles, equal-ratio media
portrait3:4vertical cards, posters
thumbnail4:3small previews
avatarroundprofile / author images

An unknown shape falls back to cover.

Drafting with placeholders lets you lay out an image-heavy page before the real assets exist, then swap each placeholder:<shape> for a real path later.

icon:<name> — inline icons

The inline shorthand for the {% icon %} rune: resolve an icon by name (e.g. icon:star, icon:mail) from the theme's icon set, right inside prose.

Give a project a ![star](icon:star) star, or reach us by ![email](icon:mail) email.
<p>
  Give a project a
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="rf-icon" data-icon="star" role="img" aria-label="star">
    <path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
  </svg>
  star, or reach us by
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="rf-icon" data-icon="mail" role="img" aria-label="email">
    <path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"></path>
    <rect x="2" y="4" width="20" height="16" rx="2"></rect>
  </svg>
  email.
</p>

Give a project a star, or reach us by email.

<p>
  Give a project a 
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="rf-icon" data-icon="star" role="img" aria-label="star">
    <path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"></path>
  </svg>
   star, or reach us by 
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="rf-icon" data-icon="mail" role="img" aria-label="email">
    <path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"></path>
    <rect x="2" y="4" width="20" height="16" rx="2"></rect>
  </svg>
   email.
</p>

icon:<name> draws from exactly the same icon registry as the {% icon %} rune, including group/name syntax (e.g. icon:hint/warning). Reach for the rune when you want a size override or a standalone icon; reach for icon: when an icon reads most naturally as an inline image in a sentence. An unknown name falls back to a neutral glyph.

Accessibility

The image alt becomes the accessible label:

  • ![GitHub](icon:github) → the icon is exposed to assistive tech labelled "GitHub" (role="img", aria-label="GitHub").
  • ![](placeholder:cover) with empty alt is treated as decorative (aria-hidden), so screen readers skip it — the right default for a stand-in image.

Write alt the same way you would for any image: describe what it conveys, or leave it empty when it's purely decorative.

A note on data:image/svg+xml

Raw inline SVG data-URIs — ![](data:image/svg+xml,…) — are silently dropped. The Markdown parser's link sanitiser allows data:image/png (and gif/jpeg/webp) but rejects data:image/svg+xml as an XSS-hardening measure, so the image never parses. Use placeholder: or icon: (which emit a trusted inline <svg> element, sidestepping the restriction), or a non-SVG image.

See also