Workshop Kit · widget reference

Shipped

menu-builder

A reorderable dish list with inline name + price editing. Add / remove / reorder up to a configurable max (default 12); the min (default 3) keeps the operator from accidentally publishing an empty menu page. Same keyboard-first reordering model as drag-rank — up/down buttons, no HTML5 drag — plus inline editing for every field. Writes to dishes[]; the L14 generator renders the same array as menu.html.

Live demo

Click Add to grow the list; type into name/price fields; reorder with the up/down buttons; remove with ×. Saves on every change. The L8 lesson seeds operators with a "menu shortlist" framing — 5-8 dishes that demonstrate the kitchen's range, not the whole menu.

data-context-key="dishes" data-min="3" data-max="12"

Current MuntinContext.dishes Add a dish above…

Contract

Drop a <section> with the data-context-key and the widget renders the editable list. Optional data-min and data-max set the hard caps; the Add button disables at max, the Remove button disables at min.

AttributeTypePurpose
data-widgetRequired literal Always "menu-builder".
data-context-keyRequired string Where the dish array lands in MuntinContext. The shape is [{ name: string, price: string }, …]. L8 uses "dishes" by convention; the L14 generator reads from that exact key.
data-min integer Minimum row count. Default 3. The Remove button on each row disables when length === min, preventing accidental deletion below the floor.
data-max integer Maximum row count. Default 12. The Add button disables when length === max. Why a cap: a generated menu page with 40 dishes is a wall of text that loses to a phone scroll; the operator prints the long version on paper.

What it writes: the array commits to the named context key on every name/price edit, add, remove, and reorder. Price values pass through a strict regex (/^\$?\d{1,4}(?:\.\d{1,2})?$/ after whitespace stripping) at render time in the generator — the widget itself accepts any string, but malformed prices render blank on the deployed menu page.

Markup

<section class="course-widget" data-widget="menu-builder" data-context-key="dishes" data-min="3" data-max="12"></section>

Keyboard model

Same posture as drag-rank: no HTML5 drag-and-drop, every reorder goes through a button. Plus inline-editable fields:

Why a shortlist, not the full menu

The temptation in L8 is to enter every single dish on the operator's printed menu. The widget's hard max of 12 nudges away from that. Reasons:

Where it ships

L14's generator reads the same dishes array and renders menu.html in the operator's palette.

Source

/tools/_shared/workshop/menu-builder.js — ~290 LOC. Exports { tag, contextKeys, mount, serialize } per the Workshop Kit widget contract. Reads min/max caps, hydrates from saved state (or seeds with three empty rows on first mount), wires per-row event handlers for name/price edits + reorder buttons + remove, manages the Add button's disabled state, and announces every action via the polite live region.

Focus management on reorder: after pressing up/down, focus stays on the same direction button (not the row's first input). This matches drag-rank's pattern — operators who want to keep moving an item can keep pressing without re-tabbing.

← Back to the Workshop Kit