Skip to content

Add width and height configuration to st.table#13850

Merged
lukasmasuch merged 29 commits intodevelopfrom
lukasmasuch/table-width-height
Feb 13, 2026
Merged

Add width and height configuration to st.table#13850
lukasmasuch merged 29 commits intodevelopfrom
lukasmasuch/table-width-height

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Feb 6, 2026

Describe your changes

Implement configurable width and height support for st.table with proper scrolling behavior and sticky headers/index columns. The table element now accepts:

  • width: "content" | "stretch" | int (pixels)
  • height: "content" | "stretch" | int (pixels)

When a fixed pixel dimension is specified, the table enables scrolling with sticky headers (top) and index columns (left) for easy navigation through large datasets.

Github Issues

Testing Plan

  • Frontend unit tests validate width/height configuration, sticky header/index positioning, and content truncation behavior
  • Backend unit tests verify parameter validation and LayoutConfig serialization
  • Default behavior (width="stretch", height="content") maintains backward compatibility

Contribution License Agreement

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

Implement configurable width and height for arrow tables with proper overflow handling and scrolling behavior. Includes styled-components updates, frontend component changes, and backend protobuf field additions with corresponding tests.
Copilot AI review requested due to automatic review settings February 6, 2026 20:04
@snyk-io
Copy link
Copy Markdown
Contributor

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

@lukasmasuch lukasmasuch changed the title Add width and height configuration to st.table Add width and height configuration to st.table Feb 6, 2026
@lukasmasuch lukasmasuch added security-assessment-completed change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 6, 2026
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

Adds width/height sizing support for st.table across the Python API and frontend rendering so tables can be constrained (pixel, stretch, content) and support scrolling with sticky headers/index.

Changes:

  • Backend: add width/height parameters to st.table, validate them, and serialize via LayoutConfig.
  • Frontend: update ArrowTable styling/logic for scroll containers, sticky header/index behavior, and cell wrapping/truncation.
  • Tests: add Python unit tests for layout config serialization + frontend unit tests for truncation/sticky offsets.

Reviewed changes

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

Show a summary per file
File Description
table-width-height.md Adds an implementation plan document for the feature.
table-sizing.md Adds implementation notes/learned behavior for CSS sizing + sticky/scroll details.
lib/streamlit/elements/arrow.py Extends st.table API with width/height, validates inputs, and enqueues with LayoutConfig.
lib/tests/streamlit/elements/arrow_table_test.py Adds backend tests for width/height validation + layout config serialization.
frontend/lib/src/components/elements/ArrowTable/ArrowTable.tsx Adds width/height config props + sticky/scroll logic.
frontend/lib/src/components/elements/ArrowTable/styled-components.ts Implements scroll container changes, sticky cell styles, and wrapping/truncation rules.
frontend/lib/src/components/elements/ArrowTable/ArrowTable.test.tsx Adds frontend tests for truncation defaults and sticky offset behavior.
frontend/lib/src/components/core/Block/StyledElementContainerLayoutWrapper.tsx Adds table to stretch sizing behavior and documents overflow handling intent.

Remove unnecessary useMemo wrappers for simple boolean coercions and
replace nested ternary with clearer getStickyType helper function.
@lukasmasuch lukasmasuch changed the title Add width and height configuration to st.table [WIP] Add width and height configuration to st.table Feb 6, 2026
Delete table-sizing.md and table-width-height.md files that are no longer needed.
@lukasmasuch lukasmasuch marked this pull request as draft February 6, 2026 20:15
Resolved conflict in StyledElementContainerLayoutWrapper.tsx by adopting the new
ElementContainerConfig pattern from develop. Updated the table case in
ElementNodeRenderer.tsx to use ElementContainerConfig.LARGE_ELEMENT to preserve
the large stretch behavior for st._arrow_table.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 6, 2026

✅ PR preview is ready!

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

- Add E2E tests for fixed height, width, and combined scrolling behavior
- Add test for multi-index tables with sticky index columns
- Document fallback offset constants in ArrowTable.tsx explaining their purpose
Add dedicated tableColumnMaxWidth constant to avoid affecting
the status widget which shares appStatusMaxWidth.
Sticky index columns now have a min-width matching the fallback
offset (120px), ensuring consistent column widths and preventing
gaps or overlaps between sticky columns.
Update the height parameter documentation to accurately describe that
sticky index columns only work when horizontal scrolling is enabled
via a fixed pixel width, not just with vertical scrolling.
Update snapshots to reflect the increased table column max width
(20rem -> 25rem) and multi-index sticky column fixes.
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.

As a follow-up, it probably makes sense to clean this up so that it doesn't depend on indices.

- Consolidate 4 separate width/height tests into 2 focused tests:
  1. Fixed dimensions with multi-index (tests scrolling in both directions
     with sticky headers and index columns)
  2. Content width sizing (tests width="content")
- Remove redundant snapshots for height-only and width-only tests
- CI will regenerate Linux snapshots for the new tests
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.

The PR adds a max column width that will trigger the column to wrap to new line

Add snapshots for:
- st_table-35/36: Numbered snapshots for the two new test tables
- st_table-fixed_dimensions_scrolled: Multi-index table scrolled in both directions
- st_table-content_width: Table with width="content" sizing
The fit-content styling on markdown containers was preventing code
blocks from expanding to fill the available cell width. Add a specific
CSS rule for pre elements to use 100% width while keeping the
fit-content behavior for regular text content.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 10, 2026

📈 Frontend coverage change detected

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

  • Current PR: 86.8900% (13920 lines, 1824 missed)
  • Latest develop: 86.8500% (13881 lines, 1824 missed)

✅ Coverage change is within normal range.

📊 View detailed coverage comparison

@lukasmasuch
Copy link
Copy Markdown
Collaborator Author

@cursor review

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@lukasmasuch lukasmasuch force-pushed the lukasmasuch/table-width-height branch from 5da8676 to d3a6f76 Compare February 11, 2026 13:17
The content_width table is already covered by test_table_rendering
which iterates through all table indices including index 36.
Replace data-testid selector with direct Emotion component selector
for better type safety and maintainability.
@lukasmasuch lukasmasuch force-pushed the lukasmasuch/table-width-height branch from 1a608fc to a3a39a0 Compare February 11, 2026 13:51
- Add overflow-y: hidden to markdown CSS in ArrowTable to prevent
  vertical overflow affecting layout
- Add width constraints and overflow: clip to StyledTableContainer
- Add brief markdown test row to st_table.py with key features
- Add code block to st_markdown.py test
Convert hardcoded pixel fallbacks to rem-based values so they scale
properly with user font size changes. The header row offset (2rem) is
now derived from theme tokens, and the index column offset (7.5rem)
scales proportionally instead of being fixed at 120px.
Add verticalAlign: top to markdown elements in table cells to prevent
the baseline shift caused by overflowY: hidden on inline-block elements.
@lukasmasuch lukasmasuch changed the title [WIP] Add width and height configuration to st.table Add width and height configuration to st.table Feb 11, 2026
@lukasmasuch lukasmasuch marked this pull request as ready for review February 11, 2026 17:18
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Feb 11, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Summary

This PR adds configurable width and height parameters to st.table, enabling users to create fixed-dimension, scrollable tables with sticky headers and index columns. The three key changes are:

  • Backend: New width (default "stretch") and height (default "content") keyword-only parameters on st.table(), with validation reusing the existing validate_width/validate_height utilities.
  • Frontend: ArrowTable component refactored to support scrollable containers, sticky positioning for header/index cells, and content truncation when a fixed pixel width is specified. Styled-components updated with new props for sticky behavior (StickyType, offsets, z-index layering).
  • ElementNodeRenderer: Changed st.table container config from DEFAULT to LARGE_ELEMENT to support the width/height layout flow.

Code Quality

The code is well-structured and follows existing Streamlit patterns:

  • Backend: Parameters are correctly keyword-only. Validation delegates to the shared validate_width/validate_height helpers. Docstrings are Numpy-style and comprehensive. The LayoutConfig dataclass is reused cleanly.

  • Frontend: Values are derived during render with no unnecessary useEffect. useMemo is used appropriately for offset arrays. The getStickyType and getStickyOffset helper functions are clean and well-named. Styled-component props follow the Styled* naming convention.

  • Minor optimization opportunity: convertRemToPx(FALLBACK_INDEX_COLUMN_OFFSET_REM) is called inline inside generateTableHeader and generateTableCell for every sticky cell's stickyMinWidth. Since the input is a constant, the result could be computed once and passed as a parameter (similar to how indexLeftOffsets is precomputed). This is a very minor performance concern for tables with many index rows.

    Affected locations:

    • frontend/lib/src/components/elements/ArrowTable/ArrowTable.tsx line 240 (header) and line 349 (cells)
  • Accessibility note on captions: The caption is rendered outside <table> as a <div> (lines 189-190 in ArrowTable.tsx). This is a pre-existing concern acknowledged in the code comments, not introduced by this PR.

Test Coverage

Test coverage is thorough and appropriate:

  • Python unit tests (arrow_table_test.py): Parameterized tests for width/height serialization across all three modes ("stretch", "content", int). Tests for default values, invalid values, negative values, and combined parameters. Follows the pattern of using WidthConfigFields/HeightConfigFields enums from shared test utilities.
  • Frontend unit tests (ArrowTable.test.tsx): Tests for truncation behavior (default vs. fixed pixel width), sticky offset positioning with multi-index data, and existing border mode tests.
  • E2E tests (st_table_test.py): New test_table_fixed_dimensions_with_scrolling test that scrolls a table with fixed dimensions and verifies the visual result via snapshot. The e2e app script includes new test scenarios for fixed dimensions with multi-index and content width sizing.

One minor observation: the e2e test_table_fixed_dimensions_with_scrolling test uses el.parentElement.scrollTop = 100; el.parentElement.scrollLeft = 150; via evaluate — this is a reasonable approach for testing scroll behavior since Playwright's native scroll APIs don't easily target inner scrollable containers.

Backwards Compatibility

The changes maintain backwards compatibility:

  • Default parameter values (width="stretch", height="content") reproduce the exact previous behavior.
  • New parameters are keyword-only, so no positional argument breakage.
  • The ElementContainerConfig change from DEFAULT to LARGE_ELEMENT introduces a minimum stretch width for tables. This is a subtle visual change that could affect table rendering in very narrow containers, but is intentional and aligns with how other large elements (like dataframes) are configured. This is needed for proper width/height layout support.
  • The new tableColumnMaxWidth: "25rem" in sizes.ts is referenced only by the new truncation logic and the new content-sizing Markdown wrapper — it does not affect existing styling paths.

Security & Risk

No security concerns identified:

  • The width/height parameters are validated server-side before being sent to the frontend.
  • Integer pixel values are bounded (must be > 0).
  • String values are restricted to a known allowlist ("stretch", "content").
  • No user-provided strings are interpolated into CSS; all values flow through Emotion's styled-component props.

Accessibility

  • Sticky headers/index: The implementation uses position: sticky which is a standard CSS mechanism. Screen readers should still traverse the table normally since the DOM structure (<table>, <thead>, <tbody>, <th>, <td>) is unchanged.
  • Semantic HTML: The table continues to use proper <th scope="col"> for column headers and <th scope="row"> for index cells.
  • Scrollable containers: The overflow: auto on StyledTableBorder creates a scrollable region. Users relying on keyboard navigation can scroll using standard keyboard controls. However, the scrollable container does not have tabindex="0" or role="region" with an accessible name, which means keyboard-only users may not be able to focus and scroll it without a mouse. This is a pre-existing pattern in Streamlit (other scrollable elements behave similarly), but worth noting for a future accessibility pass.

Recommendations

  1. Minor perf: Consider precomputing convertRemToPx(FALLBACK_INDEX_COLUMN_OFFSET_REM) once in ArrowTable (alongside the existing indexLeftOffsets memo) and passing it as a parameter to generateTableHeader/generateTableCell, rather than calling it per-cell. This avoids redundant DOM-based rem-to-px conversions in tables with many rows.

  2. Future a11y improvement: Consider adding tabindex="0" and role="region" with an aria-label to the scrollable StyledTableBorder wrapper when scrolling is enabled, so keyboard users can discover and scroll the table. This is not a blocker for this PR.

Verdict

APPROVED: Well-implemented feature with good test coverage, proper validation, backwards-compatible defaults, and clean code that follows established Streamlit patterns. The minor optimization and accessibility suggestions are non-blocking.


This is an automated AI review by opus-4.6-thinking.

Precompute fallbackIndexColumnWidthPx once via useMemo to avoid redundant
DOM-based rem-to-px conversions per cell. Add tabindex, role, and aria-label
to scrollable table wrapper for keyboard navigation accessibility.
- Remove redundant conditional in getStickyOffset function
- Replace if-else chain with STICKY_Z_INDEX lookup map
- Remove unnecessary toBeTruthy assertions in frontend tests
- Consolidate repetitive Python tests with parameterized.expand
Comment on lines +60 to +61
// Index column: approximate width for typical index values
const FALLBACK_INDEX_COLUMN_OFFSET_REM = "7.5rem"
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: Is this really a fallback? It seems like we are not actually measuring anything on screen, and instead, we're just using this value everywhere.

In general, I think columns are going to be a bit more variable in terms of their content, so having a static value here makes me wonder about visual edge cases that may come up. Is there a better way to measure actual column widths at runtime?

Copy link
Copy Markdown
Collaborator Author

@lukasmasuch lukasmasuch Feb 13, 2026

Choose a reason for hiding this comment

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

Calculating this at runtime would be a bit complex/hacky since column width only handled by CSS. But taking a step back, this is only for multi-index cases - which are rare - and for st.table we anyways plan to auto-hide the index in many cases... so its less important to have this sticky compared to `st.dataframe.

I pushed a change to clean this up and only apply stickyness for single index columns.

Sticky index columns now only work for single-index DataFrames. This
simplifies the code by removing the fallback width approximation that
was needed for positioning multiple sticky index columns.
@lukasmasuch lukasmasuch force-pushed the lukasmasuch/table-width-height branch from 8283b2d to 9839a9a Compare February 13, 2026 15:33
@lukasmasuch lukasmasuch merged commit ae1abb5 into develop Feb 13, 2026
43 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/table-width-height branch February 13, 2026 17:12
lukasmasuch added a commit that referenced this pull request Feb 20, 2026
## Describe your changes

Implement configurable width and height support for `st.table` with
proper scrolling behavior and sticky headers/index columns. The table
element now accepts:
- `width`: `"content"` | `"stretch"` | `int` (pixels)
- `height`: `"content"` | `"stretch"` | `int` (pixels)

When a fixed pixel dimension is specified, the table enables scrolling
with sticky headers (top) and index columns (left) for easy navigation
through large datasets.

## Github Issues

- Closes #10775
- Closes #10820
- Related #13185

## Testing Plan

- Frontend unit tests validate width/height configuration, sticky
header/index positioning, and content truncation behavior
- Backend unit tests verify parameter validation and LayoutConfig
serialization
- Default behavior (`width="stretch"`, `height="content"`) maintains
backward compatibility

---

**Contribution License Agreement**

By submitting this pull request you agree that all contributions to this
project are made under the Apache 2.0 license.
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 width & height support to st.table Support width="content" in st.table()

3 participants