Instruments
Per-device controls mounted into the cream-paper widget bar. Each render below is the actual <lab-widget-*> custom element from js/widgets/ — refactor a widget and this page updates automatically. Every device widget is grouped by control archetype. Use the index below to jump straight to one.
2 · Electronic scale · simplest readout + button
Electronic scale
<lab-widget-scale> · js/widgets/scale/ScaleWidget.js
The minimum viable widget: a label, a value with unit, and one CTA. Establishes the cadence used by every readout-style widget — Space Grotesk 22 px value, mono Plex 11 px unit hugging the descender.
3 · Spring scale · horizontal segmented gauge
Spring scale
<lab-widget-spring-scale> · js/widgets/spring-scale/SpringScaleWidget.js
Adds a horizontal pill-shaped track with green fill and per-newton ticks alongside the readout. The tick rule (filled ticks turn semi-transparent white as the fill passes them) is shared with several other gauges.
4 · Hot plate · ON/OFF + segmented heat level
Hot plate
<lab-widget-hot-plate> · js/widgets/hot-plate/HotPlateWidget.js
Combines a binary on/off button (left) with a segmented intensity selector (Low / Med / High) and a power-state indicator dot. The active state is conveyed two ways — black-stamp button + glowing red dot. Canonical pattern for "device with intensity levels."
5 · Colored flashlight · independent multi-toggle + status dots
Colored flashlight
<lab-widget-colored-flashlight> · js/widgets/colored-flashlight/ColoredFlashlightWidget.js
Three independently-toggleable channels (R, G, B) plus a master ON/OFF and live status dots. Demonstrates non-mutually-exclusive toggle buttons inside the widget bar. The neighbouring color-dot indicators glow when the corresponding channel is lit.
6 · Gauge · draggable thumb with sweet-spot region
Gauge
<lab-widget-gauge> · js/widgets/gauge/GaugeWidget.js
Continuous value with a draggable thumb on a 5 px track. A green "sweet spot" region marks the target band — the student aims to land the thumb inside it. Pointer events are captured directly by the widget; no separate confirm.
7 · Slider · range input + commit button
Slider (generic)
<lab-widget-slider> · js/widgets/slider/SliderWidget.js
Native <input type="range"> styled with the system thumb (green disk + 3 px hard shadow), plus a Space Grotesk readout and an OK button to commit. Used for any continuous parameter where the student should preview before submitting.
8 · Microscope · multi-axis controls
Microscope
<lab-widget-microscope> · js/widgets/microscope/MicroscopeWidget.js
The most control-dense widget. Three independent axes: objective lens (mutually exclusive 4x / 10x / 40x), focus pair (Coarse + Fine, both toggleable), and Diaphragm (single toggle). Demonstrates how to fit several toggle groups + standalone toggles into one bar without crowding.
9 · Cart launcher · linear state machine
Cart launcher
<lab-widget-cart-launcher> · js/widgets/cart-launcher/CartLauncherWidget.js
Three-state machine (placed → armed → fired) with a state readout in the bar. Buttons disable when their action is invalid for the current state. Same pattern other state-machine widgets follow (microwave, stopwatch, generators).
10 · Pour · the only floating widget (vertical)
Pour
<lab-widget-pour> · js/widgets/pour/PourWidget.js
The system's exception: a free-floating, vertical column rather than a yellow widget bar. Pinned in screen-space above the active liquid container, it shows a fill-track (color-coded by liquid temperature: blue / red / cyan / done-green), volume in mL above, and a status string below. Sets the visual vocabulary for the next round of "ambient" widgets.
11 · Thermometer · readout family
Thermometer
<lab-widget-thermometer> · js/widgets/thermometer/ThermometerWidget.js
ON/OFF probe with a Space Grotesk °C readout. The pilot of the readout-style family; every "value + unit" widget below shares its display rules.
13 · Ammeter · readout + unit toggle
Ammeter
<lab-widget-ammeter> · js/widgets/ammeter/AmmeterWidget.js
ON/OFF + segmented mA / A unit selector. Establishes the "readout with a unit toggle" pattern shared by graduated cylinder, ruler, and others.
14 · Light meter · readout + range cycle
Light meter
<lab-widget-light-meter> · js/widgets/light-meter/LightMeterWidget.js
Lux readout with a cyclic range button (auto / 2K / 20K / 200K). Same chrome as the ammeter but with a step-through control instead of a binary toggle.
15 · Humidity sensor · dual-readout
Humidity sensor
<lab-widget-humidity-sensor> · js/widgets/humidity-sensor/HumiditySensorWidget.js
Two stacked readouts in one bar: %RH primary, °C secondary. Pattern for any device that reports two correlated values.
16 · CO₂ sensor · readout + stabilizing pulse
CO₂ sensor
<lab-widget-co2-sensor> · js/widgets/co2-sensor/Co2SensorWidget.js
ppm readout with a stabilizing-state visual: the value blinks while warming up, settles when stable. First widget to model a transient sub-state inside an "on" mode.
17 · Graduated cylinder · volume readout + unit
Graduated cylinder
<lab-widget-graduated-cylinder> · js/widgets/graduated-cylinder/GraduatedCylinderWidget.js
Volume in mL or L, toggleable. Always-on (no power button) — the cylinder is a passive measurement device.
18 · Ruler · distance readout + unit
Ruler
<lab-widget-ruler> · js/widgets/ruler/RulerWidget.js
Distance with cm / mm toggle. Shares unit-cycle chrome with graduated cylinder — same family, different domain.
19 · Force sensor · readout + record + zero + peak
Force sensor
<lab-widget-force-sensor> · js/widgets/force-sensor/ForceSensorWidget.js
The control-rich corner of the readout family: live N value, peak captured below it, plus Zero / Start-Stop recording buttons. Pattern for any tool that records over time.
19b · Crater depth · passive readout, no controls
Crater depth
<lab-widget-crater-depth> · js/widgets/crater-depth/CraterDepthWidget.js
The measure_depth readout: a label and a centimetre value, no button — depth is geometric, there is no sensor to arm. The value arrives as depth_m (metres) on the telemetry bus keyed to the depth stick; the element renders cm to one decimal, and "--" before a crater exists.
20 · Flashlight · ON/OFF + beam-width toggle
Flashlight
<lab-widget-flashlight> · js/widgets/flashlight/FlashlightWidget.js
Single-channel light: on/off + a wide / narrow beam selector. The "binary toggle plus modal sub-control" template every directional emitter follows.
21 · Laser pointer · pure binary toggle
Laser pointer
<lab-widget-laser-pointer> · js/widgets/laser-pointer/LaserPointerWidget.js
The minimal toggle widget: one button, two states. Reference for every device whose only choice is on or off.
22 · Kitchen lighter · momentary state group
Kitchen lighter
<lab-widget-kitchen-lighter> · js/widgets/kitchen-lighter/KitchenLighterWidget.js
Strike → lit → extinguish. Looks like a toggle but the third state (extinguished) is reachable only from "lit", giving a small directional state graph in a single button group.
23 · Fan · ON/OFF + speed segmented
Fan
<lab-widget-fan> · js/widgets/fan/FanWidget.js
Same shape as hot plate: power button + a Low / High segmented selector. Different domain, identical chrome.
24 · Hair dryer · ON/OFF + speed segmented
Hair dryer
<lab-widget-hair-dryer> · js/widgets/hair-dryer/HairDryerWidget.js
Variant of the fan: same controls, different label. Confirms that copies of an archetype don't need a new visual primitive.
25 · Toggle · two-sided binary on/off
Toggle
<lab-widget-toggle> · js/widgets/toggle/ToggleWidget.js
Generic discrete on/off control: a two-sided switch showing both positions at once — circle for off on the left, line for on at the right (IEC 60417) — with a highlight that travels between them and changes colour as it goes: dark ink on the off side, green on the on side, following the base sheet's .off / .active stamp semantics, so the colour reports the device state rather than marking which side is selected. Both marks are drawn as inline SVG on a shared grid, not set as text: as characters the O reads as a digit zero and the control as a numeric readout. It is one button rather than two halves, so clicking anywhere flips it and there is no dead side to press; the rocker switch below keeps two targets because it authors a named state rather than flipping. Bound to a boolean object property, e.g. a circuit switch's closed state.
The flip is usually the last thing a step asks for, and the handler unmounts the control as soon as it commits — so the element moves the highlight first and emits toggle only once the slide has landed (240 ms travel, 20 ms settle). The step advances after the student has seen the new position, not before it renders. Presses during that window are ignored, but the control is never disabled. Under prefers-reduced-motion the travel is dropped and the dwell kept.
26 · Rocker switch · open / closed circuit
Rocker switch
<lab-widget-rocker-switch> · js/widgets/rocker-switch/RockerSwitchWidget.js
Domain-specific binary toggle for circuit work — labelled "open" / "closed" instead of "on" / "off". Preserves the pattern but speaks the right vocabulary.
27 · Tumble buggy · ON/OFF + direction
Tumble buggy
<lab-widget-tumble-buggy> · js/widgets/tumble-buggy/TumbleBuggyWidget.js
Toggle plus a forward / reverse selector — the "device with a directional axis" variant of the fan/hot-plate shape.
28 · Speaker · play / stop + animated wave
Speaker
<lab-widget-speaker> · js/widgets/speaker/SpeakerWidget.js
Binary toggle decorated with animated wave bars while playing. Sets the visual rule for "audio currently emitting" used by the rest of the sound family.
29 · Tuning fork · strike / dampen + vibrate
Tuning fork
<lab-widget-tuning-fork> · js/widgets/tuning-fork/TuningForkWidget.js
Strike action + a "vibrating" indicator that decays over time. Same toggle shape as the speaker but with momentary semantics.
30 · Drum · strike / dampen
Drum
<lab-widget-drum> · js/widgets/drum/DrumWidget.js
Same momentary-toggle shape as the tuning fork, with a "struck" indicator instead of "vibrating". Both widgets share their attack/decay rules.
31 · pH strip · color swatch + numeric
pH strip
<lab-widget-ph-strip> · js/widgets/ph-strip/PhStripWidget.js
No yellow chrome — just a coloured strip plus a numeric pH. The undipped state shows the strip blank; once dipped, the colour and number lock in.
32 · BTB indicator · color swatch + label
BTB indicator
<lab-widget-btb-indicator> · js/widgets/btb-indicator/BtbIndicatorWidget.js
Coloured chip + categorical label (Acidic / Neutral / Basic). Demonstrates a colour-driven readout where the value is qualitative, not numeric.
33 · LED · bi-color status indicator
LED (bi-color)
<lab-widget-led> · js/widgets/led/LedWidget.js
Three-state indicator (off / green / red). No chrome around it — drops directly onto the bench as a status light.
34 · Microwave · door state machine + countdown
Microwave
<lab-widget-microwave> · js/widgets/microwave/MicrowaveWidget.js
Four-state machine (door open → closed → running → stopped) with a digit timer. Adds a countdown readout to the cart-launcher pattern.
35 · Stopwatch · self-ticking start / stop / reset
Stopwatch
<lab-widget-stopwatch> · js/widgets/stopwatch/StopwatchWidget.js
Owns its own clock — increments live in the second tile. Same Space Grotesk tabular display rule as the time control bar, but mounted as a per-step measurement tool.
36 · Hand-crank generator · multi-state + signed readouts
Hand-crank generator
<lab-widget-generator> · js/widgets/generator/GeneratorWidget.js
Stop / slow / fast / intermittent / reverse with a paired V + mA readout. The fullest example of how a state-machine widget adds quantitative outputs alongside its mode buttons.
37 · Homemade generator · slower variant + LEDs
Homemade generator
<lab-widget-homemade-generator> · js/widgets/homemade-generator/HomemadeGeneratorWidget.js
Sister widget to the hand-crank generator: same state machine, but the readout is replaced by two indicator LEDs for a more qualitative student experience.
38 · Snapping track · discrete-snap rotary
Snapping track
<lab-widget-snapping-track> · js/widgets/snapping-track/SnappingTrackWidget.js
Continuous-input chrome that snaps to a fixed list of positions. Same shape as the gauge but the thumb can only land on authored angles.
40 · Smart-cart track · brake state + angle readout
Smart-cart track
<lab-widget-smart-cart-track> · js/widgets/smart-cart-track/SmartCartTrackWidget.js
Pairs a numeric angle readout with a brake-release toggle: the cart only rolls when the brake is released. Demonstrates the "input + safety interlock" combination.
41 · Preset · multi-choice button group
Preset
<lab-widget-preset> · js/widgets/preset/PresetWidget.js
Generic multi-choice selector for any ui: preset step. Wraps the segmented-toggle atom with the standard yellow chrome and emits a preset-select event with the picked value.
42 · Text · authored chrome copy on the widget bar
Text
<lab-widget-text> · js/widgets/text/TextWidget.js
A short authored line on the widget bar. First deployment is olfactory description on sniff steps — a step composes a phenomenon-framed close-in with widget: text + widget_text:, the binder forwards the copy and the eyebrow label ("Smell" for v1). Inherits the standard cream + ink + hard-shadow widget-bar chrome via widgetBaseStyles; local rule adds prose typography (Space Grotesk 14 px regular, 1.3 line-height). Fixed 56 px silhouette — copy stays within two lines under the 200-char schema cap (Phase B). Control entry in the telemetry registry; no events, no bus subscription.