[feature] Add filter mode to select widgets#14537
Conversation
Implement configurable dropdown filtering so selectbox and multiselect can use fuzzy, contains, prefix, or disabled matching. This gives apps more precise search behavior for IDs and codes, and lets them turn off typing when filtering is unnecessary.
✅ PR preview is ready!
|
Added Agent Docs
|
✅ 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. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new filter_mode option for st.selectbox and st.multiselect, wiring it end-to-end (Python API → protobuf → frontend filtering) to support "fuzzy" (default), "contains", "prefix", and disabling filtering/typing (None).
Changes:
- Add
filter_modeto Selectbox/MultiSelect protos and Python widget implementations (including validation + docs). - Add shared frontend filtering utilities + hook support to apply the selected filter mode consistently.
- Add/extend typing, unit, frontend, and Playwright E2E coverage for the new modes.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| proto/streamlit/proto/Selectbox.proto | Adds filter_mode field to Selectbox protobuf message. |
| proto/streamlit/proto/MultiSelect.proto | Adds filter_mode field to MultiSelect protobuf message. |
| lib/streamlit/elements/widgets/selectbox.py | Adds filter_mode param, validation, stable-ID behavior note, and proto wiring for st.selectbox. |
| lib/streamlit/elements/widgets/multiselect.py | Adds filter_mode param, validation, stable-ID behavior note, and proto wiring for st.multiselect. |
| lib/tests/streamlit/elements/selectbox_test.py | Adds unit coverage for default/none/invalid filter_mode and stable-ID whitelist behavior. |
| lib/tests/streamlit/elements/multiselect_test.py | Adds unit coverage for default/none/invalid filter_mode and stable-ID whitelist behavior. |
| lib/tests/streamlit/typing/selectbox_types.py | Adds typing assertions for filter_mode usage in selectbox. |
| lib/tests/streamlit/typing/multiselect_types.py | Adds typing assertions for filter_mode usage in multiselect. |
| frontend/lib/src/util/fuzzyFilterSelectOptions.ts | Adds normalization + new contains/prefix/none filtering behavior. |
| frontend/lib/src/util/fuzzyFilterSelectOptions.test.ts | Adds unit tests for new filter modes and normalization fallback. |
| frontend/lib/src/hooks/useSelectCommon.ts | Plumbs filterMode through shared select hook; disables typing when mode is none. |
| frontend/lib/src/components/shared/Dropdown/Selectbox.tsx | Passes filterMode into shared dropdown Selectbox implementation. |
| frontend/lib/src/components/shared/Dropdown/Selectbox.test.tsx | Adds tests for contains/prefix/none filtering behavior in shared Selectbox. |
| frontend/lib/src/components/widgets/Selectbox/Selectbox.tsx | Passes backend filterMode through to shared dropdown Selectbox. |
| frontend/lib/src/components/widgets/Multiselect/Multiselect.tsx | Passes backend filterMode into useSelectCommon for multiselect behavior. |
| frontend/lib/src/components/widgets/Multiselect/Multiselect.test.tsx | Adds tests for contains filtering + none disabling typing/keeping options visible. |
| frontend/knip.json | Updates Knip configuration. |
| e2e_playwright/st_selectbox.py | Adds selectbox examples for prefix/contains/none modes. |
| e2e_playwright/st_selectbox_test.py | Adds E2E tests for prefix/contains/none modes and updates widget count/initial expectations. |
| e2e_playwright/st_multiselect.py | Adds multiselect examples for prefix/contains/none modes. |
| e2e_playwright/st_multiselect_test.py | Adds E2E tests for prefix/contains/none modes and updates widget count/initial expectations. |
There was a problem hiding this comment.
Summary
This PR adds a filter_mode parameter to st.selectbox and st.multiselect with four modes: "fuzzy" (default, preserving existing behavior), "contains" (case-insensitive substring), "prefix" (case-insensitive prefix), and None (disables typing/filtering). The implementation spans protobuf definitions, Python backend validation/serialization, a new centralized filterSelectOptions frontend function, the shared useSelectCommon hook, and both widget components. The feature closes #7238 and #6160.
Reviewer consensus: All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) approved the PR unanimously with no blocking issues. Feedback across reviewers was highly consistent.
Code Quality
The implementation is well-structured and follows existing codebase patterns. All three reviewers independently noted the clean architecture:
- Centralized filtering logic in
fuzzyFilterSelectOptions.tswith afilterSelectOptionsdispatcher that delegates to the appropriate strategy. ThenormalizeSelectFilterModefunction provides a safe default fallback for unknown values. - Proper widget identity management:
filter_modeis correctly included inkey_as_main_identityso the widget resets when the mode changes, consistent with howaccept_new_optionsis handled. - Render-time derivation:
normalizedFilterModeis computed at the top of theuseSelectCommonhook (not in auseEffect), andisFilteringDisabledis derived inline — following React's "You Might Not Need an Effect" guidance. useMemodependency array forcreateFilterOptionscorrectly includes[normalizedFilterMode].- Validation logic in Python is clean and correctly handles the incompatibility between
filter_mode=Noneandaccept_new_options=True.
Minor observations (agreed by 2 of 3 reviewers):
- The
_validate_filter_modefunction is nearly identical inselectbox.pyandmultiselect.py. This could be extracted to a shared utility, though the duplication is small. - The
knip.jsonchange (removingignoreUnresolvedfor proto imports) appears unrelated to thefilter_modefeature and would be cleaner verified or separated.
Test Coverage
All three reviewers agreed the test coverage is comprehensive and well-layered:
- Python unit tests: Default value assertion, explicit mode setting,
Noneserialization to"none", invalid mode rejection, incompatibility withaccept_new_options=True, and widget identity changes via parameterized tests for both widgets. - TypeScript unit tests:
contains,prefix, andnonemodes tested in bothSelectbox.test.tsxandMultiselect.test.tsx, including readonly input assertion and negative assertions for non-matching options. - Frontend utility tests: Direct testing of
filterSelectOptionsfor all four modes, plusnormalizeSelectFilterModefor unknown values. - Typing tests:
assert_typecoverage forfilter_modeparameter in bothselectbox_types.pyandmultiselect_types.py. - E2E tests: Three new tests per widget covering prefix-only matching, contains matching with negative assertions, and
Nonemode (readonly + dropdown still works). Element count constants updated correctly.
Backwards Compatibility
All three reviewers confirmed full backwards compatibility:
- Default value is
"fuzzy", preserving existing behavior for all users. - The protobuf field is
optional string, so older frontends ignore it, and older backends that don't send it result in the frontend defaulting to"fuzzy"vianormalizeSelectFilterMode. - No existing API signatures are changed;
filter_modeis added as a new keyword-only argument with a default.
Security & Risk
All reviewers agreed: no security concerns identified. This is a purely UI/UX feature with no changes to routing, authentication, session management, WebSocket handling, dependencies, external requests, or dynamic code execution. The frontend gracefully falls back to "fuzzy" for unknown filter modes, improving mixed-version resilience.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence: All changes are confined to widget filtering behavior (proto field addition, Python validation, frontend filter logic, and component props). No routing, auth, WebSocket, embedding, asset serving, CORS, security header, or runtime changes.
- Confidence: High (unanimous across all reviewers)
- Assumptions and gaps: None — the change surface is entirely within widget UI logic.
Accessibility
All reviewers confirmed good accessibility handling:
- When
filter_mode=None, the input correctly receives areadonlyattribute, preventing typing while keeping the combobox role and ARIA label intact. - The dropdown remains navigable via keyboard with
filter_mode=None(readonly on the input doesn't prevent dropdown interaction). - The select widget retains its accessible name (
aria-label) regardless of filter mode. - No new interactive elements are introduced that would need additional ARIA attributes.
- This is also a UX improvement for mobile, where
filter_mode=Noneprevents the keyboard from popping up for short option lists.
Recommendations
- Consider extracting
_validate_filter_modeinto a shared utility (e.g., instreamlit/elements/lib/) to reduce near-identical duplication betweenselectbox.pyandmultiselect.py. The widget name for the error message could be passed as a parameter. (Flagged by 2 of 3 reviewers; non-blocking.) - Verify the removal of
"ignoreUnresolved"infrontend/knip.jsonwas intentional and doesn't causeknipto fail. If unrelated, consider separating it into its own commit. (Flagged by 2 of 3 reviewers; non-blocking.)
Verdict
APPROVED: A clean, well-tested, and backwards-compatible feature addition with unanimous approval from all three reviewers. The filter mode is properly wired through all layers (proto, backend, frontend) with comprehensive test coverage, no security risks, and no regression concerns.
Consolidated AI review by claude-4.6-opus-high-thinking from 3 of 3 expected reviews (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high). All expected models completed their reviews successfully.
This review also includes 2 inline comment(s) on specific code lines.
Centralize select widget filter_mode validation in shared option utilities and sync the related spec and feature-guide docs. This keeps the selectbox and multiselect code paths aligned while preserving the shipped behavior.
- Align knip.json with develop so generated protobuf imports do not fail Knip on a clean tree. - Use locale-invariant toLowerCase() for select option filter normalization (review feedback).
There was a problem hiding this comment.
Summary
This PR adds a filter_mode parameter to st.selectbox and st.multiselect with four modes: "fuzzy" (default, preserving current behavior), "contains" (case-insensitive substring), "prefix" (case-insensitive prefix), and None (disables typing/filtering entirely). The implementation spans protobuf definitions, Python backend validation, frontend filtering logic in a shared hook, and comprehensive test coverage at all layers.
Reviewer agreement: All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) independently approved this PR, with strong consensus on code quality, test coverage, backwards compatibility, and absence of security concerns.
Code Quality
The implementation is clean and well-structured, following established Streamlit patterns:
- Shared utilities: Backend validation is centralized in
validate_select_widget_filter_modeinoptions_selector_utils.py. Frontend filtering is centralized infilterSelectOptionsinfuzzyFilterSelectOptions.ts, and the shareduseSelectCommonhook handles normalization and read-only state for both widgets. - Defensive normalization:
normalizeSelectFilterModefalls back to"fuzzy"for unknown or missing values, ensuring forward/backward compatibility across Streamlit versions. - Widget identity:
filter_modeis correctly added tokey_as_main_identityfor both widgets, with docstrings noting the state-reset behavior. - Unrelated changes: The
knip.jsonchange (removingignoreUnresolvedfor proto imports) andwiki/new-feature-guide.mdfix (yarn vitest→yarn test) appear to be drive-by cleanups. Theknip.jsonchange should be verified to not cause regressions (one reviewer flagged this).
Test Coverage
All three reviewers agreed that test coverage is thorough and well-layered:
- Python unit tests: Default value assertion, non-default modes,
None→"none"serialization, invalid mode rejection,None+accept_new_options=Trueincompatibility, and stable key kwargs. - Typing tests:
filter_mode="contains"andfilter_mode=Nonefor both widgets. - Frontend unit tests: All four modes tested for
Selectbox;Multiselectcovers contains and none modes. One reviewer optionally suggested adding aprefixmode unit test forMultiselectfor parity (covered in E2E). - E2E tests: Prefix, contains, and none modes tested for both widgets with good negative assertions.
Backwards Compatibility
No breaking changes. All three reviewers confirmed full backwards compatibility:
- Default
filter_mode="fuzzy"preserves current behavior exactly. - The parameter is keyword-only, so existing positional call signatures are unaffected.
- Protobuf fields are additive optional, safely ignored by older frontends and normalized by newer ones.
- The intentional widget-state reset on
filter_modechange is documented in docstrings.
Security & Risk
No security concerns identified by any reviewer. All changes are scoped to widget-level filtering logic — no new dependencies, external requests, auth/session changes, or injection risks. The readonly attribute for filter_mode=None uses standard HTML input behavior.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence: All changes are confined to widget-level filtering (protobuf fields, Python API parameters, frontend filter functions, shared hook). No routing, auth, WebSocket transport, embedding, asset serving, cross-origin behavior, security headers, or runtime integrations are modified.
- Confidence: High (all three reviewers independently reached the same conclusion)
- Assumptions and gaps: None
Accessibility
All three reviewers confirmed accessibility is maintained:
- The
readonlyattribute on the input whenfilter_mode=Noneis semantically correct and properly communicated to assistive technology. - The dropdown remains fully accessible via click/keyboard interaction when filtering is disabled.
- No changes to ARIA attributes, roles, or focus management. Existing accessibility patterns are preserved.
Recommendations
- Verify
knip.jsonchange: Confirm that removing theignoreUnresolvedpattern for proto imports doesn't causemake frontend-knipregressions, since this change appears unrelated to the feature. (Raised by 1 of 3 reviewers) - Docstring formatting (optional): Consider using bullet points for the
filter_modeliteral options in bothselectbox.pyandmultiselect.pyperlib/streamlit/AGENTS.mdguidelines. The current prose style matches the existinglabel_visibilitypattern, so this is a low-priority consistency improvement. (Raised by 1 of 3 reviewers) - Multiselect prefix unit test (optional): Adding a
filterMode="prefix"unit test inMultiselect.test.tsxwould provide parity with the contains-mode unit test, though this behavior is already covered in E2E tests. (Raised by 1 of 3 reviewers)
Verdict
APPROVED: This is a well-implemented, thoroughly tested, backwards-compatible feature addition that follows established Streamlit patterns. All three reviewers approved unanimously with no critical or blocking issues identified. The minor recommendations above are optional improvements.
Consolidated AI review by claude-4.6-opus-high-thinking from 3 individual reviews (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high). All expected models completed their reviews successfully.
This review also includes 4 inline comment(s) on specific code lines.
There was a problem hiding this comment.
Summary
This PR adds a filter_mode parameter to st.selectbox and st.multiselect, supporting four modes: "fuzzy" (default, preserving current behavior), "contains" (case-insensitive substring match), "prefix" (case-insensitive prefix match), and None (disables typing/filtering entirely). The feature is implemented end-to-end across protobuf definitions, Python backend, and React frontend, with shared filtering logic centralized in useSelectCommon and fuzzyFilterSelectOptions.ts.
All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) completed their reviews. Two approved outright; one requested changes based on a single inline comment (see below).
Code Quality
Reviewers unanimously agreed the implementation is clean, well-structured, and follows existing codebase patterns:
- Backend: Validation is centralized in
validate_select_widget_filter_modewithinoptions_selector_utils.py, keeping both widget modules thin. TheSelectWidgetFilterModetype alias is well-defined and shared. - Frontend:
normalizeSelectFilterModeinfuzzyFilterSelectOptions.tsprovides a clean normalization boundary. ThefilterSelectOptionsdispatcher delegates to strategy-specific logic, anduseSelectCommonintegrates filter mode with proper memoization. TheinputReadOnlyderivation is computed during render rather than viauseEffect, correctly following codebase guidelines. - Protobuf: New
optional string filter_modefields with correct field numbers and updated// Next:comments. - Docstrings: Thorough, NumPy-style, and clearly document the
filter_mode=None/accept_new_options=Trueincompatibility.
The filter_mode is included in key_as_main_identity for widget ID computation, correctly resetting the widget when the filter mode changes.
One inline comment was raised by gpt-5.3-codex-high regarding a TypeError when unhashable types (e.g., list, dict) are passed to filter_mode validation. This is technically valid — frozenset.__contains__ calls __hash__ — but is a low-impact robustness edge case: the parameter is typed as Literal["fuzzy", "contains", "prefix"] | None, so type checkers catch misuse, and even at runtime the user still receives an error (just TypeError instead of StreamlitAPIException). This is not a blocking issue but is retained as a suggestion in the inline comments.
Test Coverage
All reviewers agreed test coverage is thorough across all layers:
- Python unit tests: Default value, each non-default mode,
None→"none"serialization, invalid mode rejection,None+accept_new_options=Trueerror, and widget key identity changes via parameterized tests. - Typing tests: Verify
filter_mode="contains"andfilter_mode=Noneproduce correct return types. - Frontend unit tests: Cover
contains,prefix, andnonemodes for both Selectbox and Multiselect. Thenonemode tests verifyreadonlyattribute and full option visibility.normalizeSelectFilterModefallback behavior is tested forundefinedand unknown values. - E2E tests: Cover prefix-only matching, contains substring matching, and
filter_mode=None(readonly input + dropdown) for both widgets. Widget counts are properly incremented.
Minor observation (non-blocking): explicit test cases for normalizeSelectFilterMode("fuzzy") and normalizeSelectFilterMode(null) would improve documentation, though both fall through to the default case correctly.
Backwards Compatibility
All reviewers unanimously confirmed full backward compatibility:
filter_mode="fuzzy"is the default, producing identical behavior to current release.- Protobuf field is
optional string— old frontends ignore it and continue fuzzy filtering; old backends that don't send it result inundefined, whichnormalizeSelectFilterModemaps to"fuzzy". - No breaking API changes; parameter is keyword-only with a sensible default.
- Existing widgets without
filter_modecontinue to have the same element ID.
Security & Risk
No security concerns identified by any reviewer. Changes are purely UI filtering behavior — no impact on authentication, session management, WebSocket handling, file serving, CORS, or external networking. No new dependencies, no eval/exec, no HTML rendering changes.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence:
- All changes are confined to widget filtering behavior (protobuf field additions, Python parameter plumbing, frontend filter logic).
- No changes to routing, authentication, WebSocket handling, embedding, asset serving, CORS, security headers, or storage.
- Protobuf additions are additive
optionalfields with no impact on message compatibility.
- Suggested external_test focus areas: N/A
- Confidence: High (all three reviewers concur)
- Assumptions and gaps: None
Accessibility
All reviewers agreed the accessibility impact is minimal and positive:
filter_mode=Noneusesreadonlyon the input, correctly preventing typing while keeping the combobox focusable and operable via keyboard (Enter/Space to open dropdown).- No changes to ARIA attributes, focus management, or keyboard navigation patterns.
- Screen readers will continue to announce the combobox role and options correctly.
Recommendations
- (Optional) Guard against unhashable
filter_modeinputs invalidate_select_widget_filter_modewith atry/except TypeErrorwrapper around theincheck, converting toStreamlitAPIExceptionfor a cleaner user-facing error. This is a minor robustness improvement, not a blocking concern. - (Optional) Add explicit test cases for
normalizeSelectFilterMode("fuzzy")andnormalizeSelectFilterMode(null)infuzzyFilterSelectOptions.test.tsto document common-path behavior. - (Optional) Consider adding runnable
Examplessections forfilter_modein the docstrings of bothselectbox.pyandmultiselect.py.
Verdict
APPROVED: This is a well-designed, thoroughly tested, and fully backward-compatible feature addition. All three reviewers agree the implementation is clean and follows existing patterns. The single inline comment is a minor robustness suggestion (not blocking), and two of three reviewers approved outright. No critical or blocking issues were identified.
Consolidated AI review by claude-4.6-opus-high-thinking. Individual reviews: claude-4.6-opus-high-thinking (APPROVED), gemini-3.1-pro (APPROVED), gpt-5.3-codex-high (CHANGES_REQUESTED). Please verify the feedback and use your judgment.
This review also includes 1 inline comment(s) on specific code lines.
Tighten filter_mode validation to return a Streamlit API error for unhashable inputs and strengthen the shared Selectbox tests with explicit negative assertions. This addresses the remaining actionable review feedback without changing widget behavior.
The filter_mode feature is now implemented, so remove it as an example of a hypothetical feature addition from the spec documentation. Co-Authored-By: Claude Opus 4.6 <[email protected]>
There was a problem hiding this comment.
Summary
This PR adds a filter_mode parameter to st.selectbox and st.multiselect, supporting four modes: "fuzzy" (default, existing behavior), "contains" (case-insensitive substring), "prefix" (case-insensitive prefix), and None (disables typing/filtering entirely). The implementation spans all layers: a new shared protobuf enum (SelectWidgetFilterMode), backend validation and serialization in Python, frontend filtering logic, and comprehensive test coverage across unit tests, typing tests, and E2E tests.
Code Quality
The implementation is clean and well-structured:
- Protobuf:
FILTER_MODE_FUZZY = 0as the zero-value default is the correct choice for backwards compatibility. The enum is extracted into its own shared proto file and imported by bothSelectbox.protoandMultiSelect.proto. - Backend: Validation is centralized in
validate_select_widget_filter_mode()with clear error messages. The validation correctly handles unhashable inputs viaTypeErrorcatch and enforces the logical constraint thatfilter_mode=Noneis incompatible withaccept_new_options=True. The type aliasSelectWidgetFilterMode = Literal["fuzzy", "contains", "prefix"] | Noneprovides good IDE support. - Frontend: The filtering logic is cleanly extracted into
filterSelectOptions()which dispatches to the appropriate strategy. TheuseSelectCommonhook properly normalizes the filter mode and memoizes the filter function withnormalizedFilterModein the dependency array. Thereadonlyinput behavior forFILTER_MODE_NONEis implemented at the shared hook level, benefiting both widgets. - Docstrings: Parameter documentation follows the existing NumPy-style conventions and clearly describes each mode's behavior.
Minor documentation fix in wiki/new-feature-guide.md (yarn vitest → yarn test) is a nice bonus.
Test Coverage
Test coverage is thorough across all layers:
- Python unit tests: Cover default mode assertion, each non-default mode, invalid mode rejection, and the
filter_mode=None + accept_new_options=Trueincompatibility for both widgets. Stable ID tests verifyfilter_modeis included in element identity but not in thekey_as_main_identitywhitelist. Typing tests (selectbox_types.py,multiselect_types.py) verify type inference. - Validation unit tests: Cover known mode validation and unhashable input rejection.
- Frontend unit tests: Cover contains mode, prefix mode, and none mode (readonly input + all options visible) for both
SelectboxandMultiselectcomponents, plus the sharedfilterSelectOptionsutility. - E2E tests: Cover prefix-only matching, contains-only matching, and none mode (readonly + selection still works) for both widgets. Tests include negative assertions (excluded options are absent).
Backwards Compatibility
This change is fully backwards compatible:
- The default value of
filter_modeis"fuzzy", preserving existing behavior for all current users. - The protobuf enum uses
FILTER_MODE_FUZZY = 0as the zero value, so messages from older backends (without the field) default to fuzzy mode on the frontend. - The frontend
getSelectFilterMode()helper defaultsnull/undefinedtoFILTER_MODE_FUZZY, handling any edge cases with missing proto fields. - The parameter is keyword-only (added after
*) in the public API, so it doesn't affect positional argument ordering.
Security & Risk
No security concerns identified:
- No new dependencies (frontend or backend).
- No external assets or network requests.
- No
eval/exec/Function()usage. - Filter logic is pure, in-memory string matching.
- No changes to routing, auth, WebSocket, CORS, or CSP.
- No changes to file serving or path handling.
- The
readonlyinput attribute forfilter_mode=Noneis a standard HTML attribute with no security implications.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence:
- All changes are scoped to widget filtering behavior (
selectbox/multiselectoptions filtering UI) - No changes to routing, auth, cookies, WebSocket, embedding, asset serving, CORS, security headers, or storage
- No changes to
lib/streamlit/web/,lib/streamlit/runtime/, or connection handling
- All changes are scoped to widget filtering behavior (
- Suggested external_test focus areas: N/A
- Confidence: High
- Assumptions and gaps: None — the change is entirely within widget rendering and filtering logic, with no external integration surface.
Accessibility
- The
readonlyattribute on the input whenfilter_mode=Noneis the correct approach: the input remains focusable, screen readers still announce it as a combobox, and keyboard navigation continues to work. Only character input is suppressed. - No new interactive elements, ARIA attributes, or focus management changes are introduced.
- The existing keyboard and screen reader accessibility of the selectbox/multiselect widgets is preserved.
Recommendations
- (Low priority) In
filterSelectOptions(), the finalreturn normalizedLabel.startsWith(normalizedPattern)acts as a catch-all for bothFILTER_MODE_PREFIXand any unknown future enum values. Consider an explicitFILTER_MODE_PREFIXcheck for defensive clarity. - (Low priority)
test_validates_known_modesinoptions_selector_utils_test.pycovers 2 of 4 modes. Consider@pytest.mark.parametrizeto cover all four modes explicitly for completeness.
Verdict
APPROVED: A well-implemented, fully backwards-compatible feature addition with thorough test coverage across all layers. The code follows established patterns, protobuf design is sound, and no security or compatibility risks were identified.
This is an automated AI review by claude-4.6-opus-high-thinking.
This review also includes 2 inline comment(s) on specific code lines.
lib/tests/streamlit/elements/lib/options_selector_utils_test.py
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Summary
This PR adds a filter_mode parameter to st.selectbox and st.multiselect, supporting four modes: "fuzzy" (default, preserves existing behavior), "contains" (case-insensitive substring), "prefix" (case-insensitive prefix), and None (disables typing/filtering entirely). The implementation spans all layers: a new shared protobuf enum (SelectWidgetFilterMode), backend validation and serialization in Python, frontend filtering logic, and comprehensive test coverage across unit tests, typing tests, and E2E tests.
All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) approved the PR unanimously, with only minor, non-blocking suggestions.
Code Quality
Reviewers agree the implementation is clean, well-structured, and follows established codebase patterns across all layers:
- Protobuf:
FILTER_MODE_FUZZY = 0as the zero-value default is the correct choice for backwards compatibility. The enum is extracted into its own shared proto file and imported by bothSelectbox.protoandMultiSelect.proto. - Backend: Validation is centralized in
validate_select_widget_filter_mode()with robust error handling (including unhashable inputs viaTypeErrorcatch) and the logical constraint thatfilter_mode=Noneis incompatible withaccept_new_options=True. - Frontend: Filtering logic is cleanly extracted into
filterSelectOptions()which dispatches to the appropriate strategy. TheuseSelectCommonhook properly normalizes the filter mode and thereadonlyinput behavior forFILTER_MODE_NONEbenefits both widgets at the shared hook level. - Docstrings: Follow existing NumPy-style conventions and clearly describe each mode's behavior.
Two reviewers (claude-4.6-opus-high-thinking, gpt-5.3-codex-high) noted that the filterSelectOptions() function falls through to prefix-matching behavior for unknown enum values. An explicit check for FILTER_MODE_PREFIX with a safe default would be more defensive for future-proofing (see inline comment).
Test Coverage
All reviewers agree that test coverage is thorough and well-layered:
- Python unit tests: Cover default mode assertion, each non-default mode, invalid mode rejection, unhashable input rejection, and
filter_mode=None + accept_new_options=Trueincompatibility for both widgets. Stable ID tests verifyfilter_modeis included in element identity. - Typing tests:
assert_typechecks for the new parameter in bothselectbox_types.pyandmultiselect_types.py. - Frontend unit tests: Cover contains, prefix, and none mode (readonly input + all options visible) for both
SelectboxandMultiselectcomponents, plus the sharedfilterSelectOptionsutility. - E2E tests: Cover prefix-only matching, contains-only matching, and none mode for both widgets. Tests include negative assertions (excluded options are absent).
One minor gap noted: test_validates_known_modes covers only 2 of 4 modes at the validation-function level (see inline comment).
Backwards Compatibility
All reviewers agree the change is fully backwards compatible:
- Default
filter_modeis"fuzzy", preserving existing behavior. - Protobuf zero value is
FILTER_MODE_FUZZY, so messages from older backends default correctly. - The frontend
getSelectFilterMode()helper defaultsnull/undefinedtoFILTER_MODE_FUZZY. - The parameter is keyword-only in the public API, so positional argument ordering is unaffected.
One additional observation (gpt-5.3-codex-high only): Unkeyed widget IDs now include filter_mode in identity inputs via compute_and_register_element_id, which could cause a one-time state reset for keyless select widgets after upgrade. This is a minor compatibility caveat worth noting in release notes, though it is standard practice for new parameters.
Security & Risk
All reviewers agree there are no security concerns:
- No new dependencies, external requests, or risky runtime execution.
- No changes to routing, auth, WebSocket, CORS, CSP, or file serving.
- Filter logic is pure, in-memory string matching.
- The
readonlyattribute forfilter_mode=Noneis a standard HTML attribute.
External test recommendation
- Recommend external_test: No
- Triggered categories: None
- Evidence: All changes are scoped to widget filtering behavior (
selectbox/multiselectoption filtering UI). No changes to routing, auth, cookies, WebSocket, embedding, asset serving, CORS, security headers, or storage. - Suggested external_test focus areas: N/A
- Confidence: High (unanimous across all three reviewers)
- Assumptions and gaps: None — the change is entirely within widget rendering and filtering logic.
Accessibility
All reviewers agree the approach is correct: the readonly attribute on the input when filter_mode=None preserves focusability, screen reader announcements, and keyboard navigation while suppressing only character input. No new interactive elements, ARIA attributes, or focus management changes are introduced.
Recommendations
- (Low) Consider an explicit
FILTER_MODE_PREFIXcase infilterSelectOptions()so unknown enum values don't silently degrade to prefix matching. Two of three reviewers flagged this. - (Low) Consider using
@pytest.mark.parametrizeto cover all four modes intest_validates_known_modesfor completeness. - (Low) Consider a brief release note mentioning the potential one-time state reset for keyless select widgets due to identity input changes (raised by gpt-5.3-codex-high).
Verdict
APPROVED: All three reviewers unanimously approved this PR. The feature is well-implemented with clean code, sound protobuf design, thorough cross-layer test coverage, full backwards compatibility, and no security or accessibility concerns. Only minor, non-blocking improvements were suggested.
Consolidated AI review by claude-4.6-opus-high-thinking from 3/3 expected models (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high). All models completed their reviews successfully.
This review also includes 2 inline comment(s) on specific code lines.

Describe your changes
filter_modetost.selectboxandst.multiselectwith"fuzzy","contains","prefix", andNonemodes.GitHub Issue Link (if applicable)
st.selectbox#6160specs/2026-03-25-selectbox-filter-mode/product-spec.mdTesting Plan
make allmake checkE2E_CHECK=true make checkuv run pytest lib/tests/streamlit/elements/selectbox_test.py lib/tests/streamlit/elements/multiselect_test.pycd frontend && yarn test lib/src/util/fuzzyFilterSelectOptions.test.ts lib/src/components/shared/Dropdown/Selectbox.test.tsx lib/src/components/widgets/Multiselect/Multiselect.test.tsx lib/src/components/widgets/Selectbox/Selectbox.test.tsxmake frontend-fastmake run-e2e-test e2e_playwright/st_selectbox_test.pywithPYTEST_ADDOPTS='-k "widget_rendering or has_correct_initial_values or prefix_filter_mode or contains_filter_mode or filter_mode_none"'make run-e2e-test e2e_playwright/st_multiselect_test.pywithPYTEST_ADDOPTS='-k "initial_value or prefix_filter_mode or contains_filter_mode or filter_mode_none"'Agent metrics