busy-office-ui
Menu

Density

ERP users live in dense grids, so density is a first-class dimension, not a theme afterthought: data-density="compact | comfortable | spacious" on <html> or ANY wrapper remaps one token tier — rows go 30 / 40 / 48px, controls 28 / 36 / 44px, and every component follows, because components consume only density aliases for internal sizing.

Try it (this page is live)

The header's density selector drives this whole site. Per-region override:

regardless of the global setting — the attribute cascades.
<html data-density="compact">          <!-- app-wide default -->
…
<div class="bo-data-table-container" tabindex="0" data-density="spacious">
  <!-- one table, locally more readable — density CASCADES -->
</div>

<script>
  // The complete persistence recipe:
  const d = localStorage.getItem('density') ?? 'comfortable';
  document.documentElement.dataset.density = d;
</script>

Device/platform coverage

Audited 2026-08-15 as a follow-up to the Slice 7 Objective review — the question was whether "Web / Mobile app / RF / Tablet (Bento UI)" needs dedicated component variants. It doesn't: density + the named container-query registry already cover every one of these archetypes with the SAME components, just configured differently. No new CSS/JS came out of this — it's a verification pass, not a build.

ArchetypeMechanismLive example
Web data-density="comfortable" (the default) + ordinary container-query response Data table, Dashboard
Mobile app data-density="compact" + the app shell's own bo-shell container query (sidebar collapses to an icon rail ≤ 56rem) The narrow frame on Sidebar navigation, or see the container-query registry
Tablet (Bento UI) bo-widget-grid's container query (span-2 widgets collapse ≤ 41rem) — a Bento-style icon/tile grid is this SAME mechanism at a different breakpoint, not a new component. The App Launch pattern uses it directly rather than inventing a separate grid. Dashboard widget grid
RF / warehouse data-density="spacious" (44px rows — the control boxes inside them do not grow; see the target-size note below) + initScanInput()'s keyboard-wedge input mode — the one archetype that genuinely needed a new input pattern, not just a layout setting Goods receipt pattern

Open question, not resolved here: whether RF/Mobile/Tablet are real target markets worth more investment than this documentation pass is a product decision, not something a coverage audit can settle — the recommendation is to keep documenting the mechanism regardless, since the cost is low and the value doesn't depend on the answer.

Rules that keep density safe

  • Density tokens are rem-only — they scale with the user's root font, so raised text never clips. The low-vision power user runs large text + compact and gets density at a readable size; that combination is the point.
  • Explicit data-density beats container-query auto-compaction when set on the table or its container element itself — a distant wrapper's density tokens still inherit, but the compaction query only checks those two elements, so it stays in force. (This sentence is the canonical statement; 2026-08-21 sweep — two pages previously said "always beats", which the shipped :not([data-density]) selector contradicts.)
  • Density moves row height and font size, not control boxes. A checkbox, radio and the tag-input remove button are 1rem (16px) in every tier, and the data-table sort button is 18px tall. WCAG 2.5.8 is met through its spacing exception — a 24px circle centred on each of those reaches no other target, and check:target-size runs that exact spec test in CI across all three tiers — not through a 24px size floor. If you pack controls closer than 24px between centres, you break that, and the framework cannot guarantee it for markup it did not author.
  • Font size and row height move together per tier — never mix a density font token with a hand-set height.
↑↓ move Enter open Esc close