Skip to content

[feature] add width auto parameter to st.markdown#13841

Merged
sfc-gh-lwilby merged 4 commits intodevelopfrom
feature/markdown-width-auto
Feb 9, 2026
Merged

[feature] add width auto parameter to st.markdown#13841
sfc-gh-lwilby merged 4 commits intodevelopfrom
feature/markdown-width-auto

Conversation

@sfc-gh-lwilby
Copy link
Copy Markdown
Collaborator

@sfc-gh-lwilby sfc-gh-lwilby commented Feb 5, 2026

Describe your changes

Adds width="auto" as the new default for st.markdown. When auto, the width adapts based on container context:

  • Vertical containers: behaves like width="stretch" (fills available space)
  • Horizontal containers: behaves like width="content" (shrinks to fit)

This enables markdown elements to naturally fit within horizontal layouts without requiring explicit width="content".

GitHub Issue Link (if applicable)

Testing Plan

  • Unit Tests (JS and/or Python)
    • lib/tests/streamlit/elements/markdown_test.py - Tests width="auto" parameter acceptance and proto generation
    • frontend/lib/src/components/elements/Markdown/Markdown.test.tsx - Tests container-aware width behavior in horizontal/vertical layouts
  • E2E Tests
  • Any manual testing needed?
    • Manually verified.

Contribution License Agreement

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

@sfc-gh-lwilby sfc-gh-lwilby added change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 5, 2026

✅ PR preview is ready!

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

@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 5, 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.

@sfc-gh-lwilby sfc-gh-lwilby marked this pull request as ready for review February 5, 2026 20:08
Copilot AI review requested due to automatic review settings February 5, 2026 20:08
@sfc-gh-lwilby sfc-gh-lwilby added the ai-review If applied to PR or issue will run AI review workflow label Feb 5, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 5, 2026

Summary

This PR introduces a new width="auto" parameter as the default for st.markdown(). When set to "auto", the width adapts based on the container context:

  • Vertical containers: behaves like width="stretch" (100% width)
  • Horizontal containers: behaves like width="content" (fit-content width)

The implementation works by omitting the widthConfig from the protobuf message when width="auto", and then handling this case in the frontend's ElementNodeRenderer by checking the FlexContext to determine the layout direction.

Code Quality

The implementation is clean and follows existing patterns in the codebase:

  1. Backend (lib/streamlit/elements/markdown.py): The change is minimal and well-contained. The conditional logic at lines 184-188 correctly handles the "auto" case by not setting a widthConfig, allowing the frontend to determine the appropriate width.

  2. Frontend (frontend/lib/src/components/core/Block/ElementNodeRenderer.tsx): Lines 436-448 correctly implement the container-aware sizing using the existing FlexContext pattern. The code adds a block scope {} for the markdown case (line 436) to properly scope the config variable, which is good practice.

  3. Documentation: The docstring is well-updated with the new "auto" option and clearly explains the behavior.

Minor note: The type annotation Width | Literal["auto"] at line 47 is correct but creates a slight inconsistency since Width is defined as int | Literal["stretch", "content"] in layout_utils.py. Consider whether "auto" should be added to the Width type alias for consistency across the codebase in a follow-up.

Test Coverage

The test coverage is good but could be improved:

Python Unit Tests (lib/tests/streamlit/elements/markdown_test.py):

  • test_st_markdown_default_width (lines 97-107): Tests that the default produces no width config ✓
  • test_st_markdown_auto_width (lines 109-119): Tests explicit width="auto"
  • test_st_markdown_explicit_stretch_width (lines 121-131): Tests that explicit stretch still works ✓
  • Good use of negative assertions (lines 105-107, 117-119) to verify neither stretch nor content is set ✓

Frontend Unit Tests (frontend/lib/src/components/elements/Markdown/Markdown.test.tsx):

  • Integration tests for ElementNodeRenderer with different FlexContext configurations ✓
  • Tests cover horizontal layout with fit-content (lines 406-426) ✓
  • Tests cover vertical layout with 100% width (lines 428-448) ✓
  • Tests cover explicit width configs (useStretch, useContent, pixelWidth) ✓
  • Uses waitFor pattern for async assertions ✓

Missing Coverage:

  • No E2E test specifically for the auto width behavior in horizontal layouts (e.g., st.columns). The existing st_markdown.py tests headers in columns but doesn't explicitly test the new auto width behavior. While the unit tests are comprehensive, an E2E test would provide full-stack validation of this user-facing behavior change.

Backwards Compatibility

This is a breaking change in behavior - the default width for st.markdown() changes from "stretch" to "auto":

  • Before: st.markdown("text") in a horizontal layout (e.g., inside st.columns) would stretch to fill the column width.
  • After: st.markdown("text") in a horizontal layout will shrink to fit its content.

This change is intentional and improves the default experience for markdown in horizontal layouts. Users who relied on the stretch behavior can explicitly use width="stretch" to restore it.

The change is semantically backwards compatible:

  • All existing width options ("stretch", "content", integer pixels) still work
  • Users can explicitly opt into the old behavior with width="stretch"

Note: The st.caption() and st.latex() functions retain "stretch" as their default, maintaining consistency for those elements.

Security & Risk

No security concerns identified. This is a purely visual/layout change with no impact on:

  • Data handling
  • Authentication/authorization
  • External system interactions
  • User input processing

Risk Assessment: Low. The change is well-scoped and the fallback to explicit width="stretch" provides a clear migration path for any affected users.

Accessibility

No accessibility concerns. The change:

  • Does not affect any interactive elements
  • Does not modify ARIA attributes
  • Does not change focus handling
  • Does not impact screen reader behavior

The width changes are purely visual and do not affect the semantic structure or accessibility tree of the rendered content.

Recommendations

  1. Consider adding an E2E test for the auto width behavior in horizontal layouts to provide full-stack validation. This could be added to e2e_playwright/st_markdown.py:

    # Example test case
    col1, col2 = st.columns(2)
    with col1:
        st.markdown("Auto width markdown")  # Should be fit-content
    with col2:
        st.markdown("Stretch markdown", width="stretch")  # Should be 100%
  2. Minor: Consider updating the existing snapshot tests in st_markdown_test.py (e.g., test_match_snapshot_for_columns) to explicitly verify the auto width behavior, as these tests use markdown in columns and will now produce different visual results.

  3. Documentation note: The release notes should clearly communicate this default behavior change to help users understand why their horizontal layouts might look different after upgrading.

Verdict

APPROVED: The implementation is clean, well-tested with unit tests, and follows existing codebase patterns. The breaking change in default behavior is intentional and improves the user experience for markdown in horizontal layouts. The ability to explicitly set width="stretch" provides a clear migration path for any affected users. The missing E2E test is a minor gap that could be addressed in a follow-up.


This is an automated AI review using opus-4.5-thinking. Please verify the feedback and use your judgment.

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 width="auto" as the new default for st.markdown, enabling container-aware width behavior. When set to "auto", markdown elements automatically adapt their width based on the layout context: they stretch to fill vertical containers and shrink to content width in horizontal containers. This improves the default behavior for markdown in horizontal layouts (like st.columns) without requiring users to explicitly set width="content".

Changes:

  • Changed default width parameter from "stretch" to "auto" for st.markdown
  • Backend skips width validation and proto configuration when width="auto"
  • Frontend applies container-aware sizing (fit-content in horizontal layouts, 100% in vertical layouts) when no widthConfig is present
  • Added comprehensive tests for auto-width behavior in both Python and TypeScript

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
lib/streamlit/elements/markdown.py Updated markdown() method signature to default to width="auto", added conditional logic to skip width config when auto, updated docstring to document auto behavior
lib/tests/streamlit/elements/markdown_test.py Added tests for default auto width, explicit auto width, and explicit stretch width with negative assertions
frontend/lib/src/components/core/Block/ElementNodeRenderer.tsx Added container-aware width logic for markdown elements when widthConfig is absent
frontend/lib/src/components/elements/Markdown/Markdown.test.tsx Added integration tests verifying auto-width behavior in both horizontal and vertical layouts, plus tests for explicit width configs

@sfc-gh-lwilby sfc-gh-lwilby force-pushed the feature/markdown-width-auto branch from 7d674d3 to a1547b7 Compare February 5, 2026 20:16
@sfc-gh-lwilby
Copy link
Copy Markdown
Collaborator Author

No E2E test specifically for the auto width behavior in horizontal layouts (e.g., st.columns). The existing st_markdown.py tests headers in columns but doesn't explicitly test the new auto width behavior. While the unit tests are comprehensive, an E2E test would provide full-stack validation of this user-facing behavior change.

I decided against adding this since the styling/visuals are the same as "stretch" and "content" which are already tested, it is the switching behaviour that differs and is covered in the new tests.

// - In vertical layouts: stretch (100%)
let config = ElementContainerConfig.DEFAULT
if (!node.element.widthConfig) {
config = new ElementContainerConfig({
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: This re-creates new ElementContainerConfig() on each render. Even if behavior is correct, it can make downstream hooks/components do extra work.

We also have a couple places in ElementNodeRenderer that recently started using this pattern, and our Playwright perf metrics show small regressions around the same timeframe:

Could we refactor this to keep the config stable across renders to reduce unnecessary churn?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Interesting, looking at the first graph, I don't really see a clear indication of a regression just visually inspecting it. There is a spike on my commit, but the graph is overall very spiky and later commits are lower.

The second graph does seem to show a regression, but the test app it uses doesn't have any elements that are creating a new object (3 markdown -- only updated in this PR, 1 button).

I have added some logic to cache these anyways, what do you think?

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 6, 2026

📈 Frontend coverage change detected

The frontend unit test (vitest) coverage has increased by 0.0900%

  • Current PR: 86.6000% (13868 lines, 1858 missed)
  • Latest develop: 86.5100% (13875 lines, 1871 missed)

🎉 Great job on improving test coverage!

📊 View detailed coverage comparison

Copy link
Copy Markdown
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 revision, this is headed in a good direction! I have a couple small questions to make sure we keep behavior predictable.

readonly styleOverrides?: CSSProperties

// Cache for configs created via create() to ensure referential stability
private static readonly cache = new Map<string, ElementContainerConfig>()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: How do we want to prevent the cache from growing without bound over time? It looks like it will likely stay small for typical usage, but should we add a cap just in case?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It looks like it will likely stay small for typical usage

Ended up removing this, these further questions confirmed my feeling that this is too much complexity to add. But, also, the cache as added would be bounded to 11, we can count it in ElementNodeRenderer.tsx.

* ElementContainerConfig.create({ styleOverrides: { width: "100%" } })
* ```
*/
static create(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: Since .create() is where we get the perf benefit, how do we want to ensure callers use the factory rather than instantiating directly? Would it make sense to make the constructor private so usage naturally funnels through .create()?

@sfc-gh-lwilby
Copy link
Copy Markdown
Collaborator Author

@sfc-gh-bnisco thinking about this more, I have decided to remove the cache because it feels like over-engineering when, looking at the charts you have mentioned, it doesn't seem like there is any evidence of a regression (let me know if you observed differently, but below is my analysis).

I traced through the two benchmarks linked. The basic_app test uses st.title, st.button, st.markdown, and st.write — all of which resolve to ElementContainerConfig.DEFAULT (a static constant). None of them create new config objects, so the data from that test isn't attributable to this change. The mega_tester_app graph shows high per-run variance (boxes span ~200-500 units against a ~6000ms baseline) with no visible step change at the commit boundary — the oscillation pattern is consistent before and after.

What I did instead: Added FULL_WIDTH, FIT_CONTENT_ELEMENT, and LARGE_OVERFLOW_VISIBLE as static constants, which covers the majority of call sites with zero overhead and guaranteed referential stability. The remaining ~4 conditional call sites use a ternary to select a static constant in the common case and only fall back to new ElementContainerConfig(...) when runtime conditions require it.

@sfc-gh-lwilby sfc-gh-lwilby merged commit 74e1e01 into develop Feb 9, 2026
42 checks passed
@sfc-gh-lwilby sfc-gh-lwilby deleted the feature/markdown-width-auto branch February 9, 2026 18:25
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.

3 participants