Skip to content
Sections
Language, currently English
Rastrillo Design System

UI primitives

The shapes a component cannot be, because they wrap a body only the caller knows: the section card, the data grid, the disclosure menu, the shells' own chrome. tokens.css ships the classes and an app writes the markup. Everything below is the markup ui.Styleguide returns, with its words in your language, and ui's tests hold that markup against tokens.css.

Each example is live, but its links go nowhere.

box

The padded section card, and the heading that sits outside it.

Which card is which

rst-list and rst-card have no padding by design: they hold a run of rows, and each row pads itself. Put a form, a paragraph, a strip of links or anything else that is not a row straight into one and it renders flush against the border; the text touching the edge is the tell. The padded card for arbitrary content is rst-box, with its heading as a sibling rst-box-head before it.

Preview
<div rst-box-head>
  <h2>Payout</h2>
  <a rst-btn href="/payout/edit">Edit</a>
</div>
<section rst-box>
  <p>Everything on a screen sits inside boxes.</p>
  <div rst-box-foot>Last updated 2 hours ago</div>
</section>

button

Buttons come in four variants and three sizes, and a link can look like one. For the spinner a form shows while it submits, see form-foot.

Preview
<p>
  <button rst-btn type="button">Cancel</button> <button rst-btn="primary" type="button">Save</button> <button rst-btn="ghost" type="button">Preview</button> <button rst-btn="danger" type="button">Delete</button>
</p>
<p>
  <button rst-btn="primary sm" type="button">Small</button> <button rst-btn="primary" type="button">Default</button> <button rst-btn="primary lg" type="button">Large</button> <a rst-btn href="/orders">View orders</a>
</p>
<p>
  <button rst-btn="primary block" type="button">Continue</button>
</p>
<p>
  <button rst-btn type="button" disabled>Archived</button>
</p>

dropdown

The details/summary menu behind header overflow menus and a list bar's filter, plus an applied filter as a removable chip.

Preview
<details rst-dropdown name="rst-menus">
  <summary>
    Filter
    <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>
    <span class="rst-sr-only">Filter orders: Paid</span>
  </summary>
  <div rst-dropdown-menu>
    <a aria-current="true" href="/orders?status=paid">Paid</a>
    <details rst-menu-group name="rst-menus-price" open>
      <summary>Price</summary>
      <div>
        <a href="/orders?price=free">Free</a>
      </div>
    </details>
  </div>
</details>
<span rst-ftok>
  <span rst-ftok-k>Paid</span>
  <a href="/orders" aria-label="Remove filter Paid">✕</a>
</span>

form-layout

The attributes that give a form its rhythm and its save bar. No partial emits these; they wrap a run of fields you compose yourself.

Preview
<form rst-form-flow method="post" action="/settings">
  <div rst-field>
    <label rst-field-label for="name">Name</label>
    <input rst-input type="text" id="name" name="name">
  </div>
  <div rst-field>
    <label rst-field-label for="email">Email</label>
    <input rst-input type="email" id="email" name="email">
  </div>
  <div rst-field-row>
    <div class="rst-grow" rst-field>
      <label rst-field-label for="city">City</label>
      <input rst-input type="text" id="city" name="city">
    </div>
    <div rst-field>
      <label rst-field-label for="zip">ZIP</label>
      <input rst-input="short" type="text" id="zip" name="zip">
    </div>
  </div>
  <div rst-form-bar>
    <span rst-form-bar-note>Changes save immediately.</span>
    <div rst-form-actions>
      <a rst-btn href="/settings">Cancel</a>
      <button rst-btn="primary" type="submit">Save</button>
    </div>
  </div>
</form>

help

A bordered question mark linking to a help article. Its CSS tooltip is decoration; the link carries its own full-sentence label.

Preview
<a rst-help rst-tip href="/help/orders" target="_blank" rel="noopener" aria-label="Help: orders" data-tip="About orders">
  <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="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
</a>

list-grid

The real data-table vocabulary: the card sets its columns once, rows only choose cells.

Screens stack vertically

A screen is a column: page-header, then section-header + card, then the next section-header + card, in reading order. Horizontal arrangement is reserved for the idioms that ship it: rst-box-head, rst-field-row, rst-lbar, rst-lrow cells, rst-seg-tabs.

Preview
<div rst-card style="--rst-cols: 2fr 110px var(--rst-col-menu)">
  <div rst-lrow="head">
    <span>Order</span>
    <span class="rst-m-hide">Status</span>
    <span></span>
  </div>
  <div rst-lrow>
    <a class="rst-nm" href="/orders/AB3PX"><bdi>Grace Hopper</bdi>
      <small>AB3PX · <bdi>grace@example.com</bdi></small>
    </a>
    <span class="rst-m-hide rst-cell-mut">Paid</span>
    <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">View</a>
        <hr>
        <a class="rst-danger" href="/orders/AB3PX/refund">Refund order…</a>
      </div>
    </details>
  </div>
  <p rst-no-match>No orders match. <a href="/orders">Clear filters</a></p>
</div>
<p rst-count-line>Displaying <strong>1–20</strong> of <strong>412</strong></p>

modal

A modal is its own URL, not client state: the page underneath, marked inert, with the panel over it and a plain link to close.

Preview
<div rst-backdrop inert>
  <div rst-page>
    <h1>Settings</h1>
  </div>
</div>
<div rst-modal-overlay>
  <dialog rst-modal-panel open aria-labelledby="modal-title">
    <nav>
      <a href="/settings/profile" aria-current="page">Profile</a>
      <a href="/settings/billing">Billing</a>
      <a href="/settings/notifications">Notifications</a>
    </nav>
    <section>
      <a rst-modal-close href="/settings" aria-label="Close settings">✕</a>
      <h2 id="modal-title">Profile</h2>
      <p>Update the name and photo shown across the account.</p>
    </section>
  </dialog>
</div>

See it live at the URL it belongs to (opens in a new tab).

selbox

The selection checkbox a list row wears in select mode. Its label restates the row's identity.

Preview
<label rst-selbox>
  <input type="checkbox" aria-label="Select order AB3PX">
</label>

shell-sidebar

The sidebar shell's chrome on a content page: on a phone, a back control to the index.

Preview
<div rst-shell-sidebar="page">
  <a rst-skip href="#main">Skip to content</a>
  <div rst-shell-back>
    <a href="/#nav-reports" rel="up" aria-label="Back to Sections">Sections</a>
  </div>
  <aside rst-shell-rail>
    <h1 rst-shell-title>
      <span><a rst-shell-brand href="/">Notes</a></span><span>Reports</span>
    </h1>
    <a rst-shell-brand href="/">Notes</a>
    <nav rst-shell-nav>
      <span rst-shell-group>Work</span>
      <a id="nav-dashboard" href="/dashboard">Dashboard</a>
      <a id="nav-reports" href="/reports" aria-current="page">Reports</a>
    </nav>
    <div rst-shell-rail-foot>
      <details rst-dropdown rst-shell-profile name="rst-shell-menu">
        <summary>
          <span rst-shell-avatar>
            <span rst-person-av aria-hidden="true">A</span>
            <span rst-person-name>Ada Lovelace</span>
          </span>
        </summary>
        <div rst-dropdown-menu>
          <div rst-shell-who>
            <span rst-person-av aria-hidden="true">A</span>
            <span rst-person-name>Ada Lovelace</span>
            <span rst-person-email>ada@example.com</span>
          </div>
          <a href="/settings">Settings</a>
        </div>
      </details>
    </div>
  </aside>
  <main rst-shell-main id="main">
    <div rst-page>Content.</div>
  </main>
</div>

Open the sidebar shell demo, a standalone preview. (opens in a new tab).

shell-topbar

The topbar shell's own chrome, as markup. Layout ships it as a whole template.

Preview
<div rst-shell-topbar>
  <a rst-skip href="#main">Skip to content</a>
  <header rst-shell-bar>
    <a rst-shell-brand href="/">Notes</a>
    <details rst-shell-menu name="rst-shell-menu">
      <summary>
        <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="M4 12h16"/><path d="M4 6h16"/><path d="M4 18h16"/></svg>Menu</summary>
    </details>
    <div rst-shell-tail>
      <nav rst-shell-nav>
        <a href="/" aria-current="page">Home</a>
        <a href="/archive">Archive</a>
      </nav>
      <details rst-dropdown rst-shell-account name="rst-menus">
        <summary>
          Account
          <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>
          <a href="/settings">Settings</a>
        </div>
      </details>
    </div>
  </header>
  <main rst-page id="main">Content.</main>
  <footer rst-shell-foot>Made with rastrillo</footer>
</div>

Open the topbar shell demo, a dedicated preview. (opens in a new tab).

stat-band

A row of stats across the top of a dashboard.

Preview
<div rst-stats>
  <div rst-stat="lead">
    <span rst-stat-label>Revenue this month</span>
    <span rst-stat-num>&euro;48,210</span>
    <span rst-stat-delta rst-tone="positive">+12%</span>
    <span rst-stat-note>vs. last month</span>
  </div>
  <div rst-stat>
    <span rst-stat-label>Orders today</span>
    <span rst-stat-num>318</span>
  </div>
  <div rst-stat>
    <span rst-stat-label>Average basket</span>
    <span rst-stat-num>&euro;41.20</span>
    <span rst-stat-delta rst-tone="negative">&minus;3%</span>
  </div>
  <div rst-stat>
    <span rst-stat-label>Refunds</span>
    <span rst-stat-num>7</span>
  </div>
</div>

tblock

A bordered card whose body reveals only while its switch is on, via :has(). The switch is authoritative; the reveal is a display convenience.

Preview
<div rst-tblock>
  <label rst-tblock-head>
    <input type="checkbox" name="notify" checked>
    <span rst-switch-track aria-hidden="true"></span>
    <span>
      <span rst-tblock-title>Email notifications</span>
      <span rst-tblock-desc>Sent for every reply to a thread you're in.</span>
    </span>
  </label>
  <div rst-tblock-body>
    <div rst-field>
      <label rst-field-label for="notify-freq">Frequency</label>
      <input rst-input type="text" id="notify-freq" name="notify_freq" value="Daily digest">
    </div>
  </div>
</div>