Skip to content

[feat] Add on_click rerun support to st.link_button#14116

Merged
lukasmasuch merged 7 commits intodevelopfrom
feature/link-button-on-click
Mar 2, 2026
Merged

[feat] Add on_click rerun support to st.link_button#14116
lukasmasuch merged 7 commits intodevelopfrom
feature/link-button-on-click

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Feb 24, 2026

Describe your changes

  • Update st.link_button so on_click=\"rerun\" and callable on_click return a bool (False by default, True after click), matching st.button trigger semantics.
  • Keep on_click=\"ignore\" returning DeltaGenerator, and add API overloads + doc updates to make return types explicit.
  • Add/expand unit, typing, and e2e coverage for callback/rerun return values and keyed CSS class behavior.

GitHub Issue Link (if applicable)

Testing Plan

  • make check
  • uv run pytest lib/tests/streamlit/elements/button_test.py -k link_button
  • make run-e2e-test e2e_playwright/st_link_button_test.py::test_link_button_click_calls_callback
  • make run-e2e-test e2e_playwright/st_link_button_test.py::test_link_button_click_returns_true_for_rerun
  • make run-e2e-test e2e_playwright/st_link_button_test.py::test_custom_css_class_via_key

lukasmasuch and others added 3 commits February 24, 2026 20:03
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]>
Copilot AI review requested due to automatic review settings February 24, 2026 20:20
@lukasmasuch lukasmasuch added change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 24, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

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

✅ PR preview is ready!

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

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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 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, and key parameters to st.link_button with 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

lukasmasuch and others added 2 commits February 24, 2026 23:01
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]>
@streamlit streamlit deleted a comment from lukasmasuch Feb 24, 2026
@sfc-gh-lmasuch sfc-gh-lmasuch added the ai-review If applied to PR or issue will run AI review workflow label Feb 24, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 25, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review: PR #14116 — Add on_click support to link_button

Summary

This PR adds on_click, args, kwargs, and key support to st.link_button, following the established pattern used by st.download_button. The default on_click="ignore" preserves existing no-rerun behavior. When set to "rerun" or a callable, the link still opens in a new tab but also triggers an app rerun (optionally invoking the callback). The implementation spans the full stack:

  • Proto: Adds bool ignore_rerun = 13 to LinkButton.proto (additive, wire-compatible).
  • Backend (lib/streamlit/elements/widgets/button.py): Extends link_button() and _link_button() with new parameters; conditionally registers the widget and validates keys.
  • Frontend (LinkButton.tsx): Gains widgetMgr and fragmentId props; handleClick conditionally calls setTriggerValue.
  • ElementNodeRenderer.tsx: Switches from spreading elementProps to explicit widgetMgr and fragmentId props (cleaner — avoids passing unused props).
  • Tests: Backend unit tests, frontend unit tests, and typing tests are added/updated across 5 test files.

Code Quality

Reviewers agreed: The code is clean, well-structured, and closely follows existing patterns (particularly _download_button). Both reviewers found no blocking code-quality issues.

Specific strengths noted:

  • The should_register_element_id logic correctly handles the three distinct cases (rerun mode, key provided, shortcut provided).
  • check_widget_policies only fires when the element acts as a widget, not for the shortcut-only path.
  • key_as_main_identity ensures stable identity when used as a widget while preserving content-based identity for the default case.
  • The frontend handleClick guards the trigger with both !element.ignoreRerun and element.id, and the dependency array is complete.
  • The switch from {...elementProps} to explicit props in ElementNodeRenderer.tsx is an improvement.
  • icon_position is now included in compute_and_register_element_id, fixing a minor identity gap.

Test Coverage

Reviewers agreed: Test coverage is solid across all layers — Python unit tests, frontend unit tests, and typing tests.

Specific coverage:

  • Python unit tests (link_button_test.py): Default ignore_rerun flag, key-setting in ignore mode, invalid key rejection (empty and reserved prefix), rerun/callback enabling.
  • Python unit tests (button_test.py): Stable key-based identity for both rerun/callback and ignore modes, including varying callback args.
  • Frontend unit tests (LinkButton.test.tsx): No-rerun by default, rerun with/without fragmentId, disabled-prevents-rerun.
  • Typing tests (button_types.py): Comprehensive assert_type coverage for all new parameters.

Gap noted by both reviewers: No E2E test was added. Both reviewers agreed this is a non-blocking gap — the underlying setTriggerValue mechanism is already E2E-tested via other widgets, and the existing st_link_button_test.py E2E tests still pass since default behavior is unchanged.

Backwards Compatibility

Reviewers agreed: Fully backwards compatible.

  • All new parameters are keyword-only with sensible defaults.
  • Default on_click="ignore" preserves existing no-rerun, no-widget-registration behavior.
  • The protobuf addition is purely additive; proto3's default false for ignore_rerun combined with the frontend guard !element.ignoreRerun && element.id ensures safe behavior with old backends.
  • The _link_button internal method signature changes help from positional to keyword, but this method is private with only one caller.

Security & Risk

Reviewers agreed: No security concerns.

  • Uses the same register_widget / check_widget_policies infrastructure as existing widgets.
  • writes_allowed=False correctly prevents session state writes.
  • No new external inputs or URL handling introduced.
  • The link opens via the native <a href> mechanism; the rerun is a separate side effect.

Main regression risk (noted by both): browser-level interaction timing between navigation and rerun is not covered by E2E tests.

Accessibility

Reviewers agreed: No accessibility regressions.

  • The component continues to render a semantic <a> element with aria-disabled, target="_blank", and rel="noreferrer".
  • No new interactive elements introduced.
  • Keyboard shortcut integration unchanged.
  • Click handler correctly prevents link navigation when disabled.

Recommendations

  1. E2E test (follow-up): Both reviewers recommend adding an E2E test scenario for on_click="rerun" to st_link_button_test.py. While unit tests cover the mechanism well, a single E2E scenario would verify the full-stack callback flow. This is non-blocking.
  2. Docstring example (follow-up): The link_button docstring only shows the basic example. Consider adding an example showing on_click usage, similar to st.download_button.

Reviewer Agreement

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.py are cohesive and keep behavior centralized in _link_button.
  • Frontend wiring in frontend/lib/src/components/core/Block/ElementNodeRenderer.tsx and frontend/lib/src/components/elements/LinkButton/LinkButton.tsx is straightforward and uses the existing WidgetStateManager trigger 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_button behavior 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:

  • LinkButton remains an anchor-based control (<a href> behavior via BaseLinkButton), which is appropriate for navigation.
  • No new non-semantic clickable containers were introduced.
  • No ARIA regressions were observed in the modified code paths.

Recommendations

  1. 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 = 13 to LinkButton.proto.
  • Backend: Extends link_button() and _link_button() with new parameters; conditionally registers the widget and validates keys.
  • Frontend: LinkButton component gains widgetMgr and fragmentId props; handleClick conditionally calls setTriggerValue.
  • 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_button pattern for on_click handling, which promotes consistency.
  • The should_register_element_id logic (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_policies guard (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_identity expression (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 handleClick callback (line 61-73 of LinkButton.tsx) correctly guards the trigger with both !element.ignoreRerun and element.id, and the dependency array [element, fragmentId, widgetMgr] is complete and correct.
  • The change from {...elementProps} to explicit widgetMgr and fragmentId props in ElementNodeRenderer.tsx (line 915-919) is an improvement — the old code was spreading unused props (disableFullscreenMode, widthConfig, heightConfig) that LinkButton never consumed.
  • Minor style improvement: element.shortcut || undefined (line 42) is equivalent to the previous ternary and is more concise.
  • The icon_position parameter is now included in compute_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 default ignore_rerun flag, key-setting in ignore mode, invalid key rejection (empty and reserved prefix), rerun/callback mode enabling. Good use of @parameterized.expand and 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. Uses WidgetStateManager spy pattern properly.
  • Typing tests (button_types.py): Comprehensive assert_type coverage for the new key, on_click, args, kwargs parameters.
  • E2E tests: Not added. The PR justification — "behavior is covered by backend + frontend unit tests for this API-focused change" — is reasonable. The underlying setTriggerValue mechanism is already E2E-tested via other widgets. The existing E2E tests in st_link_button_test.py still 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_button internal method signature changes help from a required positional parameter to an optional keyword parameter, but this method is private and the only caller is the public link_button method (confirmed via search).
  • The protobuf addition (ignore_rerun = 13) is purely additive. The proto3 default of false combined with the frontend guard !element.ignoreRerun && element.id ensures safe behavior: old backends that don't set this field will have element.id as empty string for non-shortcut link buttons, so the rerun path won't fire.

Security & Risk

No security concerns identified:

  • The on_click callback mechanism uses the same register_widget / check_widget_policies infrastructure as existing widgets.
  • writes_allowed=False correctly 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 with aria-disabled, target="_blank", and rel="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

  1. Consider adding an E2E test scenario for on_click="rerun" to the existing st_link_button_test.py file. 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.
  2. Docstring example: The docstring for link_button only shows the basic example (st.link_button("Go to gallery", ...)). Consider adding a brief example showing on_click usage in a follow-up, similar to how st.download_button documents its on_click parameter 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.

@sfc-gh-lmasuch sfc-gh-lmasuch changed the title [feature] Add on_click support to link_button [feature] Add on_click rerun support to st.link_button Feb 26, 2026
@sfc-gh-lmasuch sfc-gh-lmasuch changed the title [feature] Add on_click rerun support to st.link_button [feat] Add on_click rerun support to st.link_button Feb 26, 2026
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
@lukasmasuch lukasmasuch merged commit 1f4fc20 into develop Mar 2, 2026
43 checks passed
@lukasmasuch lukasmasuch deleted the feature/link-button-on-click branch March 2, 2026 18:36
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.

Add an on_click parameter to st.link_button

4 participants