Your first screen
This is what you will have in four steps — a working dense invoice list, the flagship ERP pattern. It is live: check a row and the bulk bar appears. Every step below shows its own result, with the markup that produced it underneath.
| Vendor | Amount | Status | ||
|---|---|---|---|---|
| INV-1 | Acme Supply Co. | $4,208.00 | Pending |
Composed from the exact markup in steps 2 and 3 — nothing here is a mock-up, and no step produces something different from what you just saw.
Step 1 — the page skeleton
Density on <html>, one stylesheet, two init calls (once,
ever). Get the files per Installation.
<!doctype html>
<html lang="en" data-density="compact">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Invoices</title>
<link rel="stylesheet" href="/assets/busy-office-ui/index.min.css">
</head>
<body>
<div class="bo-app-shell">
<header class="bo-navbar bo-app-shell__header">
<a class="bo-navbar__brand" href="/">Acme ERP</a>
</header>
<nav class="bo-sidebar-nav bo-app-shell__sidebar" aria-label="Main">
<ul>
<li><a class="bo-sidebar-nav__link" href="/invoices" aria-current="page">
<span class="bo-sidebar-nav__icon" aria-hidden="true">▤</span>
<span class="bo-sidebar-nav__label">Invoices</span></a></li>
</ul>
</nav>
<main class="bo-app-shell__main"><!-- steps 2-4 go here --></main>
</div>
<script type="module">
import { initDialogs, initDataTables } from "/assets/busy-office-ui/js/index.js";
initDialogs(); initDataTables();
</script>
</body>
</html> No preview for this one, deliberately: it is a whole document rather than a fragment, and after this step the page is an empty shell. The screen starts appearing in step 2.
Step 2 — the filter bar (into <main>)
A real <form>: submit and reset work without JS.
View code
<form class="bo-filter-bar" role="search" aria-label="Invoice filters"
method="get" action="/invoices">
<input class="bo-input" type="search" name="q" aria-label="Search invoices" placeholder="Search…">
<select class="bo-select" name="status" aria-label="Status">
<option>All</option><option>Pending</option><option>Approved</option>
</select>
<button class="bo-btn bo-btn--secondary" type="submit">Apply</button>
</form> Step 3 — the table
| Vendor | Amount | Status | ||
|---|---|---|---|---|
| INV-1 | Acme Supply Co. | $4,208.00 | Pending |
View code
<div class="bo-data-table-container" tabindex="0">
<div class="bo-data-table__toolbar">
<div class="bo-data-table__bulk-actions" role="group" aria-label="Bulk actions">
<button class="bo-btn" type="button">Approve selected</button>
</div>
<span class="bo-data-table__selection-count"></span>
</div>
<table class="bo-data-table">
<thead><tr>
<th scope="col"><input type="checkbox"
class="bo-checkbox bo-data-table__select-all" aria-label="Select all"></th>
<th scope="col" aria-sort="ascending">
<button class="bo-data-table__sort-btn" type="button">Invoice #</button></th>
<th scope="col">Vendor</th>
<th scope="col" class="bo-data-table__col--numeric">Amount</th>
<th scope="col">Status</th>
</tr></thead>
<tbody>
<tr>
<td><input type="checkbox" name="id" value="INV-1"
class="bo-checkbox bo-data-table__row-select" aria-label="Select INV-1"></td>
<td class="bo-data-table__col--code"><a href="#">INV-1</a></td> <!-- your record URL -->
<td>Acme Supply Co.</td>
<td class="bo-data-table__col--numeric">$4,208.00</td>
<td><span class="bo-badge bo-badge--warning">Pending</span></td>
</tr>
<!-- …your rows… -->
</tbody>
</table>
<div class="bo-data-table__footer">
<nav class="bo-pagination" aria-label="Pagination">
<a class="bo-pagination__btn" aria-current="page" href="?page=1">1</a>
<a class="bo-pagination__btn" href="?page=2">2</a>
<span class="bo-pagination__info">1–25 of 312</span>
</nav>
</div>
</div> - Free (CSS +
initDataTables): check a row → the bulk bar and live "n selected" count appear; select-all works; sticky header; the amount column aligns on tabular numerals. - Yours to wire: the invoice link points at your record URL
(
#here only because this page renders it live); the sort button re-orders rows server-side and updatesaria-sort; pagination links carry your querystring; row checkboxes POST via theirname/value.
Step 4 — verify
- Keyboard-only: Tab reaches every control; focus rings visible.
- Flip
data-densitytocomfortable— everything resizes from the one attribute. - ⌘P — nav and toolbars disappear, the table prints black-on-white with repeating headers.
- Compare against the live pattern; if yours differs, diff your markup against its Markup block.
Next: the detail screen — form sections, timeline, and an approve dialog — composed in the approval pattern.