<!-- https://svgrid.com/docs/help/columns/column-sizing/ - SvGrid documentation as markdown. Index of every page: https://svgrid.com/llms.txt -->

# Column sizing

Each column has a pixel width. The default for all columns is the grid's
`columnWidth` prop (default ~140 px); each column can override via its
`width` field.
> Live demo: Column layout API - https://svgrid.com/demos/63-column-layout-api/

The examples on this page run against these rows:

```svelte
<script lang="ts">
  import { SvGrid, type GridColumns } from '@svgrid/grid'

  type Person = {
    id: number
    name: string
    department: string
    city: string
    age: number
    salary: number
  }

  const people: Person[] = [
    { id: 1, name: 'Ada Lovelace',   department: 'Engineering', city: 'London',   age: 36, salary: 142000 },
    { id: 2, name: 'Grace Hopper',   department: 'Engineering', city: 'New York', age: 45, salary: 168000 },
    { id: 3, name: 'Linus Torvalds', department: 'Platform',    city: 'Portland', age: 54, salary: 155000 },
    { id: 4, name: 'Radia Perlman',  department: 'Networking',  city: 'Seattle',  age: 49, salary: 161000 },
    { id: 5, name: 'Barbara Liskov', department: 'Platform',    city: 'Boston',   age: 52, salary: 172000 },
  ]

  let rows = $state<Person[]>(people)
  const data = people

  const columns: GridColumns<Person> = [
    { field: 'name',       header: 'Name',       width: 200 },
    { field: 'department', header: 'Department', width: 150 },
    { field: 'city',       header: 'City',       width: 140 },
    { field: 'age',        header: 'Age',        width: 90 },
    { field: 'salary',     header: 'Salary',     width: 130, format: { type: 'currency', currency: 'USD' } },
  ]
</script>
```

```ts
const columns: GridColumns<Person> = [
  { field: 'firstName',  header: 'First name', width: 150 },
  { field: 'department', header: 'Department', width: 180 },
  { field: 'salary',     header: 'Salary',     width: 120 },
]
```

```svelte
<SvGrid {columns} {data} features={{}} columnWidth={140} />
```

## User resizing

Off by default. Set `columnResize` and every header grows a drag handle on its
right edge: drag to widen or narrow, minimum 40 px, or focus the handle and use
Left/Right (Shift for 1px steps). Widths are stored per column id inside the
grid, so a resize survives sorting and filtering.

Leaving it off is the reason `width` means what it says - the width you asked
for, not a starting point a user can drag away:

```svelte
<script lang="ts">
  import { SvGrid, type GridColumns } from '@svgrid/grid'

  type Person = { id: number; name: string; city: string }

  const people: Person[] = [
    { id: 1, name: 'Ada Lovelace', city: 'London' },
    { id: 2, name: 'Grace Hopper', city: 'New York' },
  ]

  const columns: GridColumns<Person> = [
    { field: 'name', header: 'Name', width: 180 },
    { field: 'city', header: 'City', width: 150 },
  ]
</script>

<p>Fixed widths (the default):</p>
<SvGrid data={people} {columns} containerHeight={140} />

<p>With <code>columnResize</code> - drag a header edge:</p>
<SvGrid data={people} {columns} containerHeight={140} columnResize />
```

### A drag is one undo

A width the user drags, or double-clicks into, is one step in the grid's
undo history: Ctrl+Z puts it back, Ctrl+Y widens it again. `onColumnResize`
reports each such change with the column id and the new width.
`api.setColumnWidth` and `api.autosizeColumn` record nothing and fire
nothing, so restoring saved widths does not fill the history.

### Locking one column: `resizable: false`

`columnResize` turns resizing on for the grid; a column opts back out with
`resizable: false`. Use it where the width is part of the layout rather than a
preference - a row-number gutter, a checkbox column, a fixed icon column.

Opting out removes that column's handle entirely, so the drag, the arrow keys
and double-click-to-autosize all go with it, and the column menu drops its
Autosize item. Nothing is left that lets a user change the width by accident:

```svelte
<script lang="ts">
  import { SvGrid, type GridColumns } from '@svgrid/grid'

  type Person = { id: number; name: string; city: string }

  const people: Person[] = [
    { id: 1, name: 'Ada Lovelace', city: 'London' },
    { id: 2, name: 'Grace Hopper', city: 'New York' },
  ]

  const columns: GridColumns<Person> = [
    // Fixed: the row id is 60px because that is what fits, not a preference.
    { field: 'id',   header: '#',    width: 60, resizable: false },
    { field: 'name', header: 'Name', width: 200 },
    { field: 'city', header: 'City', width: 160 },
  ]
</script>

<SvGrid data={people} {columns} columnResize containerHeight={150} />
```

The column option only ever subtracts. `resizable: true` on a grid without
`columnResize` does nothing - the grid-wide prop is still the switch.

Programmatic sizing is unaffected either way: `api.setColumnWidth()`,
`api.autosizeColumn()` and `fitColumns` all still apply to a locked column,
exactly as they do when `columnResize` is off.

The handles come from an action the grid loads on demand, so a grid that leaves
`columnResize` off never downloads that code.

## Programmatic resizing + persistence

The imperative API exposes `setColumnWidth` and `getColumnWidths`:

```svelte
<script lang="ts">
  import type { SvGridApi } from '@svgrid/grid'
  let api = $state<SvGridApi<typeof features, Person> | null>(null)

  function save() {
    if (!api) return
    localStorage.setItem('widths', JSON.stringify(api.getColumnWidths()))
  }
  function restore() {
    if (!api) return
    const saved: Record<string, number> = JSON.parse(localStorage.getItem('widths') ?? '{}')
    for (const [id, w] of Object.entries(saved)) api.setColumnWidth(id, w)
  }
</script>

<SvGrid {data} {columns} features={features}
  onApiReady={(next) => (api = next)} />

<button onclick={save}>Save layout</button>
<button onclick={restore}>Restore layout</button>
```

`getColumnWidths()` returns every column's *current effective* width
(user resize OR columnDef `width` OR grid-wide default), so the
snapshot round-trips cleanly.

## Auto-fit

`fitColumns={true}` scales every column proportionally to fill the
viewport - the wrapper handles rounding-residue absorption + a modest
shrink-to-fit (down to ~85 % of natural widths). For "fit to content"
(longest cell text), pre-compute per-column widths once after a data
load and call `api.setColumnWidth(id, w)` per column.

## Column virtualization

With many columns, enable column virtualization so only the visible columns
render:

```svelte
<SvGrid
  {columns}
  {data}
  features={{}}
  columnVirtualization={true}
  columnWidth={120}
  columnOverscan={3}
/>
```

`columnOverscan` is how many columns stay rendered ahead of a horizontal
scroll at all times (default 3), with one kept behind it. The rendered
columns don't move on every column boundary: they stay put while that
many are left ahead, then move three at once. Each move updates every
rendered row, whether one column comes in or four, so fewer, larger
moves are less work, and the scroll frames in between change nothing.
Right after a move, up to `columnOverscan + 2`
columns are rendered ahead. Raise it if fast scrolling shows blank columns
before they fill in; lower it for less work per scroll.

See [examples/src/demos/06-large-dataset.svelte](https://svgrid.com/demos/06-large-dataset/)
for a 100-column virtualized grid.

## Collapsing a column to nothing

`api.setColumnCollapsed(id, true)` folds a column away the way a spreadsheet
hides one: the column keeps its place, its cells and the width it had, and
takes no room until `setColumnCollapsed(id, false)` unfolds it. Its
neighbours' borders meet at the seam. Arrow keys, Tab and Enter step over a
collapsed column, so the cursor never rests on it; `api.setActiveCell` and a
sheet's Name Box still can. `api.isColumnCollapsed(id)` reads the state.

This is not `setColumnVisible(id, false)`, which takes the column out of the
model: every column after a hidden one moves up an index, which is right for
a "Choose columns" panel and wrong for anything that addresses cells by
position, such as a sheet's formulas and formats. Collapse when the indices
must hold; hide when the column should be gone.

```ts
api.setColumnCollapsed('cost', true)   // folded: cells and width kept, no room
api.isColumnCollapsed('cost')          // true
api.setColumnCollapsed('cost', false)  // back at the width it had
```

The spreadsheet shell's Hide and Unhide (Ctrl+9, Ctrl+0, and the header
menus) are this, kept per sheet.

## Gotchas

- Under `columnResize`, a `width` in the column def is an **initial** width:
  once the user drags, their override wins for the rest of the session. Without
  the prop it is simply the width.
- To persist what the user chose, read `api.getColumnWidths()` and replay it
  with `api.setColumnWidth(id, w)` - see [Programmatic resizing](#programmatic-resizing--persistence)
  above. `api.getState()` carries `columnWidths` too, if you are already
  saving a whole view.

## Try it

Without `fitColumns` the explicit widths are used as-is and any leftover space
stays empty on the right. With it, the columns scale to fill. Both carry
`columnResize`, so drag a header edge in either - a user resize wins from then
on, including over `fitColumns`.

```svelte
<SvGrid data={people} {columns} columnResize />

<SvGrid data={people} {columns} columnResize fitColumns />
```

## See also

- [Column moving](https://svgrid.com/docs/help/columns/column-moving/)
- [Column pinning](https://svgrid.com/docs/help/columns/column-pinning/)

---

SvGrid is the Svelte 5 data grid (`npm install @svgrid/grid`, MIT). This page: https://svgrid.com/docs/help/columns/column-sizing/ . All docs: https://svgrid.com/llms.txt
