[feature] add width auto parameter to st.markdown#13841
[feature] add width auto parameter to st.markdown#13841sfc-gh-lwilby merged 4 commits intodevelopfrom
Conversation
✅ PR preview is ready!
|
✅ 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. |
SummaryThis PR introduces a new
The implementation works by omitting the Code QualityThe implementation is clean and follows existing patterns in the codebase:
Minor note: The type annotation Test CoverageThe test coverage is good but could be improved: Python Unit Tests (
Frontend Unit Tests (
Missing Coverage:
Backwards CompatibilityThis is a breaking change in behavior - the default width for
This change is intentional and improves the default experience for markdown in horizontal layouts. Users who relied on the stretch behavior can explicitly use The change is semantically backwards compatible:
Note: The Security & RiskNo security concerns identified. This is a purely visual/layout change with no impact on:
Risk Assessment: Low. The change is well-scoped and the fallback to explicit AccessibilityNo accessibility concerns. The change:
The width changes are purely visual and do not affect the semantic structure or accessibility tree of the rendered content. Recommendations
VerdictAPPROVED: 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 This is an automated AI review using |
There was a problem hiding this comment.
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"forst.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 |
7d674d3 to
a1547b7
Compare
I decided against adding this since the styling/visuals are the same as |
| // - In vertical layouts: stretch (100%) | ||
| let config = ElementContainerConfig.DEFAULT | ||
| if (!node.element.widthConfig) { | ||
| config = new ElementContainerConfig({ |
There was a problem hiding this comment.
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:
- Long animation frames total duration (ms) and Main mount total duration (ms) in test_mega_tester_app_rendering_performance
- Main mount total duration (ms) in test_basic_app_performance
Could we refactor this to keep the config stable across renders to reduce unnecessary churn?
There was a problem hiding this comment.
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?
📈 Frontend coverage change detectedThe frontend unit test (vitest) coverage has increased by 0.0900%
🎉 Great job on improving test coverage! |
sfc-gh-bnisco
left a comment
There was a problem hiding this comment.
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>() |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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-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. |
Describe your changes
Adds
width="auto"as the new default forst.markdown. Whenauto, the width adapts based on container context:width="stretch"(fills available space)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
lib/tests/streamlit/elements/markdown_test.py- Testswidth="auto"parameter acceptance and proto generationfrontend/lib/src/components/elements/Markdown/Markdown.test.tsx- Tests container-aware width behavior in horizontal/vertical layoutsContribution License Agreement
By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.