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 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)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)
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:
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.Specify the
idandlabelparameters ofui.input_task_button()to define the button’s identifier and the label shown while it is ready to be clicked.Optionally, set
label_busyto customize the message shown while the button is busy, or passauto_reset=Falseto keep the button busy until you reset it yourself withui.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:
- 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
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)Variation Showcase
A live kitchen sink of all possible parameters for the Task Button in Shiny Core and Shiny Express.
See also: Action Button