Why Won't a Perspective Table Auto Size in a Flex View?

Stefan Weidner7 min read
B&R AutomationHMI / SCADATroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

A table dropped into a flex row does not report its content height back to the layout engine the way a label or a text field does. Set position.basis to auto and the container hands the table exactly what it asks for — and what it asks for is the header. The body collapses. The rows are still in the model; they are simply not part of the measured box.

Where does the sizing request stop?

Follow the layout pass. The flex container asks each child for its intrinsic content height. A normal component answers with the sum of its rendered children. The Perspective table, with props.virtualized at its default of true, does not render every row. It renders a window of rows sized to the visible viewport, recycles the DOM nodes as you scroll, and keeps the row container height driven by scroll position rather than by the dataset.

That is the whole failure. Virtualization deliberately decouples DOM height from record count so that a 50,000-row dataset does not create 50,000 DOM nodes. When the parent then asks "how tall are you?", the only unambiguously measurable element is the header (plus pager and filter bar if enabled). The answer comes back as header-only, the flex container obliges, and the body gets zero usable height. It is not a bug in auto; auto is working exactly as specified against a component that has been told not to lay out its own content.

The same mechanism explains the related symptom: a table used as a subview inside another table, or nested two flex levels deep, that renders at a few pixels tall. Each level of the chain is resolving to content size, and the content size is a stub.

Which sizing approach fits the case?

Four approaches survive contact with a real flex view. They differ in accuracy, in what breaks them, and in what they cost at scale.

Approach Config Accuracy Fails when Cost
Expression × pixel constant position.basis bound to rowCount * N Drifts — error accumulates with row count Row height changes with theme, font, density, wrapped cell text, or a style class None at runtime, high maintenance
Virtualization off + auto basis props.virtualized = false, position.basis = auto Exact — measured from rendered rows Dataset is large or unbounded One DOM row set per record on the page
Fixed basis + grow position.basis in px, position.grow = 1 Fills space, does not shrink to content You need the table to give space back when rows are few None
Hide on empty meta.visible bound to rowCount > 0 Complementary, not a sizing method Never — use it alongside any of the above None

The expression approach is the one most people land on first because it works on the desk with five rows. The multiplier is a measurement of one particular render at one particular font scale. Add a pager, turn on the filter bar, let one cell wrap to two lines, or open the view on a client with a different browser zoom, and every row contributes a slightly different error. At twenty rows the table clips or floats.

What to configure when the dataset is bounded

If the table is paged — and a table with a "rows per page" setting is bounded by definition — turn virtualization off and let the layout engine do the arithmetic.

  1. Select the table. In the Property Editor, set props.virtualized to false.
  2. Set position.basis to auto.
  3. Set position.grow to 0 and position.shrink to 0. Grow will stretch the table past its content and defeat the point; shrink will squeeze it below content when siblings compete for space.
  4. Confirm the parent flex container's direction is column so that basis resolves against height rather than width. In a row container, basis governs the cross-axis differently and the table will size horizontally instead.
  5. Bind meta.visible to a row-count expression so the empty table disappears rather than leaving a floating header.
  6. Remove the old pixel-multiplier binding on position.basis entirely. A leftover binding will overwrite auto on the first data change and the symptom will appear intermittent.

With virtualization off, every row in the current page is a real DOM element, the row container measures correctly, and the table grows to the rows-per-page ceiling and shrinks to the actual record count with no constant to maintain.

When to keep virtualization on

Virtualization is not optional overhead — it is the reason a Perspective table stays responsive on a tablet or a thin client when the query returns thousands of records. Disabling it puts the full record set into the DOM of every session that has the view open. Session count multiplies the cost.

Keep virtualized = true and use a fixed position.basis with an internal scrollbar when any of these hold: the dataset is unbounded or driven by an ad-hoc query, paging is off, the rows-per-page ceiling is in the hundreds, or the view is opened by many concurrent mobile sessions. In that configuration you accept a scrollbar instead of an auto-fitting container, which is the correct trade — a table tall enough to display 500 rows without scrolling is not usable anyway.

If the dataset is bounded but you still want a hard ceiling, combine the two: virtualized = false, position.basis = auto, and a maxHeight style on the table. The table auto-fits up to the cap, then scrolls.

What to measure if you must keep the expression

Some layouts cannot give up virtualization — a large audit-log table inside a dashboard, for example. If the pixel-multiplier binding stays, stop guessing the constant and measure it.

  1. Open the view in a browser session and inspect the table with the browser's element inspector.
  2. Read the computed height of the header element and of one body row element separately. Do not derive the row height by dividing total height by row count; the header, pager, and filter bar are in that total.
  3. Check the table's props tree for a row-height property under the rows configuration. If the component exposes one, set it explicitly instead of inheriting a theme-derived value — a pinned row height is the only way the multiplier stays valid across themes.
  4. Build the binding as header + (rowCount * rowHeight) + pager, with each term a measured number rather than a single fused constant, so a theme change means editing one term.
  5. Re-measure after any change to font size, density, cell style class, or the enabling of the filter bar or pager.

This remains the least durable of the four approaches. Treat it as a workaround with an expiry date, not a design.

How to verify the fix

Layer one first: confirm the rendered geometry before trusting the props. In a live session, expand the query so the page fills to the rows-per-page ceiling and confirm the container height grows without an internal scrollbar. Then filter the dataset down to two or three rows and confirm the container shrinks and the siblings below it move up — if they do not, position.grow is still non-zero. Drive the row count to zero and confirm the whole component disappears rather than leaving an orphan header.

Then confirm the cost. With the inspector open, count the rendered row elements against the record count: with virtualized = false they match one-to-one, which is the proof the change took effect and also the number you are paying per session. Finally, open the same view in a second session at a different browser zoom and confirm the table still ends flush against the next flex row — that is the check the pixel-multiplier approach fails and the auto-basis approach passes.

FAQ

Why does setting position.basis to auto shrink a Perspective table to just the header?

With props.virtualized at its default true, the table renders only a viewport-sized window of rows, so its intrinsic content height reported to the flex container is the header plus any pager. Set virtualized to false and auto resolves against the real rendered rows.

Why does my row-count times pixel-height expression drift as rows are added?

The multiplier is a snapshot of one render. Header, pager, filter bar, wrapped cell text, theme font scale, and browser zoom each add a per-row or fixed offset that the single constant does not carry, so the error compounds with row count.

Why should I not disable virtualization on every table?

Virtualization caps DOM node count regardless of dataset size. Turning it off puts every record into the DOM of every open session, so reserve virtualized = false for paged or otherwise bounded tables and keep a fixed basis with a scrollbar for large or unbounded datasets.

Back to blog