[feat] Add on_click rerun support to st.link_button#14116
[feat] Add on_click rerun support to st.link_button#14116lukasmasuch merged 7 commits intodevelopfrom
on_click rerun support to st.link_button#14116Conversation
This adds rerun and callback handling for link buttons while keeping the default ignore behavior, and wires frontend trigger handling plus tests for key/on_click semantics. Co-authored-by: Cursor <[email protected]>
This covers the disabled + rerun-enabled path to ensure disabled link buttons never trigger widget reruns. Co-authored-by: Cursor <[email protected]>
Keep behavior unchanged while simplifying link-button code paths, and enforce key validation in ignore mode to match widget policy expectations. Co-authored-by: Cursor <[email protected]>
✅ 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.
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.
frontend/lib/src/components/elements/LinkButton/LinkButton.test.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
This PR adds on_click, args, and kwargs support to st.link_button, enabling callback execution and click-triggered reruns while maintaining backward compatibility with the default on_click="ignore" behavior. The changes bring link_button closer to the feature parity of other button widgets like st.button and st.download_button.
Changes:
- Adds
on_click,args,kwargs, andkeyparameters tost.link_buttonwith three modes: "ignore" (default, no rerun), "rerun" (triggers rerun without callback), and callable (triggers rerun with callback) - Implements widget registration and state management for non-ignore modes while preserving the lightweight behavior of ignore mode
- Ensures key-based identity stability across all modes and validates keys even in ignore mode for consistency
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
proto/streamlit/proto/LinkButton.proto |
Adds ignore_rerun boolean field and updates comment to reflect ID usage for keys and callbacks |
lib/streamlit/elements/widgets/button.py |
Implements on_click/args/kwargs support in link_button with conditional widget registration, key validation, and icon_position fix in ID computation |
lib/tests/streamlit/elements/link_button_test.py |
Adds tests for key validation in ignore mode, rerun enablement for callback/rerun modes, and ID generation |
lib/tests/streamlit/elements/button_test.py |
Validates stable key-based identity across rerun and ignore modes with varying parameters |
lib/tests/streamlit/typing/button_types.py |
Adds type assertions for new parameters (key, on_click, args, kwargs) covering all usage patterns |
frontend/lib/src/components/elements/LinkButton/LinkButton.tsx |
Implements click handler that conditionally calls setTriggerValue based on ignoreRerun flag, respecting disabled state and fragment context |
frontend/lib/src/components/elements/LinkButton/LinkButton.test.tsx |
Tests rerun triggering for ignore/rerun modes, fragment handling, and disabled state behavior |
frontend/lib/src/components/core/Block/ElementNodeRenderer.tsx |
Passes widgetMgr and fragmentId props to LinkButton component for state management |
Co-authored-by: Cursor <[email protected]>
Document that link_button args and kwargs are only used when on_click is callable, and are ignored for rerun/ignore modes. Co-authored-by: Cursor <[email protected]>
Consolidated Code Review: PR #14116 — Add
|
| Aspect | gpt-5.3-codex-high | opus-4.6-thinking | Consensus |
|---|---|---|---|
| Code quality | No issues | No issues | Agreed |
| Test coverage | Good, E2E gap noted | Solid, E2E gap noted | Agreed |
| Backwards compat | Fully compatible | Fully compatible | Agreed |
| Security | No concerns | No concerns | Agreed |
| Accessibility | No regressions | No regressions | Agreed |
| Verdict | APPROVED | APPROVED | APPROVED |
No disagreements were found between reviewers. Both independently identified the same non-blocking E2E test gap and reached the same approval verdict.
Verdict
APPROVED: Both reviewers approved. The implementation is consistent with established patterns, fully backwards compatible, and thoroughly tested across backend, frontend, and typing layers. The only noted gap (missing E2E test) is non-blocking and suitable as a follow-up.
Consolidated review by opus-4.6-thinking. Individual reviews from 2/2 expected models received.
📋 Review by `gpt-5.3-codex-high`
Summary
This PR adds on_click, args, kwargs, and key support to st.link_button while preserving prior default behavior (on_click="ignore"). The implementation updates backend widget wiring, frontend click handling, and protobuf shape, with accompanying Python/frontend/typing tests.
Code Quality
Overall code structure is solid and follows existing patterns used by button/download_button:
- Backend changes in
lib/streamlit/elements/widgets/button.pyare cohesive and keep behavior centralized in_link_button. - Frontend wiring in
frontend/lib/src/components/core/Block/ElementNodeRenderer.tsxandfrontend/lib/src/components/elements/LinkButton/LinkButton.tsxis straightforward and uses the existingWidgetStateManagertrigger path correctly. - Test updates are targeted and readable across backend, frontend, and typing suites.
No blocking code-quality issues were identified.
Test Coverage
Coverage is good for unit-level behavior:
- Backend tests validate ignore/rerun state signaling, key behavior, invalid key rejection, and stable key-based identity (
lib/tests/streamlit/elements/link_button_test.py,lib/tests/streamlit/elements/button_test.py). - Frontend tests verify rerun trigger behavior, fragment propagation, default ignore behavior, and disabled behavior (
frontend/lib/src/components/elements/LinkButton/LinkButton.test.tsx). - Typing tests cover the newly exposed public API surface (
lib/tests/streamlit/typing/button_types.py).
Residual gap: there is no e2e test validating real browser behavior for "open new tab + rerun/callback trigger" in one flow.
Backwards Compatibility
Backwards compatibility looks good:
- Default
st.link_buttonbehavior remains non-rerunning (on_click="ignore"). - New parameters are optional keyword arguments, so existing call sites continue to work.
- The new protobuf field (
ignore_rerun) is additive, which is wire-compatible.
No breaking API changes were found.
Security & Risk
No security issues were identified in this change set:
- No new privileged operations, unsafe parsing, or dynamic code execution paths were introduced.
- Callback execution continues through established widget callback infrastructure.
Main regression risk is behavioral (UI flow), not security: browser-level interaction timing between navigation and rerun is not covered by e2e.
Accessibility
Frontend changes preserve good accessibility semantics:
LinkButtonremains an anchor-based control (<a href>behavior viaBaseLinkButton), which is appropriate for navigation.- No new non-semantic clickable containers were introduced.
- No ARIA regressions were observed in the modified code paths.
Recommendations
- Add one focused e2e test for
st.link_button(on_click="rerun")(and optionally callable mode) to validate end-to-end browser behavior, including fragment context.
Verdict
APPROVED: The implementation is consistent, backward-compatible, and sufficiently covered by unit/typing tests, with only a non-blocking e2e coverage gap.
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 on_click, args, kwargs, and key support to st.link_button, following the same pattern established by st.download_button. The default on_click="ignore" preserves the existing no-rerun behavior. When set to "rerun" or a callable, the link still opens in a new tab but the app also triggers a rerun (optionally invoking the callback). The implementation spans the full stack:
- Proto: Adds
bool ignore_rerun = 13toLinkButton.proto. - Backend: Extends
link_button()and_link_button()with new parameters; conditionally registers the widget and validates keys. - Frontend:
LinkButtoncomponent gainswidgetMgrandfragmentIdprops;handleClickconditionally callssetTriggerValue. - Tests: Backend unit tests, frontend unit tests, and typing tests are added/updated.
Code Quality
The code is clean, well-structured, and follows existing patterns closely. Specific observations:
- The backend implementation in
_link_button(lib/streamlit/elements/widgets/button.py:1283-1375) closely mirrors the_download_buttonpattern foron_clickhandling, which promotes consistency. - The
should_register_element_idlogic (line 1314-1316) correctly handles the three distinct cases (rerun mode, key provided, shortcut provided) for determining when an element ID is needed. - The
check_widget_policiesguard (line 1317) correctly only fires when the element acts as a widget (rerun mode or key provided), not for the shortcut-only path. - The
key_as_main_identityexpression (not ignore_rerun or key is not None, line 1330) ensures stable identity when the link button is used as a widget, while preserving content-based identity for the shortcut-only case. - The frontend
handleClickcallback (line 61-73 ofLinkButton.tsx) correctly guards the trigger with both!element.ignoreRerunandelement.id, and the dependency array[element, fragmentId, widgetMgr]is complete and correct. - The change from
{...elementProps}to explicitwidgetMgrandfragmentIdprops inElementNodeRenderer.tsx(line 915-919) is an improvement — the old code was spreading unused props (disableFullscreenMode,widthConfig,heightConfig) thatLinkButtonnever consumed. - Minor style improvement:
element.shortcut || undefined(line 42) is equivalent to the previous ternary and is more concise. - The
icon_positionparameter is now included incompute_and_register_element_id(line 1338), which was missing before. This is a minor improvement for identity uniqueness in the shortcut-only case.
Test Coverage
Test coverage is solid across all layers:
- Python unit tests (
link_button_test.py): Tests the defaultignore_rerunflag, key-setting in ignore mode, invalid key rejection (empty and reserved prefix), rerun/callback mode enabling. Good use of@parameterized.expandand anti-regression assertions. - Python unit tests (
button_test.py): Tests stable key-based identity for both rerun/callback and ignore modes, including varying callback args. - Frontend unit tests (
LinkButton.test.tsx): Tests no-rerun by default, rerun with/without fragmentId (it.each), and disabled-prevents-rerun. UsesWidgetStateManagerspy pattern properly. - Typing tests (
button_types.py): Comprehensiveassert_typecoverage for the newkey,on_click,args,kwargsparameters. - E2E tests: Not added. The PR justification — "behavior is covered by backend + frontend unit tests for this API-focused change" — is reasonable. The underlying
setTriggerValuemechanism is already E2E-tested via other widgets. The existing E2E tests inst_link_button_test.pystill pass since the default behavior is unchanged.
One minor gap: there is no test verifying that the shortcut path still works correctly with on_click="rerun" (i.e., that pressing a shortcut on a link button with on_click="rerun" triggers both the link navigation and the rerun). This is covered implicitly by the existing shortcut E2E test and the unit test for setTriggerValue, but an explicit test could add confidence.
Backwards Compatibility
This change is fully backwards compatible:
- All new parameters (
key,on_click,args,kwargs) are keyword-only with sensible defaults (None,"ignore",None,None). - The default
on_click="ignore"preserves the existing no-rerun, no-widget-registration behavior. - The
_link_buttoninternal method signature changeshelpfrom a required positional parameter to an optional keyword parameter, but this method is private and the only caller is the publiclink_buttonmethod (confirmed via search). - The protobuf addition (
ignore_rerun = 13) is purely additive. The proto3 default offalsecombined with the frontend guard!element.ignoreRerun && element.idensures safe behavior: old backends that don't set this field will haveelement.idas empty string for non-shortcut link buttons, so the rerun path won't fire.
Security & Risk
No security concerns identified:
- The
on_clickcallback mechanism uses the sameregister_widget/check_widget_policiesinfrastructure as existing widgets. writes_allowed=Falsecorrectly prevents session state writes.- No new external inputs or URL handling is introduced.
- The link still opens via the native
<a href>mechanism in a new tab; the rerun is a separate side effect.
Accessibility
No accessibility regressions:
- The component continues to render a semantic
<a>element witharia-disabled,target="_blank", andrel="noreferrer". - No new interactive elements are introduced.
- The keyboard shortcut integration is unchanged.
- The click handler correctly prevents link navigation when
disabled, preserving the existing behavior for assistive technology users.
Recommendations
- Consider adding an E2E test scenario for
on_click="rerun"to the existingst_link_button_test.pyfile. While unit tests cover the mechanism well, a single E2E scenario would verify the full-stack callback flow (click link button -> app reruns -> callback output visible). This could be a follow-up. - Docstring example: The docstring for
link_buttononly shows the basic example (st.link_button("Go to gallery", ...)). Consider adding a brief example showingon_clickusage in a follow-up, similar to howst.download_buttondocuments itson_clickparameter in examples.
Verdict
APPROVED: Well-implemented feature that follows established patterns, is fully backwards compatible, and has thorough test coverage across all layers.
This is an automated AI review by opus-4.6-thinking.
on_click rerun support to st.link_button
on_click rerun support to st.link_buttonon_click rerun support to st.link_button
Align link_button with button-like trigger semantics for non-ignore on_click paths, and add unit, typing, and e2e coverage for return values and keyed CSS class behavior. Made-with: Cursor
Describe your changes
st.link_buttonsoon_click=\"rerun\"and callableon_clickreturn abool(Falseby default,Trueafter click), matchingst.buttontrigger semantics.on_click=\"ignore\"returningDeltaGenerator, and add API overloads + doc updates to make return types explicit.GitHub Issue Link (if applicable)
Testing Plan
make checkuv run pytest lib/tests/streamlit/elements/button_test.py -k link_buttonmake run-e2e-test e2e_playwright/st_link_button_test.py::test_link_button_click_calls_callbackmake run-e2e-test e2e_playwright/st_link_button_test.py::test_link_button_click_returns_true_for_rerunmake run-e2e-test e2e_playwright/st_link_button_test.py::test_custom_css_class_via_key