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.