Skip to content

Conversation

@SiddhantSadangi
Copy link
Contributor

Describe your changes

Adds an icon_position parameter to button-like components, allowing icons to be placed on the left (default) or right of the label.

Changes by Category

1. Protobuf Definitions (4 files)

Added icon_position string field to:

  • Button.proto
  • LinkButton.proto
  • DownloadButton.proto
  • PageLink.proto

2. Backend (Python)

lib/streamlit/elements/widgets/button.py:

  • Added IconPosition type alias (Literal["left", "right"])
  • Added _normalize_icon_position() for validation
  • Added icon_position parameter (default "left") to:
    • st.button()
    • st.download_button()
    • st.link_button()
    • st.page_link()
  • Validates that icon_position is "left" or "right", raising StreamlitAPIException otherwise

lib/streamlit/elements/form.py:

  • Added icon_position parameter to form_submit_button() methods

3. Frontend (TypeScript/React)

DynamicButtonLabel.tsx:

  • Added iconPosition prop (default "left")
  • Conditionally renders icon before or after the label based on iconPosition

Component updates:

  • Button.tsx, LinkButton.tsx, DownloadButton.tsx, FormSubmitButton.tsx — pass iconPosition to DynamicButtonLabel
  • PageLink.tsx — custom logic to position icon left or right (doesn't use DynamicButtonLabel)

Impact

Users can control icon placement on buttons:

  • Default: icon on the left (backward compatible)
  • New: icon_position="right" places the icon on the right

Applies to: st.button(), st.download_button(), st.link_button(), st.page_link(), and st.form_submit_button().

Screenshot or video (only for visual changes)

image

GitHub Issue Link (if applicable)

#13069

Testing Plan

  • Explanation of why no additional tests are needed
  • Unit Tests (JS and/or Python)
  • E2E Tests
  • Any manual testing needed?

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

Copilot AI review requested due to automatic review settings November 27, 2025 18:41
@SiddhantSadangi SiddhantSadangi requested a review from a team as a code owner November 27, 2025 18:41
@snyk-io
Copy link
Contributor

snyk-io bot commented Nov 27, 2025

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.

@SiddhantSadangi SiddhantSadangi changed the title Ss/icon position Add icon_position support for button-like components Nov 27, 2025
Copy link
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 an icon_position parameter to button-like components (st.button(), st.download_button(), st.link_button(), st.page_link(), and st.form_submit_button()), allowing developers to position icons on either the left (default) or right side of the button label.

Key Changes:

  • Added icon_position parameter with validation to all button-like components (default: "left", accepts "left" or "right")
  • Updated protobuf definitions to include the new field
  • Modified frontend components to conditionally render icons based on the position parameter

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
proto/streamlit/proto/*.proto Added icon_position field to Button, LinkButton, DownloadButton, and PageLink protobuf messages
lib/streamlit/elements/widgets/button.py Added IconPosition type, validation function, and icon_position parameter to all button-like commands
lib/streamlit/elements/form.py Added icon_position parameter to form submit button methods
frontend/lib/src/components/shared/BaseButton/DynamicButtonLabel.tsx Added conditional rendering logic to position icons left or right of the label
frontend/lib/src/components/widgets/*.tsx Updated Button, DownloadButton, FormSubmitButton, and LinkButton to pass iconPosition prop
frontend/lib/src/components/elements/PageLink/PageLink.tsx Added custom icon positioning logic for PageLink component
lib/tests/streamlit/**/*_test.py Added Python unit tests for icon position validation and serialization
frontend/lib/src/components/shared/BaseButton/DynamicButtonLabel.test.tsx Added frontend unit tests for icon positioning
e2e_playwright/st_*_test.py Added E2E snapshot tests for icon positioning across all button types
e2e_playwright/st_*.py Added test apps demonstrating icon positioning with various button types

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@sfc-gh-bnisco sfc-gh-bnisco added feature:st.download_button Related to the `st.download_button` widget feature:st.button Related to the `st.button` widget change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users feature:st.link_button Related to the `st.link_button` widget labels Dec 1, 2025
@sfc-gh-bnisco sfc-gh-bnisco self-assigned this Dec 1, 2025
Copy link
Collaborator

@sfc-gh-bnisco sfc-gh-bnisco left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution @SiddhantSadangi !

I haven't yet taken an in-depth look at the code, but I wanted to point your attention to the amount of merge conflicts that are pending. Please consider rebasing or merging on latest develop and fixing the merge conflicts, then pushing those updates to this branch. Thanks in advance!

@SiddhantSadangi
Copy link
Contributor Author

@sfc-gh-bnisco - conflicts resolved ✅

Copy link
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

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Copy link
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

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Copy link
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

Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.

Copy link
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

Copilot reviewed 28 out of 148 changed files in this pull request and generated no new comments.

@github-actions
Copy link
Contributor

Summary

This PR adds an icon_position parameter to all button-like components (st.button, st.download_button, st.link_button, st.page_link, and st.form_submit_button), plumbs it through the Python API, protobuf layer, and frontend components, and updates unit and e2e tests plus visual snapshots to validate both left and right icon placements. The default behavior remains left-aligned icons, and the implementation introduces a shared enum (ButtonLikeIconPosition) and mapping helpers to keep server and client behavior consistent.

Code Quality

Overall the implementation is clean, well-typed, and follows existing patterns for buttons and links.

  • Python API & serialization:
    • lib/streamlit/elements/widgets/button.py introduces a clear IconPosition type alias and _normalize_icon_position validator, and uses it consistently across button, download_button, link_button, and page_link (L92-L110, L132-L382, L768-L938, L943-L1138).
    • _icon_position_to_proto centralizes mapping to the shared enum and is applied in _button, _download_button, _link_button, and _page_link (lib/streamlit/elements/widgets/button.py:L113-L121, L1140-L1217, L1246-L1292, L1302-L1322, L1465-L1480), which keeps serialization logic DRY and easy to extend.
    • lib/streamlit/elements/form.py imports IconPosition and _normalize_icon_position, normalizes in form_submit_button, and passes the normalized value through _form_submit_button into dg._button (L33-L36, L242-L440, L442-L476). This maintains one clear validation path.
  • Frontend components:
    • frontend/lib/src/components/shared/BaseButton/DynamicButtonLabel.tsx adds an iconPosition?: "left" | "right" prop and renders the icon conditionally before or after the label (L31-L38, L40-L47, L52-L69). The implementation is simple, declarative, and consistent with existing styling.
    • frontend/lib/src/components/shared/BaseButton/iconPosition.ts defines a small ICON_POSITION_MAP and mapProtoIconPosition helper that defaults null/undefined enum values to LEFT (L21-L37). This is a good centralization of protobuf-to-UI mapping.
    • Button.tsx, DownloadButton.tsx, FormSubmitButton.tsx, and LinkButton.tsx all pass mapProtoIconPosition(element.iconPosition) down into DynamicButtonLabel or equivalent, without duplicating mapping logic (Button.tsx:L64-L84, DownloadButton.tsx:L178-L193, FormSubmitButton.tsx:L77-L92, LinkButton.tsx:L80-L105).
    • frontend/lib/src/components/elements/PageLink/PageLink.tsx uses mapProtoIconPosition to decide whether to render the icon before or after the label (L94-L137); the structure is straightforward and keeps the icon-position logic local.
  • Structure & naming:
    • The new proto ButtonLikeIconPosition enum is well-named and clearly scoped for button-like elements (proto/streamlit/proto/ButtonLikeIconPosition.proto:L24-L28).
    • Snapshot names for e2e tests follow st_command-test_description conventions and clearly encode icon position and type (e.g. st_button-icon_position_right_primary, st_download_button-icon_position_left_emoji, st_link_button-icon_position_left_material, st_page_link-icon_position_right_material).

I did not spot logic bugs, dead code, or style issues; the changes align with existing patterns for buttons and links.

Test Coverage

Test coverage for the new behavior is strong across Python, TypeScript, and e2e layers.

  • Python unit tests:
    • lib/tests/streamlit/elements/button_test.py adds test_invalid_icon_position_raises and test_icon_position that parameterize across all button-like commands and both "left"/"right" values, asserting the correct proto enum is set (L146-L173). This exercises validation and serialization paths for st.button, st.download_button, st.link_button, and st.page_link.
    • lib/tests/streamlit/elements/link_button_test.py adds a focused test_icon_position for st.link_button (L76-L81).
    • lib/tests/streamlit/elements/page_link_test.py asserts that default icon position for external links is left and that custom icon_position="right" is reflected in the proto (L59-L80).
    • lib/tests/streamlit/form_test.py adds parameterized tests for valid submit button icon positions and a negative test for invalid values, both mapped to the shared enum (L378-L399).
    • All new tests are pytest-based, have short docstrings, and use @parameterized.expand where appropriate, matching .cursor/rules/python_tests.mdc.
  • Frontend unit tests:
    • DynamicButtonLabel.test.tsx adds tests ensuring the icon renders on the left by default and on the right when iconPosition="right" is passed, and also exercises shortcut rendering behavior (L75-L99, L101-L117).
    • PageLink.test.tsx verifies that the icon appears before the label by default and after when iconPosition corresponds to RIGHT in the protobuf enum (L163-L182), plus multiple existing tests for behavior and buildHref remain intact.
    • Tests use React Testing Library queries, userEvent, and vitest-only syntax, in line with .cursor/rules/typescript.mdc.
  • E2E tests & snapshots:
    • e2e_playwright/st_button.py adds a dedicated "Icon Position Examples" section that exercises combinations of material vs emoji icons, primary vs tertiary types, and left vs right positions (L138-L184).
    • e2e_playwright/st_button_test.py asserts TOTAL_BUTTONS and captures focused snapshots for each icon-position combination (e.g. st_button-icon_position_left_material, st_button-icon_position_right_emoji, st_button-icon_position_right_primary, st_button-icon_position_left_tertiary) using assert_snapshot on specific locators and keys (L35-L124).
    • e2e_playwright/st_download_button.py and st_download_button_test.py mirror this pattern for download buttons, including emoji/material and left/right variants (st_download_button-icon_position_* names, e.g. L190-L220 in the app and L52-L113 in the test).
    • e2e_playwright/st_link_button.py and st_link_button_test.py do the same for st.link_button, with snapshot naming st_link_button-icon_position_* and use Playwright expect and key-based or text-based locators (st_link_button_test.py:L27-L71).
    • e2e_playwright/st_page_link.py and st_page_link_test.py introduce page-link icon-position examples and snapshots for emoji and material icons left and right (L79-L105 in the app, L128-L147 in the test).
    • The e2e tests rely on expect and shared helpers from e2e_playwright.shared.app_utils, keep screenshot surfaces small (individual elements), and follow naming guidance from .cursor/rules/e2e_playwright.mdc.

Given the above, the new feature is covered at unit level, integration/component level, and full-stack/e2e visual level.

Backwards Compatibility

  • Python API: The new icon_position parameter is added with a default of "left" and is keyword-only for the public functions (button, download_button, link_button, page_link, and form_submit_button), so existing call sites remain valid and behavior-preserving.
  • Protobuf schema:
    • Button.proto, DownloadButton.proto, LinkButton.proto, and PageLink.proto each add a new icon_position field at the end of the message with new field numbers (12, 14, 12, and 10 respectively) and import the new ButtonLikeIconPosition enum (Button.proto:L24-L42, DownloadButton.proto:L24-L39, LinkButton.proto:L24-L36, PageLink.proto:L24-L35).
    • No existing field numbers, types, or names are changed or removed, which is compatible with the protobuf guidelines in .cursor/rules/protobuf.mdc.
    • Older frontends that don't know about the new enum field will simply ignore it; newer frontends map missing/zero values to "left" via mapProtoIconPosition, so mixed-version deployments should continue to work.
  • Frontend behavior: When the server doesn't send icon_position (older server with newer frontend), mapProtoIconPosition falls back to LEFT, so UI behavior matches the previous default of left-aligned icons.

I don't see breaking changes for existing apps; the added functionality is strictly additive with safe defaults.

Security & Risk

  • The feature surfaces only a constrained string parameter ("left" or "right") that is validated on the Python side (_normalize_icon_position in lib/streamlit/elements/widgets/button.py:L92-L110), so there is no new avenue for injection or arbitrary input.
  • Icons themselves continue to be validated via validate_icon_or_emoji before being serialized to protos, and the frontend uses existing DynamicIcon rendering paths, so no new HTML/JS injection risks are introduced.
  • The protobuf changes are minimal and follow compatibility rules, reducing risk of wire-format regressions.
  • The primary risk is visual regression in button-like components if icon positioning is mishandled, but this is mitigated by comprehensive e2e snapshot coverage across themes, browsers, and combinations of icon type and button type.

Recommendations

  1. Proto compilation check: Confirm that the generated protobuf code for both Python and TypeScript has been regenerated and committed (e.g., via make protobuf), especially to ensure ButtonLikeIconPosition is available in all generated bindings used by @streamlit/protobuf.
  2. Documentation update: Consider adding brief documentation updates for icon_position in the user-facing API docs for st.button, st.download_button, st.link_button, st.page_link, and st.form_submit_button to surface the new capability.

Verdict

APPROVED: The implementation is clean, well-tested across layers, backwards compatible, and the remaining follow-ups are minor documentation/build considerations rather than blockers.


This is an automated AI review. Please verify the feedback and use your judgment.

@sfc-gh-bnisco
Copy link
Collaborator

Thanks for the contribution and iterations @SiddhantSadangi! I did another revision and updated the snapshot files. I'm going to have another teammate take a look at this for final review.

Copy link
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.

Implementation LGTM 👍 but two aspects to double check:

  1. The popover also uses our button component; we might want to add it here as well. However, since it already has an icon on the right, it might not be desired in this case. Maybe double-check with @jrieke. But this could also be a follow-up
  2. It feels like a bit too many new snapshots for a relatively simple feature. I think we could reduce this, e.g. by just sprinkling some right/left icons on the existing buttons that get snapshot tested or by have those snapshots only taken on normal app / not themed_app (since theming shouldn't make a difference here).

@jrieke
Copy link
Collaborator

jrieke commented Dec 10, 2025

The popover also uses our button component; we might want to add it here as well. However, since it already has an icon on the right, it might not be desired in this case. Maybe double-check with @jrieke. But this could also be a follow-up

Yeah I think I wouldn't add icon_position for st.popover. Having two icons on the right side definitely feels weird.

Copy link
Collaborator

@sfc-gh-bnisco sfc-gh-bnisco left a comment

Choose a reason for hiding this comment

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

It feels like a bit too many new snapshots for a relatively simple feature.

You're right, I'm going to cut the amount of snapshots down.

@sfc-gh-bnisco sfc-gh-bnisco added the security-assessment-completed Security assessment has been completed for PR label Dec 10, 2025
@sfc-gh-bnisco sfc-gh-bnisco merged commit e1579f8 into streamlit:develop Dec 10, 2025
40 checks passed
@SiddhantSadangi SiddhantSadangi deleted the ss/icon_position branch December 30, 2025 09:22
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 feature:st.button Related to the `st.button` widget feature:st.download_button Related to the `st.download_button` widget feature:st.link_button Related to the `st.link_button` widget impact:users PR changes affect end users security-assessment-completed Security assessment has been completed for PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants