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 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

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 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)
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