Observable Framework 1.5.1 GitHub️ 1.8k

Button input

API · Source · The button input emits an input event when you click it. Buttons may be used to trigger the evaluation of cells, say to restart an animation. For example, below is an animation that progressively hides a bar. Clicking the button will restart the animation.

const replay = view(Inputs.button("Replay"));

The code block below references replay, so it will run automatically whenever the replay button is clicked.

replay; // run this block when the button is clicked
const progress = (function* () {
  for (let i = canvas.width; i >= 0; --i) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.fillRect(0, 0, i, canvas.height);
    yield canvas;
The progress top-level variable is declared as a generator. This causes the Observable runtime to automatically advance the generator on each animation frame. If you prefer, you could write this animation using a standard requestAnimationFrame loop, but generators are nice because the animation will automatically be interrupted when the code is invalidated.

You can also use buttons to count clicks. While the value of a button is often not needed, it defaults to zero and is incremented each time the button is clicked.

const clicks = view(Inputs.button("Click me"));

You have clicked times.

You have clicked ${clicks} times.

While buttons count clicks by default, you can change the behavior by specifying the value and reduce options: value is the initial value, and reduce is called whenever the button is clicked, being passed the current value and returning the new value. The value of the button below is the last time the button was clicked, or null if the button has not been clicked.

const time = view(Inputs.button("Update", {value: null, reduce: () => new Date}));

Note that even if the value of the button doesn’t change, it will still trigger any cells that reference the button’s value to run. (The Observable runtime listens for input events on the view, and doesn’t check whether the value of the view has changed.)

For multiple buttons, pass an array of [content, reduce] tuples. For example, to have a counter that can be incremented, decremented, and reset:

const counter = view(Inputs.button([
  ["Increment", value => value + 1],
  ["Decrement", value => value - 1],
  ["Reset", value => 0]
], {value: 0, label: "Counter"}));

The first argument to Inputs.button() is the contents of the button. It’s not required, but is strongly encouraged.

const x = view(Inputs.button());

The contents of the button input can be an HTML element if desired, say for control over typography.

const y = view(Inputs.button(html`<i>Fancy</i>`));

Like other basic inputs, buttons can have an optional label, which can also be either a text string or an HTML element.

const confirm = view(Inputs.button("OK", {label: "Continue?"}));

You can change the rendered text in Markdown based on whether a button is clicked. Try clicking the OK button with the Continue? label.

confirm ? "Confirmed!" : "Awaiting confirmation..."

You can also use a button to copy something to the clipboard.

Inputs.button("Copy to clipboard", {value: null, reduce: () => navigator.clipboard.writeText(time)})


Inputs.button(content, options)

The available button input options are: