Skip to content

Widgets & @bind

Controls in KaimonSlate are real Julia constructors, not a static macro DSL. @bind name Widget(...) binds a reactive variable name to a widget; because the widget is an ordinary constructor call, its arguments can be dynamic.

julia
@bind hi Slider(10:10:200)
@bind n  Slider(1:hi)          # range depends on another binding — updates live

Read the bound variable from any other cell and it re-runs when the control changes:

julia
using CairoMakie; set_theme!(theme_dark())
lines(1:n, sin.(range(0, , n)))

Widget catalog

ConstructorRendersValue
Slider(range) / Slider(lo, hi; step)range slidernumber
NumberField(default; min, max)number inputnumber
Checkbox(default) / Toggle(default)checkbox / switchBool
TextField(default) / TextArea(default; rows)text inputString
Select(options) / Radio(options)dropdown / radio groupchosen option
MultiSelect(options)multi-selectVector
ColorPicker(default)color pickerhex String
DateField() / TimeField()date / time pickerString
Button(label)action buttonclick count
TableSelect(data)clickable tableclicked row as a NamedTuple (or nothing)
playhead(anim)animation player (driven)current frame index

Button pairs with @onclick to run an action on click. TableSelect renders any slate_table-compatible data and binds the row you click; playhead is a driven control that receives an animation's current frame so another cell can react to playback.

All accept a label keyword. The value reconciles across re-runs: re-running a bind cell updates the widget's range/options but keeps the user's current value (unless its type or domain changed).

Insert a control fast

Press ⌘K and type "bind" to insert any of these as a snippet — at the cursor of the selected code cell, or into a fresh cell.

Each control as it renders in a cell.

Slider

Slider(0:100; default = 42, label = "samples")

NumberField

NumberField(0, 100, 12; label = "count")

Checkbox

Checkbox(true; label = "I agree")

Toggle

Toggle(true; label = "stream", on = "Live", off = "Paused")

TextField

TextField("Ada"; label = "name")

TextArea

TextArea("…"; label = "notes")

Select

Select(["red", "green", "blue"], "green"; label = "color") — the default is the second positional argument (as with Radio/MultiSelect); label is a keyword.

Radio

Radio(["S", "M", "L"], "M"; label = "size")

MultiSelect

MultiSelect(["x", "y", "z"], ["x", "z"]; label = "tags")

MultiCheckBox

MultiCheckBox(["a", "b", "c"], ["b"]; label = "flags")

ColorPicker

ColorPicker("#56d364"; label = "tint")

DateField

DateField("2026-06-05"; label = "date")

TimeField

TimeField("09:30"; label = "time")

Button

Button("Run")

Mixed cells

A cell can declare binds and run code. The control(s) render at the top and the cell's output below — useful for a self-contained "control + plot" unit.

julia
@bind freq Slider(1:20)
using CairoMakie; set_theme!(theme_dark())
lines(0:0.01:1, sin.(*freq .* (0:0.01:1)))

Control strips and the palette

A bound control can be surfaced in another cell's control strip — drag its grip into a code cell to place it near the output it drives (it can live in several cells at once; the variable stays single-sourced). Arrange controls into columns by dropping between them.

Open the 🎛 Controls palette (top bar or ⌘K) to see every @bind declared across the notebook, its live value, and where it's surfaced. Click a chip to jump to its defining cell; drag a chip into a cell to surface it; drop it back on the palette to remove it.

How it stays in sync

Changing a control posts the new value to its defining cell, which restales and recomputes the readers (see Reactive Cells). While you drag, updates are rate-limited and coalesced so the kernel isn't flooded; releasing flushes the final value. Every widget bound to the same variable — strip copies included — stays in lockstep.

Custom widgets

Not enough with the built-ins? You can register your own widget type in JavaScript and bind it with @bind x custom_widget("kind") — see Front-end Extensions.