Skip to content

Bind widgets to query params - st.checkbox & st.toggle#13900

Merged
mayagbarnes merged 4 commits intodevelopfrom
query-checkbox
Feb 11, 2026
Merged

Bind widgets to query params - st.checkbox & st.toggle#13900
mayagbarnes merged 4 commits intodevelopfrom
query-checkbox

Conversation

@mayagbarnes
Copy link
Copy Markdown
Collaborator

@mayagbarnes mayagbarnes commented Feb 10, 2026

Describe your changes

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

Also includes clean up / improvements from the st.color_picker implementation:

  • Default values cleared from URL on seed — if the URL contains a value that matches the widget's default (e.g., ?dark_mode=false when default is False), the param is now removed. This aligns backend behavior with the frontend's shouldClearUrlParam logic.
  • getDefaultStateFromProto / getCurrStateFromProto fallback — checkbox now falls back to false instead of null when proto fields are unset, matching the boolean type contract.
  • queryParamBinding construction — color_picker aligned to use the same extracted-const pattern as checkbox for consistency.

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 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 10, 2026

✅ PR preview is ready!

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

@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 10, 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 10, 2026

📈 Frontend coverage change detected

The frontend unit test (vitest) coverage has increased by 0.0500%

  • Current PR: 86.8200% (13878 lines, 1829 missed)
  • Latest develop: 86.7700% (13876 lines, 1835 missed)

✅ Coverage change is within normal range.

📊 View detailed coverage comparison

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

Adds URL query parameter binding support for st.checkbox and st.toggle, enabling two-way sync between widget state and the browser URL via the existing query-param binding infrastructure.

Changes:

  • Added bind parameter support in Python for st.checkbox/st.toggle, wiring it into widget registration and protobuf payloads.
  • Extended the Checkbox protobuf + frontend Checkbox widget to register query-param bindings when queryParamKey is present.
  • Added/updated Python, frontend, and Playwright E2E tests to cover seeding, URL updates, default-clearing, and invalid URL handling.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
proto/streamlit/proto/Checkbox.proto Adds query_param_key field to allow backend → frontend signaling of query-param binding.
lib/streamlit/elements/widgets/checkbox.py Introduces bind API for checkbox/toggle and populates query_param_key; passes binding metadata into widget registration.
lib/streamlit/runtime/state/session_state.py Adjusts URL seeding logic to clear params when URL value resolves to the widget default.
lib/tests/streamlit/runtime/state/session_state_test.py Adds regression test ensuring default-equivalent URL values are cleared.
lib/tests/streamlit/elements/checkbox_test.py Adds unit tests for bind="query-params" behavior and invalid bind value handling for checkbox/toggle.
frontend/lib/src/components/widgets/Checkbox/Checkbox.tsx Registers query-param binding config via useBasicWidgetState when queryParamKey is present; normalizes default/current proto values to booleans.
frontend/lib/src/components/widgets/Checkbox/Checkbox.test.tsx Adds frontend unit tests verifying (un)registration of query-param bindings.
e2e_playwright/st_toggle.py Adds bound toggles used by E2E coverage.
e2e_playwright/st_toggle_test.py Adds E2E tests for query-param seeding, URL updates, default-clearing, and invalid URL values for toggle.
e2e_playwright/st_checkbox.py Adds bound checkboxes used by E2E coverage.
e2e_playwright/st_checkbox_test.py Adds E2E tests for query-param seeding, URL updates, default-clearing, and invalid URL values for checkbox.

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

Summary

This PR adds bind="query-params" support to st.checkbox and st.toggle, enabling two-way sync between widget values and URL query parameters. The implementation follows the same pattern established by the st.color_picker bind feature. It also includes a backend improvement to the _seed_widget_from_url logic in session_state.py that simplifies default-value clearing, plus a minor consistency cleanup for ColorPicker.tsx.

Key changes:

  • Proto: New optional string query_param_key = 11 field in Checkbox.proto.
  • Backend: bind parameter added to checkbox() and toggle() APIs, with session_state seeding logic simplified.
  • Frontend: Checkbox.tsx integrates queryParamBinding via useBasicWidgetState, with getDefaultStateFromProto/getCurrStateFromProto now falling back to false instead of potentially null.
  • Tests: Comprehensive E2E, Python unit, frontend unit, and typing tests added.

Code Quality

The code is clean, well-structured, and follows established patterns in the codebase:

  • The checkbox.py backend implementation mirrors the existing color_picker.py bind pattern exactly — bind as a keyword-only parameter, query_param_key proto field, and clearable=False for the boolean widget type.
  • The Checkbox.tsx frontend follows the same queryParamBinding const pattern that ColorPicker.tsx now uses (after the cleanup in this PR).
  • The getDefaultStateFromProto and getCurrStateFromProto functions now properly fall back to false instead of potentially returning undefined/null (Checkbox.tsx:248-253). This is correct for a boolean widget and matches the type contract.
  • The session_state simplification (session_state.py:1036) removes unnecessary conditions (parsed_value != serialized_default and not is_empty_url_value(url_value)) and replaces them with a straightforward deserialized_value == default_value check. This is logically correct: if the URL value resolves to the widget's default, it should always be cleared from the URL, regardless of how it was encoded.
  • The ColorPicker.tsx cleanup (extracting queryParamBinding to a const) improves readability and consistency.

Minor observations:

  • The queryParamBinding object in Checkbox.tsx (lines 54-61) is created inline on every render. However, useBasicWidgetState correctly destructures it into primitives and memoized values internally, so this does not cause unnecessary effect re-runs. This is consistent with the ColorPicker.tsx pattern.

  • In the E2E tests, the regex patterns like re.compile(r"bound_toggle") could theoretically match bound_toggle_true in the URL. In practice this is safe because widgets at their default value never appear in the URL, but slightly more specific patterns like r"bound_toggle=" or r"[?&]bound_toggle=" would be more defensive. This is a very minor style nit and does not affect test correctness.

Test Coverage

Test coverage is thorough and well-structured:

E2E tests (checkbox + toggle):

  • URL seeding from query params (both default False and overriding True default)
  • URL updates on user interaction (click to change, click back to default)
  • Default value cleanup from URL
  • Invalid URL value handling
  • Each test uses expect for auto-wait assertions (no assert)
  • Negative checks included (e.g., not_to_have_url to verify param removal)

Python unit tests:

  • bind='query-params' sets proto query_param_key field
  • Missing key raises StreamlitAPIException
  • Invalid bind value raises StreamlitInvalidBindValueError
  • No bind leaves query_param_key empty
  • Parameterized across both st.checkbox and st.toggle
  • Session state tests updated for the new default-clearing behavior, with a new test for the deserialized_value == default_value case

Frontend unit tests:

  • Query param binding registration/unregistration lifecycle
  • No registration when queryParamKey is not set
  • Toggle variant with default: true

Typing tests:

  • assert_type checks for both checkbox and toggle with bind="query-params"

The element count constants (CHECKBOX_ELEMENTS = 19, TOGGLE_ELEMENTS = 19) have been correctly updated to reflect the 2 new widgets added to each e2e script.

Backwards Compatibility

This PR is fully backwards compatible:

  • The bind parameter defaults to None, preserving existing behavior for all current users.
  • The proto field query_param_key is optional with field number 11 (new), so older frontends will simply ignore it.
  • The session_state _seed_widget_from_url change is a behavioral refinement: it now clears URL params that match the default value in all cases (previously it only cleared some cases). This is an improvement — users with ?widget=default_value in their URL will see the redundant param cleaned up. This cannot break existing functionality since the widget value remains at the default either way.

Security & Risk

No security concerns identified:

  • The URL query parameter binding only reads/writes URL params that the developer explicitly opts into via bind="query-params" with a user-provided key.
  • Invalid URL values are properly sanitized: they fall back to the widget default and the invalid param is cleared from the URL.
  • The clearable=False setting for checkbox/toggle correctly prevents empty URL values from being accepted.

Accessibility

No accessibility concerns. The checkbox and toggle widgets themselves are unchanged visually and functionally — only the URL binding behavior is new. The existing aria-label={element.label} and keyboard interaction patterns remain intact.

Recommendations

No blocking issues found. A few minor suggestions:

  1. E2E regex specificity (very minor): Consider using more specific regex patterns in toggle E2E tests to avoid potential ambiguity between bound_toggle and bound_toggle_true. For example, re.compile(r"bound_toggle=") instead of re.compile(r"bound_toggle"). This is non-blocking since the tests are correct in practice.

  2. Color picker test cleanup (nice-to-have): The st_color_picker_test.py changes (replacing r"^((?!bound_color=).)*$" with not_to_have_url(re.compile(r"bound_color"))) are a nice readability improvement. Consider applying this pattern retroactively to any similar tests elsewhere if they exist.

Verdict

APPROVED: Clean, well-tested implementation that follows established patterns for binding widgets to URL query params. The session_state simplification is a correct behavioral improvement, and the PR is fully backwards compatible.


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

@mayagbarnes mayagbarnes marked this pull request as ready for review February 10, 2026 23:38
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 👍

@mayagbarnes mayagbarnes merged commit e91dc7e into develop Feb 11, 2026
43 checks passed
@mayagbarnes mayagbarnes deleted the query-checkbox branch February 11, 2026 16:42
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