Operator UI

Last updated on May 21, 2026

The operator UI is a per-phase component set the runtime renders to the operator. Inputs collect data and block the phase until the operator submits, while displays stream values from Python in real time. The same declaration renders identically in CLI and on a factory Station, so what you build on your laptop is what an operator sees on the floor.

Anatomy

A phase declares its UI under a ui block, and that block holds a list of components plus a few top-level fields.

main:
  - name: Scan Unit
    python: phases.scan_unit
    ui:
      components:
        - key: serial_number
          type: text_input
          label: "Serial Number"
          bind: unit.serial_number
        - key: live_voltage
          type: text
          label: "Live Voltage"
          size: xl
FieldDescription
componentsList of UI components.
requires_inputForce a Continue button even when no input components exist. Auto-detected from input components.

Component fields

Every component supports the base fields below, so you can lean on them before reaching for type-specific knobs.

FieldDescription
keyUnique identifier. Required. Used as the Python attribute on ui and as the form field name.
typeComponent type. See catalog.
labelLabel rendered above the component.
descriptionHelper text rendered below the label.
requiredWhether the operator must provide a value. Inputs only. Defaults to true.
default_valueInitial value. Type matches the component's value type.
bindBind the value to measurements.<key> or unit.<field>. Inputs only.

Type-specific fields are documented in each component section below.

Catalog

Inputs block phase completion until the operator submits a value that passes validation, while displays update in real time when Python assigns ui.<key> = value.

TypeCategoryValue type
text_inputInputstring
textareaInputstring
number_inputInputnumber
sliderInputnumber
switchInputboolean
radioInputstring
selectInputstring
checklistInputstring[]
multiselectInputstring[]
image_choiceInputstring
image_checklistInputstring[]
textDisplaystring
imageDisplaystring (URL or data URI)
progressDisplaynumber

Inputs

text_input

A single-line text field with optional validation. The value type is string.

FieldDescription
placeholderHint shown when empty.
min_lengthMinimum character count.
max_lengthMaximum character count.
patternRegex string the value must match.
prefixInline label rendered before the input (e.g. SN-).
suffixInline label rendered after the input (e.g. mm).
trimTrim leading/trailing whitespace before submit. Defaults to true.
- key: serial_number
  type: text_input
  label: "Serial Number"
  pattern: "^SN-[0-9]{8}$"
  required: true

textarea

A multi-line text field with the same constraints as text_input plus a rows field for visible height.

FieldDescription
placeholderHint shown when empty.
min_lengthMinimum character count.
max_lengthMaximum character count.
patternRegex string the value must match.
rowsVisible row count.
trimTrim leading/trailing whitespace. Defaults to true.
- key: notes
  type: textarea
  label: "Operator Notes"
  rows: 4
  max_length: 500
  required: false

number_input

A numeric field with stepper buttons. The value type is number.

FieldDescription
minMinimum value.
maxMaximum value.
stepIncrement for the stepper buttons. Defaults to 1. Drives decimal precision.
placeholderHint shown when empty.
prefixInline label rendered before the input.
suffixInline label rendered after the input (e.g. V, mm).

The operator must enter a number between min and max inclusive and a multiple of step, because the field validates on submit.

- key: target_voltage
  type: number_input
  label: "Target Voltage"
  min: 0
  max: 12
  step: 0.1
  suffix: "V"

slider

A numeric input with a draggable handle and visible min/max labels. The value type is number.

FieldDescription
minMinimum value. Defaults to 0.
maxMaximum value. Defaults to 100.
stepIncrement. Defaults to 1.
- key: torque
  type: slider
  label: "Torque"
  min: 0
  max: 10
  step: 0.5

switch

A boolean toggle. The value type is boolean, and when required: true the operator must explicitly flip the switch because the initial unset state fails validation.

FieldDescription
default_valuetrue or false.
optionsOptional [{label, value}, {label, value}] to override the on/off labels (defaults to "Yes"/"No").
- key: cover_closed
  type: switch
  label: "Cover Closed"
  required: true

radio

A single selection from a vertical list. The value type is string, and it requires options.

FieldDescription
optionsList of {label, value} pairs.
- key: outcome
  type: radio
  label: "Visual Inspection"
  required: true
  options:
    - { label: "Pass", value: pass }
    - { label: "Cosmetic defect", value: cosmetic }
    - { label: "Fail", value: fail }

select

A single selection from a dropdown. The value type is string, and it requires options.

FieldDescription
optionsList of {label, value} pairs.
placeholderText shown before a value is selected. Defaults to "Select...".
- key: line
  type: select
  label: "Production Line"
  placeholder: "Choose a line"
  options:
    - { label: "Line A", value: a }
    - { label: "Line B", value: b }

checklist

Multiple selections rendered as checkboxes. The value type is string[], and it requires options.

FieldDescription
optionsList of {label, value} pairs.

When required: true, the operator must select at least one item.

- key: pre_flight
  type: checklist
  label: "Pre-flight Checks"
  required: true
  options:
    - { label: "Power supply connected", value: power }
    - { label: "USB cable seated", value: usb }
    - { label: "Antenna installed", value: antenna }

multiselect

Multiple selections from a dropdown. The value type is string[], it requires options, and you reach for it over checklist when you have many options or limited vertical space.

FieldDescription
optionsList of {label, value} pairs.
placeholderText shown before any value is selected.
- key: defects
  type: multiselect
  label: "Observed Defects"
  required: false
  options:
    - { label: "Scratch", value: scratch }
    - { label: "Dent", value: dent }
    - { label: "Missing label", value: label }

image_choice

A single selection from a grid of image cards. The value type is string, and each option carries an image field that accepts a relative path or a data URI.

FieldDescription
optionsList of {label, value, image} items.
columnsGrid column count. Auto-sized when omitted (<=4 → 2, <=9 → 3, else 4).
aspectCard aspect ratio. Defaults to 1 (square). Accepts "auto", "square", or a ratio like "16/9".
fitImage object-fit mode: cover, contain (default), or fill.
width / heightPer-card sizing.
- key: cable_type
  type: image_choice
  label: "Cable Type"
  required: true
  options:
    - { label: "FFC", value: ffc, image: "./images/cable-ffc.jpg" }
    - { label: "Ribbon", value: ribbon, image: "./images/cable-ribbon.jpg" }
    - { label: "Harness", value: harness, image: "./images/cable-harness.jpg" }

image_checklist

Multiple selections from a grid of image cards. The fields match image_choice, and the value type is string[].

- key: defects
  type: image_checklist
  label: "Tick all defects you see"
  required: false
  options:
    - { label: "Scratch", value: scratch, image: "./images/scratch.svg" }
    - { label: "Dent", value: dent, image: "./images/dent.svg" }
    - { label: "Looks OK", value: ok, image: "./images/ok.svg" }

Displays

Displays do not gate phase completion, so you assign ui.<key> = value in the phase function to update them live.

text

Read-only text where the value type is string, and updates re-render immediately on the operator screen.

FieldDescription
sizexs, sm, base (default), lg, xl, 2xl, 3xl, 4xl.
colordefault, zinc, red, orange, yellow, green, lime, sky, blue, violet, purple, pink.
fontdefault (sans) or monospace.
- key: live_voltage
  type: text
  label: "Live Voltage"
  size: 3xl
  color: lime
  font: monospace
phases/measure.py
def measure(ui, multimeter):
    ui.live_voltage = f"{multimeter.read():.2f} V"

image

A static or dynamic image where the value type is string (relative path or data URI). Assign a new path or data URI from Python to swap the image in place.

FieldDescription
widthCSS width (e.g. "80%", "320px"). Defaults to 100%.
heightCSS height. Percentages map to viewport height.
aspect"auto", "square", or a ratio like "16/9".
fitcover (default), contain, fill.
- key: reference
  type: image
  default_value: "./images/reference.png"
  width: "80%"
  aspect: "16/9"
  fit: contain

progress

A progress bar where the value type is number, which is useful for long-running phases like firmware flashing where you want the operator to see steady forward motion.

FieldDescription
minLower bound. Defaults to 0.
maxUpper bound. Defaults to 100.
default_valueInitial value.
phases/flash.py
def flash_firmware(ui, programmer):
    for percent in programmer.flash():
        ui.progress = percent

Binding

Inputs can bind their value to a Measurement or a unit field, so bound values appear on the Run automatically and you do not need to assign them from Python.

- key: voltage
  type: number_input
  bind: measurements.voltage
- key: serial
  type: text_input
  bind: unit.serial_number

Bind paths: measurements.<measurement_key> or unit.<serial_number|part_number|revision_number|batch_number>.

Require continue

Phases with only display components auto-continue once their Python body returns, so you set requires_input when you want to force a Continue button anyway (for example, when the operator has to read a briefing before moving on).

ui:
  requires_input: true
  components:
    - key: briefing
      type: text
      label: "Read before continuing"
      default_value: "Disconnect power before opening the enclosure."

How is this guide?

On this page