Task Button

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 200

import time

from shiny import App, reactive, render, ui

app_ui = ui.page_fluid(
    ui.row(
        ui.column(6, ui.input_task_button("task_button", "Run task")),
        ui.column(6, ui.output_code("result")),
        {"class": "vh-100 justify-content-center align-items-center px-5"},
    ).add_class("text-center")
)

def server(input, output, session):
    @render.code
    @reactive.event(input.task_button)
    def result():
        time.sleep(1.5)  # Simulate a long-running task
        return f"Ran {input.task_button()} time(s)"

app = App(app_ui, server)
import time

from shiny import reactive, render
from shiny.express import input, ui

ui.input_task_button("task_button", "Run task")  

@render.code
@reactive.event(input.task_button)
def result():
    time.sleep(2)  # Simulate a long-running task
    return f"Task completed {input.task_button()} time(s)"
import time

from shiny import App, reactive, render, ui

app_ui = ui.page_fluid(
    ui.input_task_button("task_button", "Run task"),  
    ui.output_code("result"),
)

def server(input, output, session):
    @render.code
    @reactive.event(input.task_button)
    def result():
        time.sleep(2)  # Simulate a long-running task
        return f"Task completed {input.task_button()} time(s)"

app = App(app_ui, server)
No matching items

Relevant Functions

  • ui.input_task_button
    ui.input_task_button(id, label, *args, icon=None, label_busy='Processing...', icon_busy=MISSING, width=None, type='primary', auto_reset=True, **kwargs)

  • ui.update_task_button
    ui.update_task_button(id, *, state=None, session=None)

  • ui.bind_task_button
    ui.bind_task_button(task=None, *, button_id)

No matching items

Details

A task button is similar to an action button, but it is designed for triggering operations that take a while to complete. When clicked, the button immediately disables itself and displays a busy message ("Processing..." by default) so the user cannot re-submit the task while it is still running. Once the server finishes handling the click, the button automatically resets to its ready state.

Follow these steps to add a task button to your app:

  1. Add ui.input_task_button() to the UI of your app to create a task button. Where you call this function will determine where the button will appear within the app’s layout.

  2. Specify the id and label parameters of ui.input_task_button() to define the button’s identifier and the label shown while it is ready to be clicked.

  3. Optionally, set label_busy to customize the message shown while the button is busy, or pass auto_reset=False to keep the button busy until you reset it yourself with ui.update_task_button() (see the variation below).

The value of a task button is accessible as a reactive value within the server() function. To access the value of a task button:

  1. Use input.<task_button_id>() (e.g., input.task_button()) to access the value of the task button. The server value of a task button is an integer representing the number of clicks.

Long-running tasks

A task button on its own only manages the button’s appearance: it stays busy for the duration of the click’s reactive flush, then resets. It does not make the work itself non-blocking. Shiny runs reactive code serially, so slow work inside a render function or effect holds up every other output — and every other session served by the same process. Making that function async and awaiting does not change this; await asyncio.sleep(3) blocks exactly as much as time.sleep(3).

To actually run work in the background, move it out of the reactive graph with reactive.extended_task and link it to the button with ui.bind_task_button(). The button then stays busy for as long as the task runs, and the task becomes cancellable. See the “True non-blocking work” variation below for a complete app, and Non-blocking operations for the full guide.

Variations

Manually reset the button with update_task_button

Pass auto_reset=False to ui.input_task_button() to keep the button in its busy state after the click has been handled, then call ui.update_task_button(id, state="ready") from the server to reset it yourself. Click “Run task”, wait for the result, and note the button stays busy until you click “Reset task button”.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 200

import time

from shiny import App, reactive, render, ui

app_ui = ui.page_fluid(
    ui.row(
        ui.column(4, ui.input_task_button("run", "Run task", auto_reset=False)),
        ui.column(4, ui.input_action_button("reset", "Reset task button")),
        ui.column(4, ui.output_code("result")),
        {"class": "vh-100 justify-content-center align-items-center px-5"},
    ).add_class("text-center")
)

def server(input, output, session):
    @render.code
    @reactive.event(input.run)
    def result():
        time.sleep(1.5)  # Simulate a long-running task
        return f"Ran {input.run()} time(s)"

    @reactive.effect
    @reactive.event(input.reset)
    def _():
        ui.update_task_button("run", state="ready")

app = App(app_ui, server)
import time

from shiny import reactive, render
from shiny.express import input, ui

ui.input_task_button("run", "Run task", auto_reset=False)  
ui.input_action_button("reset", "Reset task button")

@render.code
@reactive.event(input.run)
def result():
    time.sleep(1.5)  # Simulate a long-running task
    return f"Task completed {input.run()} time(s)"

@reactive.effect
@reactive.event(input.reset)
def _():
    ui.update_task_button("run", state="ready")  
import time

from shiny import App, reactive, render, ui

app_ui = ui.page_fluid(
    ui.input_task_button("run", "Run task", auto_reset=False),  
    ui.input_action_button("reset", "Reset task button"),
    ui.output_code("result"),
)

def server(input, output, session):
    @render.code
    @reactive.event(input.run)
    def result():
        time.sleep(1.5)  # Simulate a long-running task
        return f"Task completed {input.run()} time(s)"

    @reactive.effect
    @reactive.event(input.reset)
    def _():
        ui.update_task_button("run", state="ready")  

app = App(app_ui, server)

True non-blocking work with extended_task

A plain task button only stays busy for the duration of the click’s reactive flush, and reactive code runs serially – so slow work inside a render function blocks every other output and every other session. Pair the button with reactive.extended_task and ui.bind_task_button() to run the work outside the reactive graph: the button stays busy for the whole task, the clock above keeps ticking, and .cancel() can interrupt it.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 320

import asyncio
from datetime import datetime

from shiny import App, reactive, render, ui

app_ui = ui.page_fluid(
    ui.markdown("The clock keeps ticking while the task runs:"),
    ui.output_code("current_time"),
    ui.input_numeric("x", "x", 1),
    ui.input_numeric("y", "y", 2),
    ui.input_task_button("compute", "Compute, slowly"),
    ui.input_action_button("cancel", "Cancel"),
    ui.output_code("result"),
)

def server(input, output, session):
    @render.code
    def current_time():
        reactive.invalidate_later(1)
        return datetime.now().strftime("%H:%M:%S")

    @ui.bind_task_button(button_id="compute")
    @reactive.extended_task
    async def slow_compute(a: int, b: int) -> int:
        await asyncio.sleep(3)
        return a + b

    @reactive.effect
    @reactive.event(input.compute)
    def _():
        slow_compute(input.x(), input.y())

    @reactive.effect
    @reactive.event(input.cancel)
    def _():
        slow_compute.cancel()

    @render.code
    def result():
        return str(slow_compute.result())

app = App(app_ui, server)
import asyncio
from datetime import datetime

from shiny import reactive, render
from shiny.express import input, ui

ui.markdown("The clock keeps ticking while the task runs:")

@render.code
def current_time():
    reactive.invalidate_later(1)
    return datetime.now().strftime("%H:%M:%S")

ui.input_numeric("x", "x", 1)
ui.input_numeric("y", "y", 2)
ui.input_task_button("compute", "Compute, slowly")
ui.input_action_button("cancel", "Cancel")

@ui.bind_task_button(button_id="compute")  
@reactive.extended_task  
async def slow_compute(a: int, b: int) -> int:
    await asyncio.sleep(3)
    return a + b

@reactive.effect
@reactive.event(input.compute)
def _():
    slow_compute(input.x(), input.y())

@reactive.effect
@reactive.event(input.cancel)
def _():
    slow_compute.cancel()  

@render.code
def result():
    return str(slow_compute.result())
import asyncio
from datetime import datetime

from shiny import App, reactive, render, ui

app_ui = ui.page_fluid(
    ui.markdown("The clock keeps ticking while the task runs:"),
    ui.output_code("current_time"),
    ui.input_numeric("x", "x", 1),
    ui.input_numeric("y", "y", 2),
    ui.input_task_button("compute", "Compute, slowly"),
    ui.input_action_button("cancel", "Cancel"),
    ui.output_code("result"),
)

def server(input, output, session):
    @render.code
    def current_time():
        reactive.invalidate_later(1)
        return datetime.now().strftime("%H:%M:%S")

    @ui.bind_task_button(button_id="compute")  
    @reactive.extended_task  
    async def slow_compute(a: int, b: int) -> int:
        await asyncio.sleep(3)
        return a + b

    @reactive.effect
    @reactive.event(input.compute)
    def _():
        slow_compute(input.x(), input.y())

    @reactive.effect
    @reactive.event(input.cancel)
    def _():
        slow_compute.cancel()  

    @render.code
    def result():
        return str(slow_compute.result())

app = App(app_ui, server)
No matching items

Variation Showcase

A live kitchen sink of all possible parameters for the Task Button in Shiny Core and Shiny Express.

No matching items

See also: Action Button