Skip to content
Sections
Language, currently English
Rastrillo Design System

List screen

Generic list screen with title, toolbar, rows that work desktop and mobile.

Each example is live, but its links go nowhere. HTML shows the markup to copy.

page-header

The screen's title bar: an h1, an optional subhead, and a primary action.

Title, subhead and a primary action

Preview
<header rst-page-header>
  <div rst-page-header-titles>
    <h1>Posts</h1>
    <p rst-page-header-sub>Everything you have written, newest first.</p>
  </div>
  <a rst-btn="primary" href="/posts/new">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
    Write a post
  </a>
</header>
{{template "page-header" dict
    "Title" "Posts"
    "Sub" "Everything you have written, newest first."
    "ActionHref" "/posts/new"
    "ActionLabel" "Write a post"
    "ActionIcon" "plus"}}

Title only

Preview
<header rst-page-header>
  <div rst-page-header-titles>
    <h1>Settings</h1>
  </div>
</header>
{{template "page-header" dict "Title" "Settings"}}

list-bar

The toolbar at the top of a list card with search and filter.

Search with a filter dropdown

Preview

Put this inside <div rst-list>.

<div rst-lbar>
  <search>
    <form rst-search method="get" action="/posts">
      <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
      <input type="search" name="q" value="release" placeholder="Search posts" aria-label="Search posts">
      <a rst-search-clear href="/posts?sort=newest" aria-label="Clear search">
        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
      </a>
      <input type="hidden" name="sort" value="newest">
      <button class="rst-sr-only" type="submit">Search</button>
    </form>
  </search>
  <details rst-dropdown name="rst-menus">
    <summary rst-btn aria-label="Filter by status: Paid">
      Paid
      <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
    </summary>
    <div rst-dropdown-menu>
      <a href="/posts">All</a>
      <a href="/posts?status=paid" aria-current="true">Paid <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></a>
      <a href="/posts?status=free">Free</a>
    </div>
  </details>
</div>

Put this inside <div rst-list>.

{{template "list-bar" dict
    "Filter" (dict "Aria" "Filter by status: Paid" "Items" (list (dict "Href" "/posts" "Label" "All") (dict "Current" true "Href" "/posts?status=paid" "Label" "Paid") (dict "Href" "/posts?status=free" "Label" "Free")) "Label" "Paid")
    "Hidden" (list (list "sort" "newest"))
    "Placeholder" "Search posts"
    "Query" "release"
    "SearchAction" "/posts"}}

list-search-submit

The search form's real submit button: visually hidden, in the tab order, and it un-hides on keyboard focus.

Default label

Preview

Put this inside <section rst-box>.

<button class="rst-sr-only" type="submit">Search</button>

Put this inside <section rst-box>.

{{template "list-search-submit" dict}}

Nothing is visible here until you tab into it.

list-row-action

One list row: the record's name as the primary link, a meta line, and a secondary action.

Lead marker, status and an action

Preview

Put this inside <div rst-list>.

<div rst-row>
  <span rst-row-lead data-lead="accent" aria-hidden="true">RN</span>
  <span rst-row-main><a href="/posts/1">Release notes, August</a>
    <small rst-row-sub>Published 2 August · 4 min read</small>
  </span>
  <span rst-status rst-tone="positive">Published</span>
  <a rst-row-action href="/posts/1/edit" aria-label="Edit Release notes, August">Edit</a>
</div>

Put this inside <div rst-list>.

{{template "list-row-action" dict
    "Href" "/posts/1"
    "Main" "Release notes, August"
    "Sub" "Published 2 August · 4 min read"
    "ActionHref" "/posts/1/edit"
    "ActionLabel" "Edit"
    "ActionAria" "Edit Release notes, August"
    "StatusTone" "positive"
    "StatusLabel" "Published"
    "Lead" "accent"
    "LeadInitial" "RN"}}

Name and meta line only

Preview

Put this inside <div rst-list>.

<div rst-row>
  <span rst-row-main><a href="/posts/2">Why we moved off the old runner</a>
    <small rst-row-sub>Draft · saved 20 minutes ago</small>
  </span>
</div>

Put this inside <div rst-list>.

{{template "list-row-action" dict
    "Href" "/posts/2"
    "Main" "Why we moved off the old runner"
    "Sub" "Draft · saved 20 minutes ago"}}

row-menu

A row's other actions, behind a ⋮ button. A destructive one links to its confirm page.

Links, a form and a destructive item

Preview
<details rst-row-menu name="rst-menus">
  <summary aria-label="Actions for Grace Hopper">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
  </summary>
  <div rst-row-menu-panel>
    <a href="/orders/AB3PX/edit">Edit</a>
    <form method="post" action="/orders/AB3PX/archive">
      <input type="hidden" name="state" value="archived">
      <button type="submit">Archive</button>
    </form>
    <hr>
    <a class="rst-danger" href="/orders/AB3PX/delete">Delete order…</a>
  </div>
</details>
{{template "row-menu" dict
    "Name" "Grace Hopper"
    "Items" (list
        (dict "Href" "/orders/AB3PX/edit" "Label" "Edit")
        (dict "Action" "/orders/AB3PX/archive" "Hidden" (list (list "state" "archived")) "Label" "Archive")
        (dict "Danger" true "Href" "/orders/AB3PX/delete" "Label" "Delete order…"))}}

Put the destructive item last and end its label with …. It opens a page that asks before anything is deleted.

Links only, for a list inside a selection form

Preview
<details rst-row-menu name="rst-menus">
  <summary aria-label="Actions for Alan Turing">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
  </summary>
  <div rst-row-menu-panel>
    <a href="/orders/CD4QY/edit">Edit</a>
    <hr>
    <a class="rst-danger" href="/orders/CD4QY/delete">Delete order…</a>
  </div>
</details>
{{template "row-menu" dict
    "Name" "Alan Turing"
    "Items" (list
        (dict "Href" "/orders/CD4QY/edit" "Label" "Edit")
        (dict "Danger" true "Href" "/orders/CD4QY/delete" "Label" "Delete order…"))}}

Inside a form, every item must be a link. An action that posts goes through a page of its own.

seg-tabs

A segmented control of server-rendered links. The current tab is aria-current on a link.

Two sections

Preview
<nav rst-seg-tabs aria-label="Post sections">
  <a href="?tab=basics" aria-current="page">Basics</a>
  <a href="?tab=advanced">Advanced</a>
</nav>
{{template "seg-tabs" dict
    "Label" "Post sections"
    "Items" (list
        (dict "Current" true "Href" "?tab=basics" "Label" "Basics")
        (dict "Href" "?tab=advanced" "Label" "Advanced"))}}

dropdown

A zero-JavaScript disclosure: a summary button over a panel of plain links.

A filter menu with one choice applied

Preview
<details rst-dropdown name="rst-menus">
  <summary rst-btn aria-label="Sort posts: newest first">
    Newest
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
  </summary>
  <div rst-dropdown-menu>
    <a href="/posts?sort=newest" aria-current="true">Newest <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></a>
    <a href="/posts?sort=oldest">Oldest</a>
    <a href="/posts?sort=title">By title</a>
  </div>
</details>
{{template "dropdown" dict
    "Label" "Newest"
    "Aria" "Sort posts: newest first"
    "Items" (list
        (dict "Current" true "Href" "/posts?sort=newest" "Label" "Newest")
        (dict "Href" "/posts?sort=oldest" "Label" "Oldest")
        (dict "Href" "/posts?sort=title" "Label" "By title"))}}

bulk-bar

Select mode's header strip, replacing the list bar: the count, an escalate link, and the actions menu.

Three rows selected

Preview

Put this inside <div rst-list>.

<div rst-bulkbar>
  <a rst-bulkbar-close href="/posts" aria-label="Done selecting">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
  </a>
  <span rst-bulkbar-selection><span rst-bulkbar-count>3 selected</span><a rst-bulkbar-escalate href="/posts?select=all">Select all 412 matching</a></span>
  <details rst-dropdown name="rst-menus">
    <summary>
      Actions
      <span rst-caret aria-hidden="true">
        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
      </span>
    </summary>
    <div rst-dropdown-menu>
      <button type="submit" name="action" value="export">Export</button>
      <button type="submit" name="action" value="unpublish">Unpublish</button>
      <button type="submit" name="action" value="delete" class="rst-danger">Delete…</button>
    </div>
  </details>
</div>

Put this inside <div rst-list>.

{{template "bulk-bar" dict
    "DoneHref" "/posts"
    "Count" "3 selected"
    "EscalateHref" "/posts?select=all"
    "EscalateLabel" "Select all 412 matching"
    "Actions" (list
        (dict "Label" "Export" "Value" "export")
        (dict "Label" "Unpublish" "Value" "unpublish")
        (dict "Danger" true "Label" "Delete…" "Value" "delete"))
    "MenuLabel" "Actions"}}

The destructive item is last: the first entry is the surrounding form's implicit-Enter default.

media-grid

A grid of image tiles. Every frame is 4:3, so tall screenshots and wide banners sit in one even grid.

Screenshots, a logo and a banner

Preview

Put this inside <div rst-list>.

<div rst-media>
  <div rst-media-tile>
    <div rst-media-frame>
      <img src="/images/checkout.png" alt="" loading="lazy" decoding="async" width="1280" height="3200">
    </div>
    <div rst-media-caption>
      <div rst-media-name>
        <a href="/images/1">Checkout, step 2</a>
        <details rst-row-menu name="rst-menus">
          <summary aria-label="Actions for Checkout, step 2">
            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
          </summary>
          <div rst-row-menu-panel>
            <a href="/images/1/edit">Edit</a>
            <hr>
            <a class="rst-danger" href="/images/1/delete">Delete…</a>
          </div>
        </details>
      </div>
      <p rst-media-desc>The payment step, with the card form open.</p>
      <p rst-media-meta>1280 × 3200 · 412 KB</p>
    </div>
  </div>
  <div rst-media-tile>
    <div rst-media-frame>
      <img src="/images/pricing.png" alt="" loading="lazy" decoding="async" width="1280" height="2400">
    </div>
    <div rst-media-caption>
      <div rst-media-name>
        <a href="/images/2">Pricing page</a>
        <details rst-row-menu name="rst-menus">
          <summary aria-label="Actions for Pricing page">
            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
          </summary>
          <div rst-row-menu-panel>
            <a href="/images/2/edit">Edit</a>
            <hr>
            <a class="rst-danger" href="/images/2/delete">Delete…</a>
          </div>
        </details>
      </div>
      <p rst-media-desc="none">No description</p>
      <p rst-media-meta>1280 × 2400 · 318 KB</p>
    </div>
  </div>
  <div rst-media-tile>
    <div rst-media-frame="contain">
      <img src="/images/logo.svg" alt="Harbour" loading="lazy" decoding="async" width="240" height="240">
    </div>
    <div rst-media-caption>
      <div rst-media-name>
        <a href="/images/3">Harbour logo</a>
        <details rst-row-menu name="rst-menus">
          <summary aria-label="Actions for Harbour logo">
            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
          </summary>
          <div rst-row-menu-panel>
            <a href="/images/3/edit">Edit</a>
            <hr>
            <a class="rst-danger" href="/images/3/delete">Delete…</a>
          </div>
        </details>
      </div>
      <p rst-media-desc>The mark on its own, for dark and light backgrounds.</p>
      <p rst-media-meta>240 × 240 · 3 KB</p>
    </div>
  </div>
  <div rst-media-tile>
    <div rst-media-frame="contain">
      <img src="/images/banner.png" alt="" loading="lazy" decoding="async" width="1200" height="200">
    </div>
    <div rst-media-caption>
      <div rst-media-name>
        <a href="/images/4">Spring banner</a>
        <details rst-row-menu name="rst-menus">
          <summary aria-label="Actions for Spring banner">
            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
          </summary>
          <div rst-row-menu-panel>
            <a href="/images/4/edit">Edit</a>
            <hr>
            <a class="rst-danger" href="/images/4/delete">Delete…</a>
          </div>
        </details>
      </div>
      <p rst-media-desc>The strip at the top of the shop in March.</p>
      <p rst-media-meta>1200 × 200 · 64 KB</p>
    </div>
  </div>
</div>

Put this inside <div rst-list>.

{{template "media-grid" dict
    "Items" (list
        (dict "Desc" "The payment step, with the card form open." "Height" 3200 "Href" "/images/1" "Menu" (list (dict "Href" "/images/1/edit" "Label" "Edit") (dict "Danger" true "Href" "/images/1/delete" "Label" "Delete…")) "Meta" "1280 × 3200 · 412 KB" "Name" "Checkout, step 2" "Src" "/images/checkout.png" "Width" 1280)
        (dict "Desc" "" "Height" 2400 "Href" "/images/2" "Menu" (list (dict "Href" "/images/2/edit" "Label" "Edit") (dict "Danger" true "Href" "/images/2/delete" "Label" "Delete…")) "Meta" "1280 × 2400 · 318 KB" "Name" "Pricing page" "Src" "/images/pricing.png" "Width" 1280)
        (dict "Alt" "Harbour" "Desc" "The mark on its own, for dark and light backgrounds." "Fit" "contain" "Height" 240 "Href" "/images/3" "Menu" (list (dict "Href" "/images/3/edit" "Label" "Edit") (dict "Danger" true "Href" "/images/3/delete" "Label" "Delete…")) "Meta" "240 × 240 · 3 KB" "Name" "Harbour logo" "Src" "/images/logo.svg" "Width" 240)
        (dict "Desc" "The strip at the top of the shop in March." "Fit" "contain" "Height" 200 "Href" "/images/4" "Menu" (list (dict "Href" "/images/4/edit" "Label" "Edit") (dict "Danger" true "Href" "/images/4/delete" "Label" "Delete…")) "Meta" "1200 × 200 · 64 KB" "Name" "Spring banner" "Src" "/images/banner.png" "Width" 1200))}}

Set Fit to "contain" for a logo or a banner. A screenshot keeps the default and shows its top. A tile with no Desc says "No description".

Select mode, one tile selected

Preview

Put this inside <div rst-list>.

<div rst-media>
  <div rst-media-tile>
    <div rst-media-frame>
      <img src="/images/checkout.png" alt="" loading="lazy" decoding="async" width="1280" height="3200">
      <label rst-selbox>
        <input type="checkbox" name="id" value="1" checked aria-label="Select Checkout, step 2">
      </label>
    </div>
    <div rst-media-caption>
      <div rst-media-name>
        <a href="/images/1">Checkout, step 2</a>
        <details rst-row-menu name="rst-menus">
          <summary aria-label="Actions for Checkout, step 2">
            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
          </summary>
          <div rst-row-menu-panel>
            <a href="/images/1/edit">Edit</a>
            <hr>
            <a class="rst-danger" href="/images/1/delete">Delete…</a>
          </div>
        </details>
      </div>
      <p rst-media-desc>The payment step, with the card form open.</p>
      <p rst-media-meta>1280 × 3200 · 412 KB</p>
    </div>
  </div>
  <div rst-media-tile>
    <div rst-media-frame>
      <img src="/images/pricing.png" alt="" loading="lazy" decoding="async" width="1280" height="2400">
      <label rst-selbox>
        <input type="checkbox" name="id" value="2" aria-label="Select Pricing page">
      </label>
    </div>
    <div rst-media-caption>
      <div rst-media-name>
        <a href="/images/2">Pricing page</a>
        <details rst-row-menu name="rst-menus">
          <summary aria-label="Actions for Pricing page">
            <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
          </summary>
          <div rst-row-menu-panel>
            <a href="/images/2/edit">Edit</a>
            <hr>
            <a class="rst-danger" href="/images/2/delete">Delete…</a>
          </div>
        </details>
      </div>
      <p rst-media-desc="none">No description</p>
      <p rst-media-meta>1280 × 2400 · 318 KB</p>
    </div>
  </div>
</div>

Put this inside <div rst-list>.

{{template "media-grid" dict
    "Items" (list
        (dict "Desc" "The payment step, with the card form open." "Height" 3200 "Href" "/images/1" "Menu" (list (dict "Href" "/images/1/edit" "Label" "Edit") (dict "Danger" true "Href" "/images/1/delete" "Label" "Delete…")) "Meta" "1280 × 3200 · 412 KB" "Name" "Checkout, step 2" "Select" "1" "Selected" true "Src" "/images/checkout.png" "Width" 1280)
        (dict "Desc" "" "Height" 2400 "Href" "/images/2" "Menu" (list (dict "Href" "/images/2/edit" "Label" "Edit") (dict "Danger" true "Href" "/images/2/delete" "Label" "Delete…")) "Meta" "1280 × 2400 · 318 KB" "Name" "Pricing page" "Select" "2" "Src" "/images/pricing.png" "Width" 1280))}}

Wrap the grid and a bulk-bar in one form. Each checkbox posts its tile's Select value under the name "id"; set SelectName to change it. Inside the form, menu items must be links.

pagination

A numbered strip of ordinary links, so paging survives JavaScript being off.

Middle of a long list

Preview
<nav rst-pagination aria-label="Posts pages">
  <a href="/posts?page=3" rst-pagination-step="prev"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg><span rst-pagination-word>Previous</span></a>
  <a href="/posts?page=1">1</a>
  <span rst-pagination-gap aria-hidden="true">…</span>
  <a href="/posts?page=3" rst-pagination-wide>3</a>
  <span aria-current="page">4</span>
  <a href="/posts?page=5" rst-pagination-wide>5</a>
  <span rst-pagination-gap aria-hidden="true">…</span>
  <a href="/posts?page=9">9</a>
  <a href="/posts?page=5" rst-pagination-step="next"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg><span rst-pagination-word>Next</span></a>
</nav>
{{template "pagination" dict
    "Items" (list
        (dict "Href" "/posts?page=3" "Label" "Previous" "Rel" "prev")
        (dict "Href" "/posts?page=1" "Label" "1")
        (dict "Gap" true)
        (dict "Href" "/posts?page=3" "Label" "3")
        (dict "Current" true "Label" "4")
        (dict "Href" "/posts?page=5" "Label" "5")
        (dict "Gap" true)
        (dict "Href" "/posts?page=9" "Label" "9")
        (dict "Href" "/posts?page=5" "Label" "Next" "Rel" "next"))
    "Label" "Posts pages"}}

First page, default accessible name

Preview
<nav rst-pagination aria-label="Pagination">
  <span rst-pagination-disabled rst-pagination-step="prev"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg><span rst-pagination-word>Previous</span></span>
  <span aria-current="page">1</span>
  <a href="/posts?page=2">2</a>
  <a href="/posts?page=2" rst-pagination-step="next"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg><span rst-pagination-word>Next</span></a>
</nav>
{{template "pagination" dict
    "Items" (list
        (dict "Disabled" true "Label" "Previous" "Rel" "prev")
        (dict "Current" true "Label" "1")
        (dict "Href" "/posts?page=2" "Label" "2")
        (dict "Href" "/posts?page=2" "Label" "Next" "Rel" "next"))}}

No Label, so the nav's accessible name comes from the catalog.

empty-state

The blank-state card. Empty state with affordances for starting steps.

With a POST call to action

Preview
<div rst-empty>
  <h2 rst-empty-title>Nothing here yet</h2>
  <p rst-empty-body>No posts yet. Your first one is a good place to start.</p>
  <form rst-empty-cta method="post" action="/posts/seed">
    <input type="hidden" name="csrf" value="tok-123">
    <button rst-btn="primary" type="submit">Add sample posts</button>
  </form>
</div>
{{template "empty-state" dict
    "Body" "No posts yet. Your first one is a good place to start."
    "Title" "Nothing here yet"
    "ActionLabel" "Add sample posts"
    "PostAction" "/posts/seed"
    "Hidden" (list (list "csrf" "tok-123"))}}

Sentence only

Preview
<div rst-empty>
  <p rst-empty-body>No comments on this post yet.</p>
</div>
{{template "empty-state" dict "Body" "No comments on this post yet."}}