busy-office-ui
Menu

Pattern: RF task menu

Who uses it: a warehouse floor worker, first screen after signing into the handheld — every RF shift starts here. What "done" looks like: the next task is one tap away, with 3-6 items visible at once, never a menu to search. Not for a general app launcher with dozens of destinations — that's App launch, which this reuses the mechanism from at a different scale: same .bo-widget-grid container-query grid, glove-sized tiles instead of icon tiles, and data-density="spacious" for one-handed, often-gloved use. An RF device runs a handful of tasks per shift, not a suite — this menu stays that short on purpose (roadmap 109.7).

The screen — try it

Shown the way the scanner gets it: an isolated 360×640 document styled by ONLY the rf-essentials profile (roadmap 59.2, Chrome/WebView 108) — not the docs bundle. One screen, not two: this page rendered the same component a second time at desktop width until roadmap 135.2.

What is in the profile — all 14 of them. dashboard/dashboard (the source of .bo-widget-grid/ .bo-widget) joined the profile for this pattern (109.7) — its @container queries need Chrome 105+, already inside this profile's 108 floor, so no guard work was needed.

Markup

<div class="bo-widget-grid" style="--bo-widget-min: 8rem" data-density="spacious">
  <a href="/receive" class="bo-widget" style="text-align: center; padding-block: var(--bo-space-5)">
    <span style="font-size: var(--bo-font-size-lg); font-weight: var(--bo-font-weight-semibold)">Receive</span>
    <span class="bo-badge" aria-label="4 open">4</span>
  </a>
  <!-- one .bo-widget per task -->
</div>

Anatomy

  1. Task grid — .bo-widget-grid with a raised --bo-widget-min for glove-sized tap targets; auto-fills the 360px screen at 3-4 tiles.
  2. Task tile — .bo-widget as a link; label + an open-count .bo-badge, nothing else — an RF worker already knows the task by name, so no icon set is pulled into the profile for it (grill verdict below).
  3. Zero-state tile (Count, above) — a 0 count still renders the tile; it does not disappear, so the menu's shape never shifts mid-shift.

Data contract

RequestReturns / on failure
GET /rf/tasksone entry per task type with its open count; 401 → redirect to login, never a silent empty menu
GET /rf/tasks/:typethe queue for that task (see RF task queue); 204 for an empty queue, not a 200 with an empty array a client has to interpret

States

StateWhat ships
Loadingtiles render immediately with a skeleton count; never block the whole grid on one slow endpoint
Zero openthe tile stays, badge reads 0 — see Count above; a vanished tile reads as "not my job today," which is wrong
Session lostthe reconnect state: the menu re-renders from the server when the link returns. Cached menus and stale-data cues are an APP/infrastructure layer (the reference products sell session persistence as a server product) — this framework ships the visuals a reconnect needs, and promises nothing it does not ship
Permission-scopeda task type the worker's role can't run is omitted from the grid, not shown disabled — RF screen real estate at 360px is too scarce for a tile nobody can use
No JSfully functional — this page has no JS dependency; each tile is a real <a href> (native navigation to the task queue), and the page imports no @busy-office/ui/js behavior

Components used

complexity 1 of 4

↑↓ move Enter open Esc close