Skip to content

ui.update_radio_buttons doesn't allow for no selection unless choices field is provided #2400

Open

Description

@nvd2291

I'm not sure if this is working as intended but I'm trying to update an input_radio_button group that is dependent on another radio button group. My intention is that whenever another button in a different radio group is selected the currently selected radio button is none. The only way to achieve this when calling ui.update_radio_buttons() is to provide the choices parameter and the selected = "". I would have thought to have no selection you could simply enter selected = "" but it does not update the selected field

Activity

  1. schloerke commented on Aug 1, 2026

    @schloerke
    Collaborator

    Confirmed — this is not working as intended, and there is a one-word workaround: pass selected=[] instead of selected="".

    Reprex

    # app.py
    from shiny import App, Inputs, Outputs, Session, reactive, render, ui
    
    CHOICES = {"a": "Apple", "b": "Banana", "c": "Cherry"}
    
    app_ui = ui.page_fluid(
        ui.input_radio_buttons("rb", "Fruit", CHOICES, selected="a"),
        ui.input_action_button("clear_empty_str", 'selected=""'),
        ui.input_action_button("clear_with_choices", 'choices=..., selected=""'),
        ui.input_action_button("clear_empty_list", "selected=[]"),
        ui.output_code("val"),
    )
    
    
    def server(input: Inputs, output: Outputs, session: Session):
        @render.code
        def val():
            return repr(input.rb())
    
        @reactive.effect
        @reactive.event(input.clear_empty_str)
        def _():
            ui.update_radio_buttons("rb", selected="")
    
        @reactive.effect
        @reactive.event(input.clear_with_choices)
        def _():
            ui.update_radio_buttons("rb", choices=CHOICES, selected="")
    
        @reactive.effect
        @reactive.event(input.clear_empty_list)
        def _():
            ui.update_radio_buttons("rb", selected=[])
    
    
    app = App(app_ui, server)

    Clicking each button in turn (fresh reload before each):

    initial                         input.rb() = 'a'
    update(selected="")             input.rb() = 'a'      checked=['a']    <- no-op, the bug
    update(choices=…, selected="")  input.rb() = None     checked=[]       <- your workaround
    update(selected=[])             input.rb() = None     checked=[]       <- simpler workaround
    

    Root cause

    It is on the client. RadioInputBinding.setValue only clears the group when it receives an empty array; anything else is treated as a value to look up by selector (srcts/src/bindings/input/radio.ts):

    setValue(el, value) {
      if (Array.isArray(value) && value.length === 0) {
        $('input:radio[name="' + $escape(el.id) + '"]').prop("checked", false);
      } else {
        $('input:radio[name="' + $escape(el.id) + '"][value="' + $escape(value) + '"]')
          .prop("checked", true);
      }
    }

    With selected="" the selector becomes [value=""], which matches no radio, so .prop("checked", true) is applied to an empty set. Nothing is unchecked and the previous selection survives.

    The Python side forwards "" faithfully — _update_choice_input builds {"value": selected} and drop_none() only strips None, so "" does reach the browser. It just does not mean anything there.

    Passing choices= works only incidentally: that branch regenerates the entire options HTML (with no checked attribute) and replaces the DOM, which clears the selection as a side effect.

    Suggested fix

    Either treat an empty string as "clear" in setValue:

    if (value === "" || (Array.isArray(value) && value.length === 0)) {

    or normalize on the Python side in _update_choice_input (map selected == "" to []). The first also fixes update_checkbox_group, which routes through the same binding shape.

    Whichever way it goes, update_radio_buttons' selected parameter is currently typed Optional[str], so the working workaround (selected=[]) is not expressible without a type-checker complaint — worth widening the annotation or documenting the supported "clear" sentinel.

    Verified against shiny 1.7.1.dev6 / py-shiny main.


    This repo is the documentation site; the behavior is in the shiny package itself, so this should be moved to posit-dev/py-shiny.

  2. schloerke commented on Aug 1, 2026

    @schloerke
    Collaborator

    Thank you for the Issue, @nvd2291 !

  3. added
    Priority: MediumValid bug or well-defined request with moderate impact or a workaround.
    ai-triage:doneMarks an issue whose AI triage workflow is complete.
    on Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Priority: MediumValid bug or well-defined request with moderate impact or a workaround.ai-triage:doneMarks an issue whose AI triage workflow is complete.needs-triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions