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 until the reactive cycle that the click started ends, then resets. It does not make the work itself non-blocking. While slow work in a render function or effect runs, the session holds its other outputs and new inputs until the work finishes. If the work is synchronous, it also blocks every other session served by the same process. Making the function async and awaiting frees the other sessions, but the current session still waits: with await asyncio.sleep(3), the user’s app stops responding for three seconds, just as with 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 until the reactive cycle that the click started ends. While slow work in a render function runs, the session’s other outputs and new inputs wait for it, and synchronous work also blocks 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