busy-office-ui v0.9.0

BUSY-OFFICE / UI · SYSTEM RECORD

CSS for screens
that do work.

A CSS-first framework for ERP and back-office applications: semantic components, density as a first-class dimension, and modern CSS with the cascade as the API.

npm i @busy-office/ui

On npm as @busy-office/ui — or pilot without a bundler by copying dist/ into any server stack; the output is plain CSS.

Bundle
16.1 kB gz 98 kB min · per-component files · computed at build
Dependencies
0 JS optional — 26 call-once behaviors
Contrast
40 pairs × 2 themes all AA, CI-gated
API
Versioned classes, tokens, ARIA are semver surface
Floor
Chrome/Edge 119 · Firefox 129 · Safari 17.5 modern CSS instead of a runtime
Live — this is the framework, not a screenshot
check a row →
Vendor Cost center Amount Status
INV-10234 Acme Supply Co. CC-4021 $4,208.00 Pending
INV-10235 Globex Industrial CC-4021 $18,940.50 Approved
INV-10236 Initech GmbH CC-1180 $730.25 Rejected
INV-10237 Umbrella Logistics CC-2205 $2,110.00 Pending
INV-10238 Stark Components CC-1180 $56,000.00 Approved
INV-10239 Wayne Industrial CC-2205 $9,320.75 Pending

Start from where you are

Every other list here sorts by component. This one sorts by situation — each row ends on a page that shows the thing working, not one that only describes it.

One stylesheet, no build step. The cascade contract means your own CSS still wins.

A whole back-office screen, built up from the primitives in order.

What works with zero JS, and what is deliberately not in the box.

Call-once, delegation-based, swap-proof — they survive any DOM replacement.

Server-rendered swaps, and what a 4xx must return for the screen to stay honest.

Remap the semantic token tier. A shipped preset is rendered there to copy.

Find it by task

Button · Dropdown · Segmented control

Forms · Combobox · Filters · Money · Quantity …

Data table · Inline editing · Toolbar & grid nav · Pagination · Tree …

Alerts & toasts · Loading, empty & error · Progress · Stepper · Timeline & audit …

Navbar · Sidebar navigation · Breadcrumb · Drawers · Tabs …

Find · Work one record · Correct data · Clear queues · Monitor — 39 full screens

Built from the back office out

Density is a dimension

One attribute — data-density — remaps a token tier: 30 / 40 / 48px rows, cascading, overridable per region. Users pick; text never clips.

Density →
The cascade is the API

All framework CSS lives in @layer; your unlayered CSS always wins. Re-brand by remapping tokens — live:

/* brand.css — unlayered, so it wins. An accent swap is a FAMILY swap: */
:root {
  --bo-color-accent-solid: #0f766e;   /* your teal, >= 4.5:1 under white */
  --bo-color-accent-strong: #115e59;
  --bo-color-focus-ring: #0d9488;     /* else focus stays blue */
  --bo-color-bg-selected: #f0fdfa;    /* else selected rows stay blue */
  --bo-font-sans: "Your Corporate Sans", system-ui, sans-serif;
}
/* full recipe incl. the dark-mode rule -> Theming guide */
The contract →
The same table, both ways

Semantic components

<table class="bo-data-table">
  <thead><tr>
    <th scope="col" aria-sort="ascending">
      <button class="bo-data-table__sort-btn">Invoice #</button>
    </th>
    <th class="bo-data-table__col--numeric">Amount</th>
  </tr></thead>
  …

Utility classes

<table class="w-full text-sm text-left rtl:text-right
  text-gray-500 dark:text-gray-400">
  <thead class="text-xs text-gray-700 uppercase
    bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
  <tr><th scope="col" class="px-6 py-3 font-semibold
    tracking-wider whitespace-nowrap sticky top-0
    cursor-pointer hover:bg-gray-100 …">

Markup you can read in a diff at screen #200. State lives in aria-sort, data-*, and real elements — not class strings.

State has two channels

Every state ships a visible non-color cue AND a programmatic one. Build-checked contrast; honest ledger for the rest.

+12% increase, worse, vs July

Accessibility model →

HTMX-ready, not HTMX-coupled
<tbody hx-get="/invoices" hx-trigger="every 30s"
       hx-swap="outerHTML settle:300ms">
<!-- .htmx-settling rows flash on update; selection state
     re-derives automatically on htmx:after:swap -->

The integration →

Prints like a report

ERP screens get printed and signed. Headers repeat per page, rows never split, badges print as outlines, nav disappears — ⌘P on the invoice pattern to see it.

Print layer →

Pilot one screen

No framework earns 200 screens on a landing page. Take one screen — à la carte, into your existing app:

import "@busy-office/ui/css/tokens.min";
import "@busy-office/ui/css/reset.min";
import "@busy-office/ui/css/components/data-table.min";
/* one screen, ~4.6 kB gzipped — the exact files above.
   Your unlayered CSS already outranks all of it. */

What you're not signing up for: semantic tokens, documented class names, and data-*/ARIA contracts are the versioned API; the cascade contract means leaving is incremental overrides, not a rewrite. The exit is cheap by design — verify it, don't trust it.

Reviewed like it matters

Every slice went through an adversarial multi-seat design review before sign-off: 40 components, three review rounds, every gate finding fixed or on the public ledger. Two rules that came out of it are now build-enforced: every container query is named, and every state signal carries a non-color cue plus a programmatic channel. The full trail — findings, NO-GO verdicts, and the fixes that answered them — is public in the repo.