busy-office-ui
Menu

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 updates aria-sort; pagination links carry your querystring; row checkboxes POST via their name/value.

Step 4 — verify

  • Keyboard-only: Tab reaches every control; focus rings visible.
  • Flip data-density to comfortable — 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.

↑↓ move Enter open Esc close