Auto-Sizing Columns
Two questions a column width can answer — fill the space, or fit the content — and the strategy for each. Plus the imperative calls, the per-column opt-outs, and exactly when a column the reader dragged is left alone.
Declared widths
No strategy at all — every column at the width you wrote. This is the state the other two are an answer to: Note is clipped, Status is wider than it needs to be, and the total has nothing to do with the container.
What "no strategy" actually looks like
Nothing measures anything. Note holds sentences of very different lengths and gets the same 150 px as Last Name, so most of its rows are clipped. Meanwhile the total of all six widths has no relationship to the container, which is why there is either a scrollbar or a gap — and it changes with the window.
This is a perfectly good default when your widths are deliberate and your content is predictable. It is the wrong default when either of those is untrue, which is what the next two examples fix.
Fit grid width
The columns share the container exactly — no horizontal scrollbar and no empty strip on the right. Drag the window narrower and they keep up on their own; drag a column's edge and that one is left out from then on.
It keeps up on its own
fitGridWidth re-runs whenever the container resizes and whenever a
column is shown or hidden. Narrow the window and the columns re-share the space with no code from
you. defaultMinWidth is the floor for the sharing; columnLimits caps
Status at 130 px so a one-word column cannot claim a wide share.
autoSizeStrategy: {
type: 'fitGridWidth',
defaultMinWidth: 90,
columnLimits: [{ colId: 'status', maxWidth: 130 }],
}
Dragging a column opts it out — permanently
Drag the edge of Department, then narrow the window: every other column re-shares the space and yours keeps the width you gave it. That is deliberate — an auto-fit that undid a reader's drag on the next resize would be the worst version of this feature.
Worth knowing the whole rule, though: the drag sets that column's suppressSizeToFit,
so it is out of every later fit — including a
sizeColumnsToFit() you call yourself. Press Fit now after dragging
and the dragged column does not move. If you need it back in, set the flag to
false on that column, or re-declare its width.
This lock belongs to fitGridWidth only. Under fitCellContents a drag is
not remembered — see the next example.
Fit cell contents
Each column measured against its own header and cells, so Note takes the room it needs and Status gives some back. This one runs once after the first load — the button is how you ask for it again.
Once, after the first load
fitCellContents measures each column against its header and its loaded cells. It runs
once, after the first load — measuring every cell on every render is work the
reader would pay for on each scroll.
So setting it on a grid that is already up changes nothing by itself: the one automatic
run has been and gone. Call autoSizeColumns() when you want it applied again — after
new data lands, after a column is added, or when the reader asks, which is what the button above
does.
this.grid().autoSizeColumns({ type: 'fitCellContents' });
this.grid().autoSizeColumn('note'); // just one column
Two things that surprise people
- A drag is not remembered here. Widen a column by hand, press
Size to content, and it goes back to its measured width. The
suppressSizeToFitlock the previous example describes is set only underfitGridWidth; the content fit honourssuppressAutoSizeinstead, which is a flag you set, not one a gesture sets for you. - A column with a
cellTemplatecan only grow. Plain text columns are measured precisely and can end up narrower than they started. A templated column is measured from the rendered DOM instead, and that measurement expands a column that clips its content without shrinking one that has room to spare.
Filling the leftover space, and why it is off here
Sizing to content usually leaves a strip on the right — the grid above has one.
scaleUpToFitGridWidth: true is the option that closes it, and it is worth knowing
exactly what it does before you reach for it: after measuring, it runs the container fit
over the result, which shares the width out by each column's original declared
proportion. A column that just earned a wide measurement is pulled back toward the width
you first gave it.
Measured on this very page: with the flag on, Note came back to roughly its declared share and the same rows were clipped as in the first grid — the content fit had been undone by the thing meant to tidy up after it. So: use it when the columns are close to their right sizes already and you only want the gap closed. Leave it off when you actually want the measurement, and accept the strip.
The cap is a cap
defaultMaxWidth: 320 is doing real work here, and it is worth seeing what it costs.
One of the notes is longer than 320 px of text — that row is still clipped
after the fit, because the ceiling stopped the column before it reached the content. That is the
trade you are making: without the cap, one long sentence would have taken most of the table and
pushed everything else into a horizontal scroll. Raise it, drop it, or set a per-column limit — but
"fit the contents" only ever means "fit them within the bounds you gave".
Two questions, two strategies
Column widths are only ever answering one of two questions, and the strategies map onto them exactly:
- "Fill the space I have" —
{ type: 'fitGridWidth' }. Columns are scaled so their total is exactly the container's width — widened when they fall short, narrowed when they overflow. No horizontal scrollbar, and no empty strip on the right. - "Fit what is in you" —
{ type: 'fitCellContents' }. Each column is measured against its own header and cells, so a column of one-word values stays narrow and a column of sentences gets the room.
The three grids below are the same six columns and the same rows, three times. Note is where you can see the difference — it holds text of very uneven length, which is the case declared widths handle worst.
When each one runs
fitGridWidthre-runs whenever the container resizes, and when a column is shown or hidden. Put the grid in a resizable panel and the widths keep up on their own.fitCellContentsruns once, after the first load — measuring every cell on every render would be work the reader pays for on every scroll.
Calling it yourself
Both have an imperative form on the grid, which is what the buttons above the second and third
grids use. Called with no argument each one uses config.autoSizeStrategy and does
nothing when there is none — pass a strategy to the call and it works on a grid
that has none configured, so a one-off fit does not require committing to one:
@ViewChild — or viewChild() in a signal component
private readonly grid = viewChild.required<GridcraftComponent<Employee>>('grid');
this.grid().sizeColumnsToFit(); // uses config.autoSizeStrategy
this.grid().sizeColumnsToFit({ type: 'fitProvidedWidth', width: 900 }); // or an explicit width
this.grid().autoSizeColumns({ type: 'fitCellContents' }); // measure the content
this.grid().autoSizeColumn('note'); // just this one column
fitProvidedWidth exists only in this form — it sizes to a width you name rather than
to the container, which is what printing and fixed-width exports need. It never auto-triggers.
Double-click the border
Every resizable column also carries the spreadsheet gesture: double-click the divider in the header
and that column snaps to its content. It is autoSizeColumn() under the hand, works
whatever strategy is configured, and does not re-fit the other columns — one
double-click adjusts one column and leaves the rest where the reader put them.
Keeping a column out of it
Some columns should not participate. A row-number gutter, a fixed action column, an icon column — they have a correct width and any redistribution makes them wrong. Two independent opt-outs, because the two strategies do different things:
suppressSizeToFit: true— keeps its width while others flex to fill the container.suppressAutoSize: true— excluded from content measurement, including the double-click gesture.
The # column sets both, which is why it stays at 64 px in all three grids.
Bounds
A strategy without limits can produce a 30 px column or a 900 px one. Three levers, from general to specific:
defaultMinWidth/defaultMaxWidth— a floor and ceiling for every column in the calculation.columnLimits: [{ colId, minWidth, maxWidth }]— per column, and it beats the defaults and the column's ownminWidth/maxWidth.minWidth/maxWidthon the column itself — the fallback when the strategy says nothing.defaultMinWidthdoes not replace a column's ownminWidth; whichever is larger wins.
If the minimums cannot all fit, a horizontal scrollbar appears rather than columns being squeezed below a width where they are unreadable.
Dragging a column wins
Under fitGridWidth, once the reader drags a column's edge that column's width is
theirs: the grid sets its suppressSizeToFit and leaves it out of
every later fit — the automatic ones and the calls you make. Programmatic calls are never treated
as drags, so a button press does not lock anything.
fitCellContents does not do this. A drag is not remembered there, and the next
content measurement overwrites it — suppressAutoSize is the flag that protects a
column from that one, and only you can set it.
fitCellContents measures only the rows that are loaded. On a paged or
scrolled grid that is the first page, which is usually the right trade — but if row 900 holds a
much longer value, expect it to be clipped rather than to widen the column retroactively.
Auto-sizing options
| Property | Type | Default | Description |
|---|---|---|---|
| config.autoSizeStrategy | { type: 'fitGridWidth' | 'fitCellContents' | 'fitProvidedWidth', … } | — | How columns size themselves. fitGridWidth re-runs on container resize and column show/hide; fitCellContents runs once after the first load; fitProvidedWidth only runs when you call it. |
| strategy.defaultMinWidth | number | the column's <code>minWidth</code>, else 50 | Floor for every column in the calculation. Does not replace a column's own minWidth — the larger of the two applies. |
| strategy.defaultMaxWidth | number | the column's <code>maxWidth</code>, else none | fitCellContents only — stops one long value taking the whole table. |
| strategy.columnLimits | { colId, minWidth?, maxWidth? }[] | — | Per-column bounds. Beats the defaults above and the column's own values. |
| strategy.skipHeader | boolean | false | Measure cells only, ignoring the header text — for a long header over short values. |
| strategy.scaleUpToFitGridWidth | boolean | false | After sizing to content, share out any leftover space so the table still fills the container. |
| column.suppressSizeToFit | boolean | false | Keep this column at its width while the others flex. The grid sets this itself when the reader drags the column, and only under fitGridWidth. |
| column.suppressAutoSize | boolean | false | Exclude this column from content measurement, including the double-click gesture. Never set for you. |
| grid.sizeColumnsToFit() | (strategy?) => void | — | Fit the columns to the container, or to an explicit fitProvidedWidth. Skips columns with suppressSizeToFit. |
| grid.autoSizeColumns() | (strategy?) => void | — | Measure every column against its loaded content. A cellTemplate column can grow but not shrink. |
| grid.autoSizeColumn() | (key: string) => void | — | The same for one column — what double-clicking the header divider calls. |