Skip to content

Input Widgets

@termuijs/widgets includes focusable input widgets for collecting numeric values, PIN codes, and dates in terminal UIs. Each widget handles its own keyboard controls and calls a callback when its value changes.

All input widgets extend Widget and are focusable by default. Attach them to a layout, give them focus with your focus manager, and they handle the rest.


Numeric inputs

RangeInput

A dual-handle slider for selecting a numeric range with a low and high value. The track renders between the two handles. Press Tab to switch the active handle.

·CODE
import { RangeInput } from '@termuijs/widgets'

const range = new RangeInput('Price', { width: 40 }, {
    min: 0,
    max: 500,
    step: 10,
    showValue: true,
    onChange: (low, high) => {
        console.log(`Range: ${low} – ${high}`)
    },
})

Constructor

·CODE
new RangeInput(label: string, style?: Partial<Style>, opts?: RangeInputOptions)

RangeInputOptions

PropTypeDefaultDescription
minnumber0Minimum value
maxnumber100Maximum value
stepnumber1Amount to increment or decrement per keypress
colorColorcyanTrack fill color
showValuebooleantrueDisplay low – high values at the end of the track
onChange(low: number, high: number) => void,Called when either handle moves

Methods

MethodDescription
getLow(): numberCurrent low handle value
getHigh(): numberCurrent high handle value
setLow(value: number): voidSet the low handle; clamped to [min, high]
setHigh(value: number): voidSet the high handle; clamped to [low, max]
setRange(low: number, high: number): voidSet both handles at once

Keyboard controls

KeyAction
TabSwitch active handle between low and high
RightIncrease active handle by step
LeftDecrease active handle by step

Knob

A circular knob that sweeps 270 degrees from bottom-left to bottom-right. The current value renders in the center of the knob when there is enough room. The knob is highlighted when focused.

·CODE
import { Knob } from '@termuijs/widgets'

const volume = new Knob('Volume', { width: 9, height: 5 }, {
    min: 0,
    max: 100,
    step: 5,
    showValue: true,
    onChange: (value) => {
        console.log('volume:', value)
    },
})

Constructor

·CODE
new Knob(label?: string, style?: Partial<Style>, opts?: KnobOptions)

KnobOptions

PropTypeDefaultDescription
minnumber0Minimum value
maxnumber100Maximum value (clamped to >= min)
stepnumber1Amount to change per keypress (must be positive)
colorColorcyanArc fill color when not focused
showValuebooleantrueRender the numeric value in the center of the knob
onChange(value: number) => void,Called whenever the value changes

Methods

MethodDescription
value: number (getter)Current knob value
setValue(value: number): voidSet the value; clamped to [min, max]

Keyboard controls

KeyAction
Up or RightIncrease by step
Down or LeftDecrease by step

Sizing tip, the knob fills the widget's bounding box. Give it equal or close width and height for a round shape. A minimum of 4 × 3 is needed to show the center value.


Text inputs

PinInput

A fixed-length PIN entry field. Each character occupies a labeled block ([ x ]). The active block is highlighted with inverse colors. The onComplete callback fires when every slot is filled.

·CODE
import { PinInput } from '@termuijs/widgets'

const pin = new PinInput({}, {
    length: 6,
    masked: true,
    onChange: (value) => {
        console.log('current:', value)
    },
    onComplete: (value) => {
        console.log('PIN entered:', value)
    },
})

Constructor

·CODE
new PinInput(style?: Partial<Style>, opts?: PinInputOptions)

PinInputOptions

PropTypeDefaultDescription
lengthnumber4Number of character slots
maskedbooleanfalseReplace visible characters with (or * on ASCII-only terminals)
onChange(value: string) => void,Called after each character change with the current partial value
onComplete(value: string) => void,Called when all slots are filled

Properties

PropertyTypeDescription
valuestring (getter)Current input value joined from all slots

Keyboard controls

KeyAction
Any printable characterFill the current slot and advance the cursor
RightMove cursor right
LeftMove cursor left
Backspace or DeleteClear the current slot; if already empty, move left and clear

Each slot renders as 6 columns ([ x ] ), so set the widget width to at least length × 6.


Form composition

@termuijs/widgets does not ship a dedicated Form widget. Use the numeric and text widgets above together with layout primitives from @termuijs/widgets and focus management from @termuijs/ui to build form-like interfaces. For a date field, @termuijs/widgets exports a Calendar widget with keyboard date selection.

A typical pattern for a multi-field form:

·CODE
import { PinInput, RangeInput } from '@termuijs/widgets'
import { render, useState, useKeymap } from '@termuijs/jsx'

function SettingsForm() {
    const [pinValue, setPinValue] = useState('')
    const [low, setLow] = useState(0)
    const [high, setHigh] = useState(100)

    return (
        <box flexDirection="column" gap={1} padding={1}>
            <text bold>Configure</text>

            <box flexDirection="column">
                <text>Access PIN</text>
                <PinInput
                    opts={{
                        length: 4,
                        masked: true,
                        onComplete: (v) => setPinValue(v),
                    }}
                />
            </box>

            <box flexDirection="column">
                <text>Allowed range</text>
                <RangeInput
                    label="Range"
                    opts={{
                        min: 0,
                        max: 100,
                        onChange: (l, h) => { setLow(l); setHigh(h) },
                    }}
                />
            </box>
        </box>
    )
}

For the form-wizard starter (multi-step forms), scaffold with:

·CODE
npx create-termui-app my-app --template form-wizard

This generates a working Wizard component with step navigation built in.