Skip to content

Bind widgets to query params - st.radio & st.selectbox#13922

Merged
mayagbarnes merged 5 commits intodevelopfrom
query-select
Feb 13, 2026
Merged

Bind widgets to query params - st.radio & st.selectbox#13922
mayagbarnes merged 5 commits intodevelopfrom
query-select

Conversation

@mayagbarnes
Copy link
Copy Markdown
Collaborator

@mayagbarnes mayagbarnes commented Feb 12, 2026

Describe your changes

Adds the bind parameter to st.radio & st.selectbox to enable two-way sync between widget values and URL query parameters.

Key changes:

  • Added bind="query-params" support to st.radio and st.selectbox
  • Added centralized validation in _seed_widget_from_url to reject invalid URL option strings by checking against formatted_options. This ensures that invalid query params (e.g., ?size=Random for a radio with options ["Small", "Medium", "Large"]) are removed from the URL and the widget falls back to its default. This replaces the previous TODO on formatted_options in WidgetMetadata
  • Clearability is tied to index=None: when the widget is registered with no default selection, an empty URL param (?key=) clears the widget to None. When index is set, empty URL params are ignored.
  • For st.selectbox with accept_new_options=True, formatted_options is intentionally not passed, so any string value from the URL is accepted.
  • format_func is handled naturally: the URL contains formatted option strings (what the user sees), and validation checks against the same formatted list.

Testing Plan

  • JS & Python Unit Tests: ✅ Added
  • E2E Tests: ✅ Added
  • Manual Testing: ✅

@mayagbarnes mayagbarnes added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 12, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 12, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 12, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13922/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13922.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds query parameter binding functionality to st.radio and st.selectbox widgets, enabling two-way synchronization between widget values and URL query parameters. This feature was already available for other widgets like st.checkbox, st.text_input, and st.color_picker, and this PR extends it to selection widgets.

Changes:

  • Added bind parameter to st.radio and st.selectbox with support for "query-params" value
  • Implemented URL seeding, validation, and auto-clearing for invalid options
  • Added comprehensive test coverage at all levels (Python unit tests, TypeScript unit tests, and E2E tests)

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.

Show a summary per file
File Description
proto/streamlit/proto/Radio.proto Added query_param_key protobuf field (field 14) to enable frontend-backend communication for query param binding
proto/streamlit/proto/Selectbox.proto Added query_param_key protobuf field (field 14) for selectbox query param binding
lib/streamlit/elements/widgets/radio.py Added bind parameter, set query_param_key in proto when bound, and pass validation options to register_widget
lib/streamlit/elements/widgets/selectbox.py Added bind parameter with proper handling of accept_new_options edge case for validation
lib/streamlit/runtime/state/session_state.py Enhanced _seed_widget_from_url with validation logic for selection widgets using formatted_options
lib/streamlit/runtime/state/common.py Updated formatted_options docstring to clarify its role in URL validation
lib/tests/streamlit/elements/radio_test.py Added comprehensive unit tests for bind parameter functionality
lib/tests/streamlit/elements/selectbox_test.py Added unit tests covering bind parameter with format_func, index=None, and accept_new_options
lib/tests/streamlit/runtime/state/session_state_test.py Added tests for formatted_options validation logic
lib/tests/streamlit/typing/radio_types.py Added type assertion tests for bind parameter
lib/tests/streamlit/typing/selectbox_types.py Added type assertion tests ensuring proper return type inference with bind
frontend/lib/src/components/widgets/Radio/Radio.tsx Configured queryParamBinding and passed to useBasicWidgetState hook
frontend/lib/src/components/widgets/Selectbox/Selectbox.tsx Implemented queryParamBinding configuration matching Radio pattern
frontend/lib/src/components/widgets/Radio/Radio.test.tsx Added tests for query param registration, unregistration, and clearable behavior
frontend/lib/src/components/widgets/Selectbox/Selectbox.test.tsx Added frontend unit tests for query param binding lifecycle
e2e_playwright/st_radio.py Added bound radio widgets (normal, formatted, clearable) to test script
e2e_playwright/st_radio_test.py Added 8 E2E tests covering seeding, URL updates, invalid values, format_func, and clearable behavior
e2e_playwright/st_selectbox.py Added bound selectbox widgets for E2E testing
e2e_playwright/st_selectbox_test.py Added 8 E2E tests with comprehensive coverage including cross-widget pollution guards

@mayagbarnes mayagbarnes added the ai-review If applied to PR or issue will run AI review workflow label Feb 12, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 12, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds the bind="query-params" parameter to st.radio and st.selectbox, enabling two-way synchronization between widget values and URL query parameters. The implementation follows the established pattern already used by other widgets (st.checkbox, st.text_input, st.text_area, st.color_picker). Key changes include:

  • New query_param_key proto field (field 14) on both Radio and Selectbox messages.
  • Backend logic to set the proto field, pass bind, clearable, and formatted_options through register_widget.
  • A new validation path in _seed_widget_from_url that rejects invalid option strings from URLs using formatted_options.
  • Frontend changes to construct and pass queryParamBinding to the existing useBasicWidgetState hook.
  • Comprehensive tests across all layers: Python unit, frontend unit, e2e, and type tests.

Code Quality

The code is well-structured and follows existing patterns consistently. Both radio.py and selectbox.py mirror the approach taken by other bound widgets (e.g., checkbox.py). The session state validation logic for formatted_options is cleanly integrated into _seed_widget_from_url.

Minor issue — incorrect "Next" comment in proto:

In proto/streamlit/proto/Selectbox.proto (line 38), the comment says // Next: 16 but the highest field number in use is 14 (query_param_key), so it should be // Next: 15. The Radio.proto file correctly states // Next: 15. This is a documentation-only issue and doesn't affect functionality.

The selectbox.py implementation nicely handles the accept_new_options case by passing formatted_options=None when accept_new_options=True, allowing any string to be valid from the URL — this is a well-thought-out design choice.

The wait_for_app_run(app) addition at e2e_playwright/st_selectbox_test.py:253 is a sensible stability fix for an existing test (unrelated to the bind feature itself).

Test Coverage

Test coverage is thorough and well-organized:

  • Python unit tests (radio_test.py, selectbox_test.py): Cover proto field setting, missing-key error, no-bind default, invalid bind value, format_func interaction, index=None clearable, and accept_new_options (selectbox-specific).
  • Session state tests (session_state_test.py): Cover formatted_options rejection of invalid values, acceptance of valid values, and pass-through when formatted_options is None.
  • Type tests (radio_types.py, selectbox_types.py): Verify the bind parameter doesn't break return type inference.
  • Frontend unit tests (Radio.test.tsx, Selectbox.test.tsx): Cover query param binding registration/unregistration lifecycle and clearable behavior.
  • E2E tests (st_radio_test.py, st_selectbox_test.py): Cover URL seeding, URL updates on interaction, default-override clearing, invalid value handling, format_func with URL values, clearable empty/invalid values, and non-clearable empty value rejection. Negative assertions (cross-widget pollution, URL param absence) are included throughout.

E2E tests correctly use the page/app_port pattern for URL-seeded tests and app for interaction-based tests, following the e2e_playwright/AGENTS.md guidelines.

Backwards Compatibility

The changes are fully backwards compatible:

  • Proto fields: query_param_key (field 14) is optional in both protos, so existing messages without it are unaffected.
  • Python API: The bind parameter defaults to None in all function signatures, preserving existing behavior.
  • WidgetMetadata dataclass: New fields formatted_options and clearable have defaults (None and False), so existing callers of register_widget are unaffected.
  • Frontend: The queryParamBinding is only constructed when queryParamKey is set in the proto, so unbound widgets behave identically.

Security & Risk

No security concerns identified. The implementation correctly validates URL input:

  • Invalid option strings are rejected and cleared from the URL.
  • Empty values are handled based on the widget's clearable state.
  • The validation uses the formatted_options allowlist, preventing injection of arbitrary values into widgets with fixed options.
  • The accept_new_options case correctly bypasses option validation (as any user input is valid there).

Accessibility

No accessibility impact. The changes are purely data-binding logic — no new UI elements, DOM changes, or interaction patterns are introduced. The underlying UIRadio and UISelectbox components remain unchanged.

Recommendations

  1. Fix the "Next" comment in Selectbox.proto: Change // Next: 16 to // Next: 15 on line 38 of proto/streamlit/proto/Selectbox.proto. The highest field number is 14, so the next available is 15 (matching Radio.proto).

Verdict

APPROVED: Clean, well-tested implementation that consistently follows established patterns for query param binding. The only issue is a minor off-by-one in a proto comment.


This is an automated AI review by opus-4.6-thinking.

@mayagbarnes mayagbarnes marked this pull request as ready for review February 12, 2026 04:48
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍 with one aspect that might allow some simplification.

Comment on lines +157 to +161
# The list of valid formatted option strings for selection widgets.
# When set, _seed_widget_from_url validates URL values against this list and
# rejects any that aren't valid options (e.g., ?foo=invalid). Widgets with a fixed set
# of options (radio, selectbox) pass this; widgets that accept arbitrary values (selectbox
# with accept_new_options=True) should not, since any string is valid.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One aspect to check: As part of the dynamic options support project, I added some logic that checks if the selected option(s) in the widget state are also available in the list of available options. If the option in state is not part of the available options, it gets automatically reset to the default in session state, return value and frontend. Maybe this logic is enough to handle this case here without requiring the additional check. But not sure on this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did see the validate_and_sync_value_with_options addition - though I think this part is complementary, not duplicative.
validate_and_sync_value_with_options correctly resets the widget value and session state but has no knowledge of query params — it doesn't clean up the URL. Without this formatted_options check in _seed_widget_from_url, an invalid URL param like ?foo=invalid would persist indefinitely.

@mayagbarnes mayagbarnes merged commit 6e9143e into develop Feb 13, 2026
43 checks passed
@mayagbarnes mayagbarnes deleted the query-select branch February 13, 2026 18:32
lukasmasuch pushed a commit that referenced this pull request Feb 20, 2026
Adds the bind parameter to `st.radio` & `st.selectbox` to enable two-way sync between widget values and URL query parameters.

**Key changes:**
- Added `bind="query-params"` support to `st.radio` and `st.selectbox`
- Added centralized validation in `_seed_widget_from_url` to reject invalid URL option strings by checking against `formatted_options`. This ensures that invalid query params (e.g., `?size=Random` for a radio with options `["Small", "Medium", "Large"]`) are removed from the URL and the widget falls back to its default. This replaces the previous TODO on `formatted_options` in `WidgetMetadata`
- Clearability is tied to `index=None`: when the widget is registered with no default selection, an empty URL param (`?key=`) clears the widget to `None`. When `index` is set, empty URL params are ignored.
- For `st.selectbox` with `accept_new_options=True`, `formatted_options` is intentionally not passed, so any string value from the URL is accepted.
- `format_func` is handled naturally: the URL contains formatted option strings (what the user sees), and validation checks against the same formatted list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants