busy-office-ui
Menu

Installation

These steps are executed, not just written. On every CI run check:quickstart creates an empty directory, installs the package that is about to be published, resolves each import named below, pastes a real screen from the screen kit, and asserts in a browser that the CSS actually applied — a page with no stylesheet still has every row and every class, so it measures computed style rather than markup. If anything here stops working, that build fails.

Install the package and import one stylesheet — that's it. No build step: the output is plain CSS and standard ES modules. Two ideas show up immediately below and are worth a minute each: design tokens (what you re-skin) and density (the one attribute that resizes a whole screen).

Install

npm i @busy-office/ui

Or, with the same result: pnpm add @busy-office/ui · yarn add @busy-office/ui · bun add @busy-office/ui

import "@busy-office/ui/css";   /* everything, one line */

No package manager? Copy dist/css/index.min.css and dist/js/ from the repo (or npm pack @busy-office/ui) into your assets and link them as plain files — see the skeleton below.

Import only what you use (optional)

# ORDER MATTERS: tokens, then reset, then components
import "@busy-office/ui/css/tokens";
import "@busy-office/ui/css/reset";
import "@busy-office/ui/css/components/data-table";
# a .min variant exists for every file (…/data-table.min)

Turn on interactive components

import { initDialogs, initDataTables, initTabs, initDropdowns, initAlerts }
  from "@busy-office/ui/js";

// Call each ONCE, ever — document-level delegation survives all DOM swaps.
initDialogs(); initDataTables(); initTabs(); initDropdowns(); initAlerts();

Components whose page says "JS required" are inert until you call their init. What each does → JS behaviors.

Copy-paste page skeleton

<!doctype html>
<html lang="en" data-density="comfortable"> <!-- or "compact" for back-office -->
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- NEVER add user-scalable=no — zoom is the WCAG 1.4.4 mechanism -->
  <!-- CSS-only pages are LIGHT, coherently — native scrollbars and form
       chrome included. Offering dark mode = setting data-theme, blocking,
       before first paint (localStorage, a cookie, or server-rendered): -->
  <script>
    document.documentElement.dataset.theme =
      localStorage.getItem("theme") ?? "light";
  </script>
  <link rel="stylesheet" href="/vendor/busy-office-ui@0.9.0/index.min.css">
  <!-- Works in ANY server stack (Rails, Django, PHP, Go): copy dist/ into
       your static/asset pipeline — no bundler, no Node at runtime. Put the
       VERSION in the vendored path: upgrades become an explicit new copy +
       one href change, and you can always tell which version is deployed. -->
</head>
<body>
  <div class="bo-app-shell">
    <header class="bo-navbar bo-app-shell__header">…</header>
    <nav class="bo-sidebar-nav bo-app-shell__sidebar" aria-label="Main">…</nav>
    <main class="bo-app-shell__main">…your screen…</main>
  </div>
  <script type="module">
    import { initDialogs, initDataTables } from "/vendor/busy-office-ui/js/index.js";
    initDialogs(); initDataTables();
  </script>
</body>
</html>

Check your markup in CI (recommended)

An unknown class or attribute value fails silently here — modern CSS degrades by doing nothing. One command catches both, against the same api.json the docs are generated from:

// package.json — runs on the BUILT output, so it goes after your build
"scripts": {
  "build": "…",
  "check:markup": "bo-check-markup dist"
}

npm run check:markup        # or: npx bo-check-markup dist

Point it at built HTML, not templates. It reads rendered output, so it must run after your build. If your stack renders server-side (Rails, Django, Laravel), point it at whatever HTML you already produce — a static export, or the fixtures your view tests render.

# .github/workflows/ci.yml
- run: npm ci
- run: npm run build          # must come first — the check reads rendered HTML
- run: npm run check:markup

What it does not do, stated plainly so it isn't mistaken for more than it is: it checks that every bo-* class and every framework data-* value exists. It does not check that your markup means anything — a real class on the wrong element, a heading level that skips, a table used for layout all pass. Your own data-* hooks are never flagged, because nothing distinguishes data-row-id from a misspelled framework attribute. Use it as a spell-checker, not a reviewer.

Browser floor

Chrome/Edge 119 · Firefox 129 · Safari 17.5 — 80.09% of tracked browser usage (caniuse-lite@1.0.30001809). The framework uses @layer, :has(), named container queries, and popover. Server-set aria-invalid error messages degrade gracefully below the floor; interactive :has() reveals do not.

The ceiling is 84.16%, because @layer is this framework's API — no version of it can reach further than @layer does. So the whole distance between what we ship and the best any framework of this shape could do is 4.07 points.

Of that, 0.91 points is bought by giving up two accessibility features: the empty alt on decorative glyphs (content: "↕" / "", which stops a screen reader announcing an arrow) and :user-invalid validation styling. We keep both. That trade is recorded rather than assumed — see roadmap 38.2.

One caveat: it assumes an unmodified root font-size. A host page that rescales html (legacy 62.5% hacks) rescales every rem — embed via iframe instead.

RF scanners and other older handheld devices

The floor above is Chrome/Edge 119 · Firefox 129 · Safari 17.5 — evergreen-desktop territory. A warehouse RF scanner or rugged handheld (Zebra, Honeywell, Datalogic) is a different question: its "Enterprise Browser" wrapper renders through the device's Android System WebView, so the WebView version is the real floor, and a fleet bought in 2018-2020 is not on evergreen Chrome by default.

Researched target: Chrome/WebView 108 — stated at medium confidence, not as a guarantee. Any device that has taken even one vendor patch (Zebra LifeGuard, Honeywell's guaranteed upgrade path, Datalogic Shield) in the last ~3.5 years clears it; a fleet with Play blocked and no patch pipeline sits at the factory image (WebView 61-83 depending on the shipped Android version) and is out of scope for any framework built on modern CSS, not a floor this project chases.

This framework's current CSS floor (Chrome/Edge 119 · Firefox 129 · Safari 17.5) does not reach 108. The shipped rf-essentials stylesheet provides a smaller component set for Chrome/WebView 108; import @busy-office/ui/css/rf-essentials in place of the full stylesheet. See the RF landing pattern for its supported components, and goods receipt for the scanning interaction itself — initScanInput() and the quantity stepper are already Chrome-80-safe today and need no profile.

↑↓ move Enter open Esc close