Bind widgets to query params - st.slider & st.select_slider#13979
Bind widgets to query params - st.slider & st.select_slider#13979mayagbarnes merged 7 commits intodevelopfrom
st.slider & st.select_slider#13979Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
✅ PR preview is ready!
|
There was a problem hiding this comment.
Pull request overview
This PR adds query parameter binding functionality to st.slider and st.select_slider, enabling two-way synchronization between widget values and URL query parameters. This completes the query parameter binding feature set for slider widgets, following the same patterns established in previous PRs for other widget types.
Changes:
- Adds
bindparameter to bothst.sliderandst.select_sliderAPIs - Implements bounds checking in
SliderSerde.deserializeto reject out-of-range URL values - Adds
allow_url_duplicatesfield toWidgetMetadatato support zero-width ranges in select_slider - Extends frontend
Slidercomponent to register query param bindings for both widget types - Includes comprehensive unit, typing, and E2E tests
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
proto/streamlit/proto/Slider.proto |
Adds query_param_key field to Slider protobuf message |
lib/streamlit/elements/widgets/slider.py |
Implements bind parameter, adds bounds checking to SliderSerde, updates all overloads and documentation |
lib/streamlit/elements/widgets/select_slider.py |
Implements bind parameter with allow_url_duplicates=True for zero-width ranges, updates all overloads and documentation |
lib/streamlit/runtime/state/widgets.py |
Adds allow_url_duplicates parameter to register_widget function |
lib/streamlit/runtime/state/session_state.py |
Updates _sanitize_url_array to support duplicate preservation via allow_duplicates parameter |
lib/streamlit/runtime/state/common.py |
Adds allow_url_duplicates field to WidgetMetadata with detailed documentation |
lib/streamlit/testing/v1/element_tree.py |
Updates SliderSerde initialization to include min and max bounds |
lib/tests/streamlit/elements/slider_test.py |
Adds SliderBindQueryParamsTest class with tests for bind functionality |
lib/tests/streamlit/elements/select_slider_test.py |
Adds SelectSliderBindQueryParamsTest class with tests including format_func integration |
lib/tests/streamlit/runtime/state/session_state_test.py |
Adds SanitizeUrlArrayTest class testing deduplication behavior |
lib/tests/streamlit/typing/slider_types.py |
Adds type assertions for bind parameter |
lib/tests/streamlit/typing/select_slider_types.py |
Adds type assertions for bind parameter |
frontend/lib/src/components/widgets/Slider/Slider.tsx |
Implements query param binding registration with appropriate value types and URL format |
frontend/lib/src/components/widgets/Slider/Slider.test.tsx |
Adds comprehensive tests for query param binding registration |
e2e_playwright/st_slider.py |
Adds three test sliders with bind for int, float, and range values |
e2e_playwright/st_slider_test.py |
Adds 8 E2E tests covering seeding, out-of-range handling, URL updates, and edge cases |
e2e_playwright/st_select_slider.py |
Adds three test select_sliders with bind for single value, range, and format_func |
e2e_playwright/st_select_slider_test.py |
Adds 7 E2E tests covering seeding, invalid option handling, format_func, and edge cases |
5b448bf to
da24c9f
Compare
SummaryThis PR adds
Code QualityThe code is well-structured and follows established patterns from other widgets that already support
Minor observations:
Test CoverageTest coverage is thorough across all layers: Python Unit Tests (
Frontend Tests (
E2E Tests (
Typing Tests (
The coverage is comprehensive. One observation: the E2E tests wisely use separate Backwards CompatibilityNo breaking changes:
Security & Risk
No security concerns identified. AccessibilityThe frontend changes are minimal — only the Recommendations
VerdictAPPROVED: The implementation is well-structured, follows established patterns for query-param binding across other Streamlit widgets, includes comprehensive test coverage at all layers, and introduces no breaking changes. The minor observations about date/time URL format and import placement are non-blocking. This is an automated AI review by |
| // TODO(query-params): Date/time/datetime sliders produce microsecond | ||
| // timestamps in URLs (e.g., ?date=1718409600000000) because they use | ||
| // double_array_value. Consider formatting as ISO strings for readability. |
There was a problem hiding this comment.
Added to list for potential follow-up: formatting microsecond timestamps in URL as ISO strings (e.g., ?date=2024-06-15 instead of the current display of ?date=1718409600000000)
Consolidated Code Review: PR #13979 — Bind widgets to query params -
|
| Topic | gpt-5.3-codex-high | opus-4.6-thinking | Resolution |
|---|---|---|---|
| Overall quality | Good | Good | Agreed |
| select_slider default-clearing bug | Identified as blocking | Not flagged | Confirmed as real bug via code verification |
| Test coverage | Gap noted for default-clearing | Comprehensive | Gap confirmed — missing test for select_slider default-clearing |
| Backwards compatibility | No issues | No issues | Agreed |
| Security | No issues | No issues | Agreed |
| Accessibility | No issues | No issues | Agreed |
| Step-alignment for URL values | Not mentioned | Non-blocking recommendation | Reasonable follow-up |
| E2E regex precision | Not mentioned | Non-blocking recommendation | Minor nit, low risk |
Recommendations
- Fix
select_sliderdefault-clearing (blocking): PassoptionStrings: isSelectSlider(element) ? element.options : undefinedin thequeryParamBindingconfig inSlider.tsx. - Add regression test: Add an E2E test for "change select_slider back to default clears URL" (analogous to
test_slider_query_param_default_override), and optionally a frontend unit test for this path. - Consider step-alignment validation (non-blocking, follow-up): URL-seeded values like
?bound_float=0.15withstep=0.1are accepted since they pass bounds checking. Consider documenting or adding step-snapping. - Date/time slider URLs (non-blocking, follow-up): As noted in the TODO, date/time sliders would produce microsecond timestamps in URLs. A follow-up to format these as ISO strings would improve usability.
Verdict
CHANGES REQUESTED: The select_slider frontend binding does not pass optionStrings, causing the default-comparison logic to fail (index [3] vs string ["green"]). This means the URL parameter won't be cleared when a bound select_slider is set back to its default value. This is a functional bug that should be fixed before merge, along with a corresponding regression test.
Consolidated review by opus-4.6-thinking. Based on reviews from 2 of 2 expected models.
📋 Review by `gpt-5.3-codex-high`
Summary
This PR adds bind="query-params" support for st.slider and st.select_slider, including a new query_param_key proto field, backend session-state seeding/validation, frontend URL sync registration, and new Python/TypeScript/E2E coverage.
I found one functional issue in the new select_slider frontend binding path that can keep default values in the URL when they should be cleared.
Code Quality
Overall structure is clean and consistent with existing query-param binding patterns, and the backend/frontend division of responsibilities is good.
Issue found:
frontend/lib/src/components/widgets/Slider/Slider.tsx:150-163registersselect_sliderbindings withvalueType: "string_array_value"but does not provideoptionStrings.frontend/lib/src/components/widgets/Slider/Slider.tsx:466-468useselement.default(index array) as the binding default viauseBasicWidgetState.- In
frontend/lib/src/WidgetStateManager.ts:1107-1123, default normalization from indices to strings only happens whenoptionsare provided.
Result: for bound select_slider, URL values are strings (e.g. ["green"]) but default remains numeric indices (e.g. [3]), so shouldClearUrlParam default comparison fails and reverting to default from the UI is not recognized as "at default" on the frontend.
Test Coverage
Coverage is broad for most newly introduced behavior (invalid values, range handling, empty params, duplicate params for zero-width ranges, bind validation, typing tests, and E2E seeding/update flows).
Gap:
- There is no explicit test for
select_sliderclearing URL when a user changes value back to default (analogous totest_slider_query_param_default_overrideine2e_playwright/st_slider_test.py). - A focused frontend unit test in
frontend/lib/src/components/widgets/Slider/Slider.test.tsxfor this default-clearing path is also missing.
Backwards Compatibility
No API-breaking signature changes are introduced (bind is additive and optional), and proto evolution is additive.
However, the default-clearing mismatch above changes runtime URL behavior for bound select_slider and should be fixed before merge.
Security & Risk
No direct security issue found.
Primary risk is behavioral regression in URL-sync semantics (default values lingering in URL and inconsistent frontend/backend clearing behavior).
Accessibility
No new interactive UI patterns were introduced; the changes are state-sync logic and tests.
No additional a11y concerns identified from this diff.
Recommendations
- Fix
select_sliderfrontend binding default normalization by either:- passing
optionStrings: element.optionsinSlider.tsxquery binding config, or - supplying a string-array default for query binding instead of index defaults.
- passing
- Add regression tests for "change back to default clears URL" for bound
select_slider(frontend unit + E2E).
Verdict
CHANGES REQUESTED: The select_slider default URL-clearing logic is currently inconsistent due to index-vs-string default comparison and should be corrected before merge.
This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.
📋 Review by `opus-4.6-thinking`
Summary
This PR adds bind="query-params" support to st.slider and st.select_slider, enabling two-way synchronization between widget values and URL query parameters. Key design decisions include:
- Range values use repeated URL parameters (e.g.,
?key=10&key=90). - Out-of-range and invalid URL values silently revert to defaults and clear the URL.
- A new
allow_url_duplicatesfield onWidgetMetadataletsst.select_sliderpreserve duplicate values in zero-width ranges (e.g.,?color=red&color=red). format_funcforst.select_slideris handled naturally — URLs contain the formatted option strings.- A new
query_param_keyfield is added to theSliderprotobuf message.
Code Quality
The implementation follows existing patterns established by other widgets' query param binding (e.g., st.number_input, st.multiselect). Code is well-structured across all layers:
- Proto (
Slider.proto): Clean additive change —optional string query_param_key = 19with a// Next: 20comment. No backwards-compatibility risk. - Backend (
slider.py,select_slider.py): Thebindparameter is added consistently to all overloads. Validation is handled inregister_widget(centralized), withclearable=Falsecorrectly set since sliders always have a value. SliderSerde(slider.py:170-236): The addition ofmin_value/max_valuefor bounds checking indeserialize()is clean. The bounds check only rejects URL-seeded values (frontend already enforces bounds), and falls back to default so the URL param gets cleared.SelectSliderSerde(select_slider.py:78-156): Well-designed serde that gracefully handles invalid options per-position, preserving valid values while falling back to defaults for invalid ones._sanitize_url_array(session_state.py:73-109): Theallow_duplicatesparameter is a clean extension that doesn't affect existing behavior (defaults toFalse).- Frontend (
Slider.tsx): ThequeryParamBindingconfig correctly differentiates betweenstring_array_value(select_slider) anddouble_array_value(numeric slider). Helper functionsstringValuesToIndicesandindicesToStringValuesare clear and well-placed. - Element tree (
element_tree.py): TheSliderSerdeconstructor call was correctly updated with the new requiredmin_valueandmax_valueparams.
Minor observations:
- The TODO at
Slider.tsx:153-155about date/time/datetime sliders producing microsecond timestamps in URLs is a fair known limitation. Worth addressing in a follow-up but not a blocker. - The guard
if bind and key:in bothslider.py:1024andselect_slider.py:483is technically redundant sinceregister_widgetalready validates this combination — but it's defensive and avoids setting the proto field unnecessarily.
Test Coverage
Test coverage is thorough across all layers:
- Python unit tests (
slider_test.py,select_slider_test.py): Cover bind with key, bind without key (error), bind without key (error), invalid bind value (error), different data types (int, float, range), andformat_funcinteractions. _sanitize_url_arrayunit tests (session_state_test.py): Good coverage of dedup, allow_duplicates, filtering, truncation, combined operations, and the no-op case.- Frontend unit tests (
Slider.test.tsx): Test query param registration with correctvalueTypefor both slider types, unregistration on unmount, and the negative case (no registration withoutqueryParamKey). Tests forsetStringArrayValuevssetDoubleArrayValuecorrectly verify the select_slider string-based wire format. - E2E tests: Comprehensive coverage for both widgets including: URL seeding (single, range, float), invalid value rejection, partial-invalid range auto-correction,
format_funchandling, interaction updating URL, zero-width ranges, single-value-on-range reset, both-invalid-range reset, and empty value rejection. - Type tests (
slider_types.py,select_slider_types.py): Verify thebindparameter works with mypy type checking for various return types.
The E2E tests follow best practices from e2e_playwright/AGENTS.md: they use page/app_base_url fixtures with build_app_url, wait_for_app_loaded, and expect-based assertions. Negative checks (e.g., not_to_have_url) are present throughout.
Backwards Compatibility
No breaking changes:
- The
bindparameter defaults toNone— fully opt-in. - The protobuf change is purely additive (
optionalfield 19). allow_url_duplicatesdefaults toFalse, preserving existing dedup behavior for all current widgets.- The
SliderSerdeconstructor change (addedmin_value/max_value) only affects internal code — all call sites (slider.py,element_tree.py) were updated. This class is not part of the public API. - The bounds-checking logic in
SliderSerde.deserializeis a no-op for frontend-submitted values (the UI already enforces bounds), only affecting URL-seeded values.
Security & Risk
No security concerns identified:
- URL-sourced values are properly validated: out-of-range values are rejected, invalid options fall back to defaults, empty values are rejected for non-clearable widgets.
- The deserializer's bounds checking prevents URL injection of arbitrary numeric values outside the defined range.
- The
_sanitize_url_arrayfunction properly handles filtering, deduplication, and length enforcement. - Error paths (ValueError, TypeError, IndexError) in
_seed_widget_from_urlare caught, logged, and result in URL param cleanup.
Accessibility
No accessibility regressions:
- The slider's existing accessibility attributes (
aria-valuetext,aria-valuenow,aria-valuemin,aria-valuemax,aria-label) are fully preserved. - No changes to the rendering logic of thumbs, labels, or interactive elements.
- The
renderThumbfunction continues to setaria-valuetextcorrectly for both numeric and select slider modes.
Recommendations
-
Consider step-alignment validation: URL-seeded values like
?bound_float=0.15withstep=0.1are accepted since they pass bounds checking. While consistent withst.number_inputbehavior, this could result in a slider position that doesn't align with step markers until the user interacts with it. Consider documenting this as expected behavior or adding step-snapping in a follow-up. -
Date/time slider URLs: As noted in the TODO at
Slider.tsx:153, date/time sliders would produce microsecond timestamps in URLs (e.g.,?date=1718409600000000). The current PR doesn't add E2E tests for date/time sliders withbind, which is fine since this is a known limitation. A follow-up to format these as ISO strings would improve usability. -
Minor E2E regex precision: Some E2E URL assertions use simple substring regexes like
re.compile(r"bound_int=75")which could theoretically matchbound_int=750. More precise patterns liker"[?&]bound_int=75(?:&|$)"would be more robust, though in practice this is unlikely to cause false positives.
Verdict
APPROVED: This is a well-implemented, thoroughly tested, and backwards-compatible addition of query parameter binding to st.slider and st.select_slider. The code follows existing patterns, handles edge cases properly, and has comprehensive test coverage across all layers.
This is an automated AI review by opus-4.6-thinking.
lukasmasuch
left a comment
There was a problem hiding this comment.
LGTM 👍
Fix select_slider default-clearing (blocking): Pass optionStrings: isSelectSlider(element) ? element.options : undefined in the queryParamBinding config in Slider.tsx.
This feedback from AI is probably not correct anymore with the change to use formatted values instead of index-based, right? Is the optionStrings even used somewhere?
Looks like this this finding is actually correct - the wire format change to I have a couple F/Us for slider anyway (date/time handling, step validation) and will look into changing |
35fbd37 to
ffa328c
Compare
Adds the bind parameter to `st.slider` and `st.select_slider` to enable two-way sync between widget values and URL query parameters. **Key changes:** - Range values use repeated URL parameters (e.g., `?key=10&key=90` or `?key=red&key=blue`). - **Out-of-range handling for `st.slider`:** Bounds checking is performed in `SliderSerde.deserialize` -- if any URL value falls outside `[min_value, max_value]`, the widget silently reverts to its default and the invalid param is cleared from the URL. This mirrors `st.number_input`'s deserializer behavior. - **Invalid option handling for `st.select_slider`:** Invalid URL options are auto-corrected to the closest valid state by leveraging the existing deserializer and post-registration `validate_and_sync` functions. If all values are invalid, the widget reverts to its default. - **Clearability is unconditionally `False`:** Both `st.slider` and `st.select_slider` always have a value (no empty/cleared state in the UI), so empty URL params (e.g., `?key=`) are rejected. - **Added `allow_url_duplicates` to `WidgetMetadata`:** A new boolean field that controls whether `_sanitize_url_array` deduplicates URL array values. `st.select_slider` sets this to `True` because `?color=red&color=red` is a valid zero-width range. Other widgets like `st.multiselect` retain the default deduplication behavior since duplicate selections aren't possible in the UI. - **`format_func` is handled naturally** for `st.select_slider`: the URL contains formatted option strings (what the user sees), and validation checks against the same formatted list.
Describe your changes
Adds the bind parameter to
st.sliderandst.select_sliderto enable two-way sync between widget values and URL query parameters.Key changes:
?key=10&key=90or?key=red&key=blue).st.slider: Bounds checking is performed inSliderSerde.deserialize-- if any URL value falls outside[min_value, max_value], the widget silently reverts to its default and the invalid param is cleared from the URL. This mirrorsst.number_input's deserializer behavior.st.select_slider: Invalid URL options are auto-corrected to the closest valid state by leveraging the existing deserializer and post-registrationvalidate_and_syncfunctions. If all values are invalid, the widget reverts to its default.False: Bothst.sliderandst.select_slideralways have a value (no empty/cleared state in the UI), so empty URL params (e.g.,?key=) are rejected.allow_url_duplicatestoWidgetMetadata: A new boolean field that controls whether_sanitize_url_arraydeduplicates URL array values.st.select_slidersets this toTruebecause?color=red&color=redis a valid zero-width range. Other widgets likest.multiselectretain the default deduplication behavior since duplicate selections aren't possible in the UI.format_funcis handled naturally forst.select_slider: the URL contains formatted option strings (what the user sees), and validation checks against the same formatted list.Testing Plan