Menu
Foundations
Alignment & content fit
One short value, one line. Identifiers, names, chips, dates, amounts, button labels, and column headers never break across lines, at any width. Layouts make room, scroll, or truncate; they never squeeze a value into two or three rows. The rule is part of constitution Principle IV and an automated layout audit enforces it on every change.
Tables scroll, content stays whole
| Reference | Merchant | Product | Amount (USD) | Status | Submitted | Actions |
|---|---|---|---|---|---|---|
TXN-241099 | Hillcrest Grocers Makati | novapay | 151.17 | Status · Settled | 27 Aug 2026, 18:31 | |
TXN-241073 | Riverside Fuel Station 12 | novastore | 401.87 | Status · Pending | 27 Aug 2026, 17:57 | |
TXN-240981 | Harbourfront Pharmacy | novabank | 96.40 | Status · Failed | 27 Aug 2026, 15:12 |
<div class="card card--flush">
<div class="tablewrap">
<table class="table">
<thead><tr><th>Reference</th><th>Merchant</th><th>Product</th><th class="num">Amount (USD)</th><th>Status</th><th>Submitted</th><th class="cell--actions">Actions</th></tr></thead>
<tbody>
<tr><td><code>TXN-241099</code></td><td>Hillcrest Grocers Makati</td><td>novapay</td><td class="num">151.17</td><td><span class="badge badge--success"><span class="badge__dot"></span> Status · Settled</span></td><td>27 Aug 2026, 18:31</td><td class="cell--actions"><button class="btn btn--ghost btn--sm">Detail</button></td></tr>
<tr><td><code>TXN-241073</code></td><td>Riverside Fuel Station 12</td><td>novastore</td><td class="num">401.87</td><td><span class="badge badge--warning"><span class="badge__dot"></span> Status · Pending</span></td><td>27 Aug 2026, 17:57</td><td class="cell--actions"><button class="btn btn--ghost btn--sm">Detail</button></td></tr>
<tr><td><code>TXN-240981</code></td><td>Harbourfront Pharmacy</td><td>novabank</td><td class="num">96.40</td><td><span class="badge badge--danger"><span class="badge__dot"></span> Status · Failed</span></td><td>27 Aug 2026, 15:12</td><td class="cell--actions"><button class="btn btn--ghost btn--sm">Detail</button></td></tr>
</tbody>
</table>
</div>
</div>
| Reference | Merchant | Product | Amount (USD) | Status | Submitted |
|---|---|---|---|---|---|
TXN-241099 | Hillcrest Grocers Makati | novapay | 151.17 | Status · Settled | 27 Aug 2026, 18:31 |
TXN-241073 | Riverside Fuel Station 12 | novastore | 401.87 | Status · Pending | 27 Aug 2026, 17:57 |
TXN-240981 | Harbourfront Pharmacy | novabank | 96.40 | Status · Failed | 27 Aug 2026, 15:12 |
<div style="max-width:26rem">
<table class="table">
<thead><tr><th>Reference</th><th>Merchant</th><th>Product</th><th class="num">Amount (USD)</th><th>Status</th><th>Submitted</th></tr></thead>
<tbody>
<tr><td><code>TXN-241099</code></td><td>Hillcrest Grocers Makati</td><td>novapay</td><td class="num">151.17</td><td><span class="badge badge--success"><span class="badge__dot"></span> Status · Settled</span></td><td>27 Aug 2026, 18:31</td></tr>
<tr><td><code>TXN-241073</code></td><td>Riverside Fuel Station 12</td><td>novastore</td><td class="num">401.87</td><td><span class="badge badge--warning"><span class="badge__dot"></span> Status · Pending</span></td><td>27 Aug 2026, 17:57</td></tr>
<tr><td><code>TXN-240981</code></td><td>Harbourfront Pharmacy</td><td>novabank</td><td class="num">96.40</td><td><span class="badge badge--danger"><span class="badge__dot"></span> Status · Failed</span></td><td>27 Aug 2026, 15:12</td></tr>
</tbody>
</table>
</div>
The squeezed table breaks the reference at its hyphen and stacks merchant names and dates onto several lines, so every row has a different height and nothing can be scanned. Wrap the table in .tablewrap instead.
Wrap only free text
Descriptions, notes, and messages are the only content that wraps. In tables they opt in with .cell--wrap, which keeps a readable minimum width; everywhere else paragraphs wrap as normal text.
Use the width you have
The mirror of the single-line rule: a paragraph should not stack into three
or four lines while a quarter of its row sits empty. Portal text runs to the
width of its container. A reading measure is a deliberate choice, marked with
.measure, and belongs to long-form prose, not to a console screen.
Dashboards
Every workspace you can open, with the cases waiting in each one, the breaks still unmatched, and the approvals sitting with you today.
<div class="pagehead" style="margin:0">
<div class="pagehead__row"><h1>Dashboards</h1></div>
<p class="muted pagehead__desc">Every workspace you can open, with the cases waiting in each one, the breaks still unmatched, and the approvals sitting with you today.</p>
</div>
Dashboards
Every workspace you can open, with the cases waiting in each one, the breaks still unmatched, and the approvals sitting with you today.
<div class="pagehead" style="margin:0">
<div class="pagehead__row"><h1>Dashboards</h1></div>
<p class="muted pagehead__desc" style="max-width:18rem">Every workspace you can open, with the cases waiting in each one, the breaks still unmatched, and the approvals sitting with you today.</p>
</div>
The layout audit flags any portal text block that wraps to three or more lines while a quarter or more of its row stays unused and nothing sits beside it. Text that shares its row with a chart or a card is not flagged: the space is in use.
Truncate fixed slots
<div class="card" style="max-width:15rem;display:flex;align-items:center;gap:var(--space-2);padding:var(--space-3)">
<svg class="icon icon--sm" 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="8" r="3.75"/><path d="M4.75 20a7.25 7.25 0 0 1 14.5 0"/></svg><span class="truncate" title="operations.administrator@novustech.com.sg">operations.administrator@novustech.com.sg</span>
</div>
When a value must fit a slot it cannot grow, such as a user name in the header, .truncate keeps it on one line with an ellipsis. The full value must stay available: in title, in the opened menu, or nearby.
Numbers and actions align to the end
- Amounts, counts, and percentages are right-aligned with tabular numerals (
class="num"on the header and the cells), so digits line up. - Row actions sit in the last column, aligned to the end (
.cell--actions); screen actions sit at the end of the title row in the page header. - Text, identifiers, and dates are left-aligned. Never centre data.
One control height per row
<div class="filterrow filterrow--nowrap" style="margin:0">
<input class="input" type="search" placeholder="Search" aria-label="Search">
<button type="button" class="filtermenu__trigger"><svg class="icon icon--sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16l-6.2 7.4V18l-3.6 2v-7.6Z"/></svg><span>Filters</span><svg class="icon icon--sm" 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></button>
<span class="selectwrap"><select class="select" aria-label="Rows per page"><option>10 / page</option><option>20 / page</option></select></span>
<button class="btn btn--secondary">Export</button>
</div>
Checklist
- Every data table sits in
.tablewrap; only free-text columns carry.cell--wrap. - Chips, badges, buttons, breadcrumb items, navigation labels, filter triggers, and footer text are single-line by construction in the console layer.
- A value that must fit a fixed slot uses
.truncateand keeps its full text available. - Toolbars keep one control height and one row; on narrow screens they move as whole groups or scroll, never breaking a label.
- Check at 1440px and 375px. The layout audit (
scripts/layout-audit.mjs) fails any page where short content wraps or the page scrolls sideways.