Inputs are UI elements that accept data from users and enable interaction via reactivity. They can be standard HTML elements like text boxes, sliders, and dropdowns, or custom elements like charts with interactive selection.
The view function is the primary way to create reactive inputs. It combines two operations:
display)input)view() works like Observable Framework's but supports JSX syntax for creating elements. Compare view(<input type="text"/>) vs Observable's view(html`<input type=text>`).
const name = view(<input type="text" placeholder="Enter your name"/>);`Hello, ${name}!`The input function automatically handles different input types with appropriate events and value extraction:
const textValue = view(<input type="text" value="Edit me"/>);`Text: "${textValue}"`Number inputs return actual numbers, not strings:
const numValue = view(<input type="number" value="42" style="width: 80px"/>);`Number: ${numValue} (type: ${typeof numValue})`Range inputs also return numbers:
const rangeValue = view(<input type="range" min="0" max="100" value="50"/>);`Range: ${rangeValue}`Checkboxes return boolean values:
const checked = view(<input type="checkbox" id="feature"/>);const color = view(<select>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>);<span style={`color: ${color}; font-weight: bold`}>Selected: {color}</span>Multi-select returns an array of selected values:
const colors = view(<select multiple style="height: 80px">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="yellow">Yellow</option>
</select>);<div style="font-weight: bold">
Selected: {colors.length ? colors.map((c, i) =>
<span>
<span style={`color: ${c}`}>{c}</span>{i < colors.length - 1 ? ', ' : ''}
</span>
) : 'none'}
</div>Date inputs return Date objects:
const dateValue = view(<input type="date" value="2025-01-01"/>);`Date: ${dateValue?.toLocaleDateString() ?? 'not set'}`const colorValue = view(<input type="color" value="#4a90d9"/>);<div style={`background: ${colorValue}; padding: 1em; color: white; text-shadow: 1px 1px 2px black`}>
Color: {colorValue}
</div>The input function converts a DOM element into an async iterator without displaying it. This is useful when you want to control where and when the element is displayed:
input(element, eventType?, getValue?) adds optional parameters for custom event types and value extraction functions beyond Observable Framework's Generators.input(element).
const myInput = <input type="text" placeholder="Type here..."/>;
const myValue = input(myInput);
By default, input listens for the appropriate event based on the input type. You can override this:
// Listen to 'change' instead of 'input' for text
input(element, 'change');
// Custom value extraction
input(element, 'input', (el) => el.value.toUpperCase());
| Input Type | Default Event | Value Returned |
|---|---|---|
| text, search, email, url, tel | input | string |
| number, range | input | number |
| date, datetime-local | input | Date |
| checkbox | click | boolean |
| file | change | File or FileList |
| select | input | string |
| select[multiple] | input | string[] |
| button, submit | click | value |
For more complex scenarios, observe lets you create custom async iterators from any event source. Pass an initial value as the second argument:
observe() adds initialValue and terminate parameters beyond Observable Framework's Generators.observe(). This eliminates the need to call notify() inside the initializer for the initial value.
const windowSize = observe((notify) => {
const handler = () => notify({
width: window.innerWidth,
height: window.innerHeight
});
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, {width: window.innerWidth, height: window.innerHeight});
`Window size: ${windowSize.width} x ${windowSize.height}`function observe<T>(
init: (notify: (value: T | undefined) => void) => (() => void) | void,
initialValue?: T,
terminate?: (value: T | undefined) => boolean
): AsyncGenerator<T>
Parameters:
notify callback to push new values. Can return a cleanup function.undefined)You can also use the @observablehq/inputs library for more sophisticated input controls:
import {range, select, checkbox} from "@observablehq/inputs";
const obsSlider = view(range([0, 100], {label: "Volume", step: 1, value: 50}));`Volume: ${obsSlider}%`Reactive values can depend on multiple inputs. The dependent code re-runs whenever any input changes:
const w = view(<input type="range" min="50" max="300" value="150"/>);const h = view(<input type="range" min="50" max="200" value="100"/>);const boxColor = view(<input type="color" value="#4a90d9"/>);<div style={`width: ${w}px; height: ${h}px; background: ${boxColor};
display: flex; align-items: center; justify-content: center; color: white;
text-shadow: 1px 1px 2px black`}>
{w} x {h}
</div>