Settings
UI Kit
The shared parts every Hestia screen is built from. Copy the markup; the classes live in src/styles/kit.css.
Page Header
Eyebrow, title, one-line description, actions on the right. One per page.
<header class="hk-page-head">
<div>
<p class="hk-eyebrow">Purchasing</p>
<h1 class="hk-page-title">Supplier Bills</h1>
<p class="hk-page-desc">Bills waiting for approval before they post to Xero.</p>
</div>
<div class="hk-page-actions"><button class="hk-btn">Upload a bill</button></div>
</header>Info Bubble Screens carry no explanatory lines. When James asks for an explanation, it goes in a bubble like this one, beside what it explains.
House rule (James, 6 Oct 2026): no explanatory lines on screens unless he asks for a specific one, and then only as an info bubble. Hover, focus or tap the "i".
import Info from '…/components/kit/Info.astro';
<span class="hk-label">Hourly rate <Info about="About hourly rates">The rate dated on or before a shift's date costs it.</Info></span>
// in script-built HTML: import { infoBubble } from '…/components/kit/info'; infoBubble('text', 'About X') Tracked Stock Mark
| Item A bubble in the first row of a table opens below when there is no room above; no table or card edge clips it. | Qty |
|---|---|
| Whole milk 2L Tracked Stock | 6 |
| Clotted cream tub Nothing will move: no tub size | 4 |
| Napkins | 2 |
import Tracked from '…/components/kit/Tracked.astro';
<td>{item.name}<Tracked when={item.tracked} problem={item.linked ? undefined : 'Not linked to an ingredient'} /></td>
// in script-built HTML: import { trackedMark } from '…/components/kit/info';
// trackedMark(true) trackedMark({ problem: 'Not linked to an ingredient' }) trackedMark(false) = nothing Collapsible Section
Quiet senders (3)
Majestic Wine · LWC · Woods
Recent emails (12)
Brakes · BG Fish · HG Walter
<details class="hk-collapse"><summary>Quiet senders (3)</summary> … </details> <details class="hk-card hk-collapse"><summary class="hk-card-head"><h2 class="hk-card-title">Title</h2></summary><div class="hk-card-body">…</div></details> // starts open: add the open attribute. Never a separate Show / Hide button.
Suggested Match
import MatchPicker from '…/components/kit/MatchPicker.astro';
<div class="hk-match-row"><MatchPicker id="ing12" options={ingredients} suggested="42" placeholder="Search ingredients" /><button class="hk-btn hk-btn-sm">Link</button></div>
// in script-built HTML: import { matchPicker, wireMatchPickers } from '…/components/kit/match';
// box.innerHTML = matchPicker({ id: 'ing12', options, suggested: '42' }); wireMatchPickers(box);
// then: document.getElementById('ing12').addEventListener('change', …) (value = the chosen option's value) Stat Band
The top of a dashboard, as Atlas: one framed strip of figures. Cormorant numerals; a warning cell gets a Fired Brick edge.
<div class="hk-band" style="--hk-band-cols: 4"> <a class="hk-band-item hk-warn" href="…"><div class="hk-stat-label">Overdue</div><div class="hk-stat-value">3</div><div class="hk-stat-note">oldest 9 days</div></a> </div>
Filter Row And Clickable Rows
Search and filters above a table; a row that opens a record is tr.hk-row-link with data-href (or listen for hk:rowopen). Tab reaches it, Enter opens it.
Showing 3 of 41 bills. Click a row to open it.
| Supplier | Type | Status | Total |
|---|---|---|---|
| Example Foods Ltd | Food | Queried | £504.00 |
| Sample Drinks Co | Drinks | Approved | £1,500.60 |
| Demo Linen Hire | Linen | Void | £115.20 |
hk-tag + hk-tag-good / hk-tag-warn / hk-tag-off. Count badge: Inbox39 is <span class="hk-count">39</span>. Segmented control: hk-seg with .active or aria-pressed. Selected anywhere in the kit is a Mid Olive underline and bold, never a filled box.Sortable Table And Xero Sync
Click a column title: A to Z, again Z to A; a triangle marks the active column. No code: <table class="hk-table" data-hk-sort> with <th data-sort="text|num|date">. Pages that render rows from data use src/components/kit/sort.ts. The Xero Sync button goes top right of a page header.
| Supplier | Type | Last bill | 12 months |
|---|---|---|---|
| Example Foods Ltd | Food | 2 Oct 2026 | £12,410.00 |
| Sample Drinks Co | Drinks | 28 Sep 2026 | £31,002.50 |
| Demo Linen Hire | Linen | ||
| Another Supplier | Food | 5 Oct 2026 | £980.20 |
import { xeroSyncButton } from '…/components/kit/xero'; then xeroSyncButton('syncBtn', 'Synced 6 Oct, 14:02'), and wire the click.Synced From Xero
Fields that belong to Xero sit together in one box with the one Xero mark, read-only. Change them in Xero.
<div class="hk-xero-box">
${xeroBoxHead('Synced 6 Oct, 14:02')}
<div class="hk-field"><span class="hk-label">Payment terms</span><div class="hk-readonly">30 days</div></div>
</div> Split View With A Document
hk-split (or hk-split-wide, hk-split-doc) stacks on a phone. hk-doc shows a file inline with a tab per file; on a phone the frame gives way to links.
<iframe class="hk-doc-frame">.<div class="hk-split hk-split-doc">
<div>…the record…</div>
<div class="hk-doc">
<div class="hk-doc-bar"><div class="hk-tabs">…one hk-tab per file…</div><a class="hk-btn hk-btn-quiet hk-btn-sm" href="FILE" target="_blank">Open full size</a></div>
<iframe class="hk-doc-frame" src="FILE" title="Invoice"></iframe>
<div class="hk-doc-links"><a href="FILE" target="_blank">Invoice.pdf</a></div>
</div>
</div> Stat Tiles
One figure each. DM Sans for numbers. Mid Olive when good, Fired Brick when it needs attention.
<div class="hk-stats"> <div class="hk-stat hk-warn"><div class="hk-stat-label">Overdue</div><div class="hk-stat-value">3</div><div class="hk-stat-note">Oldest 9 days</div></div> </div>
Card With A Data Table
A framed section with a header bar. hk-flush lets a table run edge to edge.
| Supplier | Invoice | Status | Net | VAT | Total |
|---|---|---|---|---|---|
| Example Foods Ltd | INV-1042 | Queried | £420.00 | £84.00 | £504.00 |
| Sample Drinks Co | SD-77 | Approved | £1,250.50 | £250.10 | £1,500.60 |
| Demo Linen Hire | DL-2026-09 | Void | £96.00 | £19.20 | £115.20 |
| 3 bills | £1,766.50 | £353.30 | £2,119.80 | ||
<tr><td colspan="6" class="hk-table-empty">No bills waiting.</td></tr>Status Chips
ApprovedNeeds attentionInactiveNeutral
Mid Olive good, Fired Brick warning, faded Brewery Dark inactive. No red, amber or green, and no danger colour until Jon sets one.
<span class="hk-chip hk-chip-good">Approved</span>
Buttons
One primary per section. Secondary is a Fired Brick outline; quiet is a coral text action. The old global .btn now matches primary.
Tabs Inside A Page
For views within one page. Moving between pages is the header's job (lib/nav.ts), not this.
Form Fields
<div class="hk-field"><span class="hk-label">Total</span> <div class="hk-readonly"><span>£504.00</span><span class="hk-readonly-src">From Xero</span></div> </div>
Drop Zone
import DropZone from '../../components/kit/DropZone.astro';
<DropZone id="billFile" accept=".pdf" multiple title="Drop bills here, or click to choose" />
// then: document.getElementById('billFile').addEventListener('change', …) (input.files = every file chosen)
// HTML built by a script:
import { dropZone, wireDropZones } from '../../components/kit/drop';
box.innerHTML = dropZone({ id: 'receiptFile', accept: 'image/*,application/pdf', title: 'Drop the receipt here, or click to choose' });
wireDropZones(box);