Inputs
Input components return the current value so you can use it immediately in the same run:
const count = br.numberInput({ label: 'Count', defaultValue: 5 });
br.write({ body: `You picked ${count}.` });
When the user changes the value, Backroad re-runs your script with the new value bound to the same call site.
Buttons
Returns true exactly on the run after the click.
if (br.button({ label: 'Compute' })) {
// runs once after the user clicks
}
Download button
Triggers a file download in the browser when clicked. data is a function that
produces the file contents; it's called only when the user clicks — never on
the runs where the button is just sitting there — so an expensive payload costs
nothing until it's actually wanted. The result also stays on the server and is
streamed on demand, so it never rides along in the component tree on every
rerun. Like button, it returns true exactly on the run after the click.
const downloaded = br.downloadButton({
label: 'Download report',
data: () => JSON.stringify({ status: 'ok' }, null, 2),
filename: 'backroad-report.json',
});
if (downloaded) {
// runs once after the user clicks
br.write({ body: 'Report downloaded!' });
}
filename defaults to "download", and mime is inferred from the filename
extension (falling back to application/octet-stream) — so for common types you
can omit it.
data may be async, and may return raw bytes (a Uint8Array or Buffer) for
binary formats like images, PDFs, or zips:
br.downloadButton({
label: 'Export PDF',
filename: 'report.pdf', // mime inferred as application/pdf
data: async () => await renderPdf(), // returns a Buffer
});
Text input
const name = br.textInput({
label: 'Name',
placeholder: 'Type here',
defaultValue: '',
});
Number input
const age = br.numberInput({
label: 'Age',
min: 0,
max: 120,
step: 1,
precision: 0,
defaultValue: 18,
});
Text area
Multi-line text. Like textInput, the value commits when the field loses
focus (so Enter inserts a newline instead of submitting).
const bio = br.textArea({
label: 'Bio',
placeholder: 'Tell us about yourself',
rows: 6,
defaultValue: '',
});
Slider
Drag (or use the arrow keys) to pick a number in a range. The value commits on release, not on every tick, so the script reruns once per change rather than once per pixel.
const volume = br.slider({
label: 'Volume',
min: 0,
max: 100,
step: 1,
defaultValue: 30,
});
Date & time
Native date and time pickers. dateInput returns an ISO YYYY-MM-DD string
and timeInput returns a 24-hour HH:mm string; both return '' when
nothing is selected.
const start = br.dateInput({
label: 'Start date',
min: '2026-01-01',
max: '2026-12-31',
defaultValue: '2026-06-15',
});
const remindAt = br.timeInput({
label: 'Reminder at',
defaultValue: '09:00',
});
Checkbox / toggle
Two flavors of the same boolean: br.checkbox renders a square check,
br.toggle renders a switch.
const agreed = br.checkbox({ label: 'I agree', defaultValue: false });
const dark = br.toggle({ label: 'Dark mode' });
Radio
const size = br.radio({
label: 'Shirt size',
options: ['S', 'M', 'L', 'XL'],
defaultValue: 'M',
});
Select / multiselect
Powered by react-select under the hood — options is an array of
{ value, label }:
const fruit = br.select({
label: 'Fruit',
options: [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'cherry', label: 'Cherry' },
],
});
const toppings = br.multiselect({
label: 'Toppings',
options: [
{ value: 'cheese', label: 'Cheese' },
{ value: 'mushrooms', label: 'Mushrooms' },
{ value: 'pepperoni', label: 'Pepperoni' },
],
});
// toppings is an array of selected values
Color picker
const color = br.colorPicker({ label: 'Brand color', defaultValue: '#ff7a59' });
File upload
Returns an array of formidable.File
objects — already saved to a temp location on the server, ready to read.
const files = br.fileUpload({
label: 'Upload CSVs',
accept: { 'text/csv': ['.csv'] },
multiple: true,
});
for (const f of files) {
// f.filepath, f.originalFilename, f.mimetype, f.size …
}