Skip to content

Bind widgets to query params - st.slider & st.select_slider#13979

Merged
mayagbarnes merged 7 commits intodevelopfrom
query-sliders
Feb 19, 2026
Merged

Bind widgets to query params - st.slider & st.select_slider#13979
mayagbarnes merged 7 commits intodevelopfrom
query-sliders

Conversation

@mayagbarnes
Copy link
Copy Markdown
Collaborator

Describe your changes

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.

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 17, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

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

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-13979/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-13979.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 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 bind parameter to both st.slider and st.select_slider APIs
  • Implements bounds checking in SliderSerde.deserialize to reject out-of-range URL values
  • Adds allow_url_duplicates field to WidgetMetadata to support zero-width ranges in select_slider
  • Extends frontend Slider component 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

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

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 changes include:

  • Protobuf: Added optional string query_param_key = 19 to Slider.proto.
  • Backend (slider.py, select_slider.py): Added bind parameter to all overloads and the implementation. SliderSerde now performs bounds checking on URL-seeded values; SelectSliderSerde handles invalid option fallback per-position.
  • State management (common.py, session_state.py, widgets.py): Added allow_url_duplicates field to WidgetMetadata and threaded it through _sanitize_url_array to support zero-width ranges on select_slider.
  • Frontend (Slider.tsx): Added queryParamBinding configuration branching on slider type (double_array_value vs string_array_value).
  • App testing (element_tree.py): Updated SliderSerde instantiation to match new constructor signature.
  • Tests: Comprehensive unit tests, frontend tests, E2E tests, and typing tests added.

Code Quality

The code is well-structured and follows established patterns from other widgets that already support bind="query-params" (checkbox, radio, selectbox, multiselect, number_input, etc.). Specific observations:

  • Consistent pattern: The queryParamBinding setup in Slider.tsx (lines 150-160) matches the structure used in Multiselect.tsx, NumberInput.tsx, and other widgets.
  • Good separation of concerns: Bounds checking is in the deserializer (SliderSerde.deserialize), URL sanitization is in _sanitize_url_array, and URL clearing/auto-correction is in _seed_widget_from_url.
  • Docstrings: Both st.slider and st.select_slider have detailed docstrings for the new bind parameter.
  • allow_url_duplicates design: The WidgetMetadata field with clear docstring (lines 179-185 in common.py) is a clean solution for the zero-width range case (?color=red&color=red).

Minor observations:

  1. In slider.py line 1024, the condition if bind and key: will silently ignore bind="query-params" when key is not provided. However, register_widget (in widgets.py line 141-148) explicitly validates this and raises a clear error. The proto setting is technically redundant guarding, but harmless since the exception fires first. The same pattern appears in select_slider.py line 483.

  2. Date/time/datetime sliders accept bind without restriction. URL seeding for these types requires microsecond timestamps in the URL (e.g., ?my_date=1609459200000000), which is not user-friendly. The code fails gracefully (unparseable strings are cleared, out-of-range values revert to default), but users may be surprised. The docstring doesn't mention this limitation. This is a minor usability concern, not a bug — consider adding a note in the docstring or restricting bind to numeric types only in a follow-up.

Test Coverage

Test coverage is thorough across all layers:

Python Unit Tests (slider_test.py, select_slider_test.py):

  • Happy path: bind='query-params' sets query_param_key in proto.
  • Error cases: missing key raises StreamlitAPIException, invalid bind value raises StreamlitInvalidBindValueError.
  • Data type coverage: int, float, and range sliders tested for st.slider; range and format_func tested for st.select_slider.

_sanitize_url_array tests (session_state_test.py):

  • Deduplication, allow_duplicates=True, filtering invalid options, max_length truncation, and no-op (returns None) cases.

Frontend Tests (Slider.test.tsx):

  • Query param binding registration for numeric and select sliders.
  • Unmount unregistration.
  • Negative case: no registration when queryParamKey is not set.

E2E Tests (st_slider_test.py, st_select_slider_test.py):

  • Seeding from URL (int, float, range, single values).
  • Out-of-range rejection and reset to default.
  • Invalid value handling (completely invalid, partially invalid range).
  • URL update on user interaction.
  • Zero-width range support.
  • format_func integration.
  • Empty value rejection.
  • Single value for range widget rejection.

Typing Tests (slider_types.py, select_slider_types.py):

  • bind="query-params", bind=None, and range values with bind.

The coverage is comprehensive. One observation: the E2E tests wisely use separate page + app_base_url fixtures for URL-seeded tests and the app fixture for interaction tests, following the established pattern.

Backwards Compatibility

No breaking changes:

  • bind parameter: Defaults to None across all overloads, so existing code is unaffected.
  • Proto field: query_param_key (field 19) is optional, so older frontends will simply ignore it, and older backends won't send it.
  • allow_url_duplicates: Defaults to False in WidgetMetadata, preserving existing deduplication behavior for other widgets like st.multiselect.
  • SliderSerde constructor: Now requires min_value and max_value, but this is an internal class not part of the public API. The only external caller (element_tree.py) has been updated.

Security & Risk

  • URL injection: URL-sourced values go through strict validation — out-of-range numeric values revert to defaults, invalid options are rejected, and unparseable strings are caught by ValueError/TypeError handlers in _seed_widget_from_url. This matches the security model of existing bound widgets.
  • No new attack surface: The bind mechanism reuses the existing query param infrastructure, which has already been security-assessed (label security-assessment-completed is on the PR).

No security concerns identified.

Accessibility

The frontend changes are minimal — only the queryParamBinding configuration is added. The actual slider rendering, ARIA attributes (aria-valuetext, aria-label, aria-valuenow), keyboard navigation, and focus management are unchanged. The existing accessibility behavior is preserved.

Recommendations

  1. Consider documenting date/time limitations: The bind parameter on st.slider works for all data types, but date/time/datetime sliders require microsecond timestamps in URLs, which is impractical for users. Consider adding a note to the docstring (e.g., "Best suited for numeric sliders; date/time sliders require internal timestamp format in URLs") or raising a warning/error when bind is used with TIMELIKE_TYPES in a follow-up.

  2. Minor: SanitizeUrlArrayTest imports inside test methods (session_state_test.py, lines ~2176+): The test class imports _sanitize_url_array inside each test method rather than at the top of the file. Per lib/tests/AGENTS.md: "Imports should be at the top-level of the test file." Consider moving this to the top-level import section.

Verdict

APPROVED: 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 opus-4.6-thinking.

Comment on lines +153 to +155
// 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.
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.

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)

@mayagbarnes mayagbarnes marked this pull request as ready for review February 18, 2026 04:35
@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review: PR #13979 — Bind widgets to query params - st.slider & st.select_slider

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. The implementation spans proto, backend, frontend, and tests.

Key design decisions:

  • 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_duplicates field on WidgetMetadata lets st.select_slider preserve duplicate values in zero-width ranges (e.g., ?color=red&color=red).
  • format_func for st.select_slider is handled naturally — URLs contain the formatted option strings.
  • A new query_param_key field is added to the Slider protobuf 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 = 19 with a // Next: 20 comment.
  • Backend (slider.py, select_slider.py): The bind parameter is added consistently to all overloads. Validation is handled centrally in register_widget, with clearable=False correctly set since sliders always have a value.
  • SliderSerde (slider.py): Addition of min_value/max_value for bounds checking in deserialize() is clean. Falls back to default so the URL param gets cleared.
  • SelectSliderSerde (select_slider.py): 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): The allow_duplicates parameter is a clean extension that doesn't affect existing behavior.
  • Frontend (Slider.tsx): The queryParamBinding config correctly differentiates between string_array_value (select_slider) and double_array_value (numeric slider). Helper functions stringValuesToIndices and indicesToStringValues are clear and well-placed.

Issue: select_slider default-clearing comparison mismatch (confirmed)

Both reviewers were asked about this, and after independent code verification, this bug is confirmed.

In Slider.tsx, the queryParamBinding config for select_slider does NOT pass optionStrings:

const queryParamBinding = element.queryParamKey
  ? {
      paramKey: element.queryParamKey,
      valueType: isSelectSlider(element)
        ? ("string_array_value" as const)
        : ("double_array_value" as const),
      clearable: false,
      urlFormat: "repeated" as const,
      // Missing: optionStrings for select_slider
    }
  : undefined

Without optionStrings, the normalization logic in registerQueryParamBinding (WidgetStateManager.ts:1107-1123) is skipped. This means:

  • binding.defaultValue remains as numeric indices (e.g., [3] for "green")
  • When the user interacts and sets the value back to default, convertToUrlValue produces string values (e.g., ["green"])
  • isDefaultArrayValue compares "green" === "3"false
  • The URL param is NOT cleared when the widget returns to its default value

Fix: Pass optionStrings: isSelectSlider(element) ? element.options : undefined in the queryParamBinding config.

Minor observations (non-blocking)

  • The TODO at Slider.tsx:153-155 about date/time/datetime sliders producing microsecond timestamps in URLs is a fair known limitation for a follow-up.
  • The guard if bind and key: in both slider.py and select_slider.py is technically redundant since register_widget already validates this — but it's defensive and harmless.

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), invalid bind value (error), different data types (int, float, range), and format_func interactions.
  • _sanitize_url_array unit 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 correct valueType for both slider types, unregistration on unmount, and the negative case (no registration without queryParamKey).
  • E2E tests: Comprehensive coverage for both widgets including: URL seeding (single, range, float), invalid value rejection, partial-invalid range auto-correction, format_func handling, 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 the bind parameter works with mypy type checking.

Test gap

  • There is no test for select_slider clearing the URL when a user changes the value back to default (analogous to test_slider_query_param_default_override in st_slider_test.py). This test would currently fail due to the bug above.

Backwards Compatibility

No breaking changes:

  • The bind parameter defaults to None — fully opt-in.
  • The protobuf change is purely additive (optional field 19).
  • allow_url_duplicates defaults to False, preserving existing dedup behavior for all current widgets.
  • The SliderSerde constructor change (added min_value/max_value) only affects internal code — all call sites were updated. This class is not part of the public API.

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_array function properly handles filtering, deduplication, and length enforcement.
  • Error paths (ValueError, TypeError, IndexError) in _seed_widget_from_url are 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 interactive elements.

Reviewer Agreement/Disagreement

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

  1. Fix select_slider default-clearing (blocking): Pass optionStrings: isSelectSlider(element) ? element.options : undefined in the queryParamBinding config in Slider.tsx.
  2. 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.
  3. Consider step-alignment validation (non-blocking, follow-up): URL-seeded values like ?bound_float=0.15 with step=0.1 are accepted since they pass bounds checking. Consider documenting or adding step-snapping.
  4. 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-163 registers select_slider bindings with valueType: "string_array_value" but does not provide optionStrings.
  • frontend/lib/src/components/widgets/Slider/Slider.tsx:466-468 uses element.default (index array) as the binding default via useBasicWidgetState.
  • In frontend/lib/src/WidgetStateManager.ts:1107-1123, default normalization from indices to strings only happens when options are 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_slider clearing URL when a user changes value back to default (analogous to test_slider_query_param_default_override in e2e_playwright/st_slider_test.py).
  • A focused frontend unit test in frontend/lib/src/components/widgets/Slider/Slider.test.tsx for 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

  1. Fix select_slider frontend binding default normalization by either:
    • passing optionStrings: element.options in Slider.tsx query binding config, or
    • supplying a string-array default for query binding instead of index defaults.
  2. 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_duplicates field on WidgetMetadata lets st.select_slider preserve duplicate values in zero-width ranges (e.g., ?color=red&color=red).
  • format_func for st.select_slider is handled naturally — URLs contain the formatted option strings.
  • A new query_param_key field is added to the Slider protobuf 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 = 19 with a // Next: 20 comment. No backwards-compatibility risk.
  • Backend (slider.py, select_slider.py): The bind parameter is added consistently to all overloads. Validation is handled in register_widget (centralized), with clearable=False correctly set since sliders always have a value.
  • SliderSerde (slider.py:170-236): The addition of min_value/max_value for bounds checking in deserialize() 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): The allow_duplicates parameter is a clean extension that doesn't affect existing behavior (defaults to False).
  • Frontend (Slider.tsx): The queryParamBinding config correctly differentiates between string_array_value (select_slider) and double_array_value (numeric slider). Helper functions stringValuesToIndices and indicesToStringValues are clear and well-placed.
  • Element tree (element_tree.py): The SliderSerde constructor call was correctly updated with the new required min_value and max_value params.

Minor observations:

  • The TODO at Slider.tsx:153-155 about 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 both slider.py:1024 and select_slider.py:483 is technically redundant since register_widget already 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), and format_func interactions.
  • _sanitize_url_array unit 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 correct valueType for both slider types, unregistration on unmount, and the negative case (no registration without queryParamKey). Tests for setStringArrayValue vs setDoubleArrayValue correctly 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_func handling, 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 the bind parameter 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 bind parameter defaults to None — fully opt-in.
  • The protobuf change is purely additive (optional field 19).
  • allow_url_duplicates defaults to False, preserving existing dedup behavior for all current widgets.
  • The SliderSerde constructor change (added min_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.deserialize is 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_array function properly handles filtering, deduplication, and length enforcement.
  • Error paths (ValueError, TypeError, IndexError) in _seed_widget_from_url are 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 renderThumb function continues to set aria-valuetext correctly for both numeric and select slider modes.

Recommendations

  1. Consider step-alignment validation: URL-seeded values like ?bound_float=0.15 with step=0.1 are accepted since they pass bounds checking. While consistent with st.number_input behavior, 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.

  2. 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 with bind, which is fine since this is a known limitation. A follow-up to format these as ISO strings would improve usability.

  3. Minor E2E regex precision: Some E2E URL assertions use simple substring regexes like re.compile(r"bound_int=75") which could theoretically match bound_int=750. More precise patterns like r"[?&]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.

@github-actions github-actions bot added the do-not-merge PR is blocked from merging label Feb 19, 2026
sfc-gh-lmasuch
sfc-gh-lmasuch approved these changes Feb 19, 2026
lukasmasuch

This comment was marked as outdated.

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 👍

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?

@mayagbarnes
Copy link
Copy Markdown
Collaborator Author

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 raw_value (formatted strings) only applies to the current value. The proto default field still carries indices (e.g., [3] for "green"). When the frontend registers the query param binding, it stores that index-based default and later compares it against the string-based URL value ("green" vs 3), which fails without optionStrings to normalize the default.

I have a couple F/Us for slider anyway (date/time handling, step validation) and will look into changing default to the option instead of the index

@mayagbarnes mayagbarnes removed the do-not-merge PR is blocked from merging label Feb 19, 2026
@mayagbarnes mayagbarnes merged commit 36face4 into develop Feb 19, 2026
42 checks passed
@mayagbarnes mayagbarnes deleted the query-sliders branch February 19, 2026 22:49
lukasmasuch pushed a commit that referenced this pull request Feb 20, 2026
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.
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.

4 participants