# Column sizing

> Set fixed and flexible widths, let users resize columns, scale every column with the grid, and widen columns whose values outgrow them.

Every column has a fixed `width` or a `flex` weight, and users can drag a header's edge to change it. Two switches change how columns respond to the grid's own width: `fitColumns` scales every column with it, and a column's `autoWidth` widens it when its values no longer fit.

Drag a header's right edge to resize: only that column changes. Double-click the edge to reset it. Toggle `fitColumns`, then resize the browser window to see the difference:

Example: Resizing and fitColumns (column-sizing.svelte)

```svelte
<script lang="ts">
  import { Grid, type ColumnDef } from 'bo-grid';
  import { quotes } from './data';

  // Drag a header's right edge to resize (only that column changes);
  // double-click the edge to reset it, or use Reset widths in a column menu.
  const columns: ColumnDef[] = [
    { type: 'text', key: 'symbol', header: 'Symbol', width: 90, minWidth: 70, maxWidth: 160 },
    { type: 'text', key: 'name', header: 'Company', width: 150 },
    { type: 'price', key: 'price', header: 'Price', width: 90 },
    { type: 'percent', key: 'change', header: 'Chg %', width: 90 },
    // Widens on its own when a value no longer fits.
    { type: 'volume', key: 'volume', header: 'Volume', width: 70, autoWidth: true },
    // A fixed action-like column: no resize grip.
    { type: 'text', key: 'sector', header: 'Sector', width: 120, resizable: false },
  ];

  let rows = $state(quotes());
  // Off: only flex columns take up a width change. On: every column scales.
  let fit = $state(true);
</script>

<label class="opt"><input type="checkbox" bind:checked={fit} /> fitColumns</label>
<Grid {rows} {columns} fitColumns={fit} columnMenu height={260} />

<style>
  .opt {
    display: inline-flex;
    gap: 6px;
    align-items: center;
    margin-bottom: 10px;
    font-size: 13px;
  }
</style>
```

data.ts:

```ts
// Small, fixed data for the docs' examples: every example reads the same
// symbols, so a reader compares features, not datasets.
// A type, not an interface: it then fits GridRow's index signature.
export type Quote = {
  id: number;
  symbol: string;
  name: string;
  sector: string;
  price: number;
  change: number;
  volume: number;
};

const BASE: Omit<Quote, 'id'>[] = [
  { symbol: 'VNM', name: 'Vinamilk', sector: 'Consumer', price: 68.5, change: 1.2, volume: 1_204_000 },
  { symbol: 'FPT', name: 'FPT Corp', sector: 'Technology', price: 132, change: -0.4, volume: 860_500 },
  { symbol: 'HPG', name: 'Hoa Phat', sector: 'Materials', price: 27.35, change: 0.74, volume: 3_412_900 },
  { symbol: 'VCB', name: 'Vietcombank', sector: 'Financials', price: 91.5, change: 0.55, volume: 1_986_200 },
  { symbol: 'MWG', name: 'Mobile World', sector: 'Consumer', price: 51.4, change: -1.1, volume: 2_250_400 },
  { symbol: 'SSI', name: 'SSI Securities', sector: 'Financials', price: 33.3, change: -0.45, volume: 4_104_700 },
  { symbol: 'VIC', name: 'Vingroup', sector: 'Real estate', price: 44.15, change: -0.11, volume: 1_530_100 },
  { symbol: 'GAS', name: 'PV Gas', sector: 'Energy', price: 71.8, change: 0.3, volume: 645_800 },
  { symbol: 'MSN', name: 'Masan', sector: 'Consumer', price: 76.2, change: 2.05, volume: 1_120_300 },
  { symbol: 'TCB', name: 'Techcombank', sector: 'Financials', price: 23.9, change: 0.84, volume: 5_870_000 },
  { symbol: 'VHM', name: 'Vinhomes', sector: 'Real estate', price: 41.6, change: -0.72, volume: 2_904_600 },
  { symbol: 'POW', name: 'PV Power', sector: 'Energy', price: 12.25, change: 1.66, volume: 6_315_200 },
];

/** A fresh copy of the quotes, so one example's edits never reach another. */
export function quotes(): Quote[] {
  return BASE.map((q, i) => ({ id: i + 1, ...q }));
}
```

## Fixed and flexible widths

- **`width`** is a fixed width in pixels.
- **`flex`** is a grow weight: flex columns share the space that fixed columns leave, in proportion to their weights. `flex` wins over `width`.

By default, only flex columns take up a change in the grid's width; fixed columns stay as they are.

## Resizing

Drag the grip on a header's right edge to resize a column. Only that column changes: every other column keeps the width it has on screen, flex columns included, and the columns after it move along. When the columns grow wider than the grid, it scrolls sideways; when they shrink, the space is left at the right.

- Double-click a grip to give that column back its own width. A flex column then fills the space the others leave again.
- **Reset widths**, in the column menu (`columnMenu`), gives every column back its own width.
- Autosize, from the column menu or `api.autosizeColumns`, changes columns the same way: the others keep their widths.
- `minWidth` and `maxWidth` bound the drag.
- Resizing is on by default. Turn it off for the grid with `resizable={false}`, or for one column with `resizable: false`, for example a narrow action column.
- `onColumnResize(key, width)` reports each resize; `persistKey` remembers widths across reloads.

```svelte
<Grid {rows} {columns} persistKey="watchlist" onColumnResize={(key, width) => save(key, width)} height={480} />
```

## Scaling every column: fitColumns

With `fitColumns`, every column scales with the grid. Each gets a share of the width in proportion to its `width`, bounded by its `minWidth` and `maxWidth`. A column with no `minWidth` shrinks to 64 px at most (or keeps its own width, if that is smaller). The grid scrolls sideways only when those floors no longer fit.

```svelte
<Grid {rows} {columns} fitColumns height={480} />
```

`fitColumns` fits until the user sets a width. After a drag or an autosize, the columns keep the widths they were given, as they do without it, until **Reset widths** brings the fit back.

## Growing with the values: autoWidth

Values that grow, such as a price gaining a digit or a running volume total, can outgrow their column and end in "…". Set `autoWidth: true` on such a column and it widens to fit the values on screen.

```ts
{ type: 'volume', key: 'totalVol', header: 'Total vol', width: 76, autoWidth: true }
```

- It is checked at most every 500 ms, and values are measured on a canvas, so it never forces a layout on a live board.
- It only grows, so values flickering wider and narrower don't make the column jump. `maxWidth` caps it.
- A width the user dragged or autosized takes over from it.

## Autosizing from code

`api.autosizeColumns(keys?)` fits columns to their content, as the column menu's Autosize item does; with no keys it fits every resizable column. See [Grid API](https://bonguynvan.github.io/bo-grid/docs/grid-api/).

## Reference

- `width`: `number` (default `96`) — Width in px. A `flex` column starts from it where columns have fixed widths (pinned columns, `virtualizeColumns`), and `fitColumns` scales columns in proportion to it.
- `flex`: `number` (default `undefined`) — flex-grow weight: the column stretches to fill the space the fixed-width columns leave, shared by weight. When the columns no longer fit, the grid scrolls sideways, counting a flex column as its `width`, else its `minWidth`, else 64 px. Unset, the column keeps its `width`.
- `minWidth`: `number` (default `48`) — Smallest width (px) a drag-resize or autosize can give this column; never less than 48. Under `fitColumns`, also the narrowest the column gets before the grid scrolls sideways (64 px without one, or the column's `width` if smaller).
- `maxWidth`: `number` (default `Infinity`) — Largest width (px) a drag-resize, an autosize, `autoWidth` or `fitColumns` can give this column.
- `resizable`: `boolean` (default `true`) — Set false to disable drag-to-resize on this column.
- `autoWidth`: `boolean` (default `false`) — Widen the column when a value on screen no longer fits (a price gaining a digit, a running total), checked at most every 500 ms. It only grows, so values flickering wider and narrower don't make it jump; `maxWidth` caps it, and a width the user dragged or autosized takes over.

- `resizable`: `boolean` (default `true`) — Allow drag-to-resize column widths. A drag (or an autosize) changes that column only: the others keep the width they have on screen, flex columns included, so the ones after it move along. Double-click a grip to give that column back its own width; Reset widths in the column menu gives every column back its own. Opt out per column with `resizable: false`.
- `fitColumns`: `boolean` (default `false`) — Scale every column with the grid, in proportion to its `width`, instead of only `flex` columns taking up the change: the grid stays responsive without hand-tuned flex weights. A column never gets narrower than its `minWidth` (64 px, or its own width if smaller, without one) nor wider than its `maxWidth`; when the floors no longer fit, the grid scrolls sideways. It fits until the user sets a width (a drag, an autosize): from then on the columns keep the widths they were given, until Reset widths in the column menu.
- `onColumnResize`: `(key: string, width: number) => void` — Called with a column key + new width after a drag-resize. Only the dragged column is reported; `getState()` has every width.

  ```svelte
  <Grid {rows} {columns} height={480} onColumnResize={(key, width) => save(`width:${key}`, width)} />
  ```

- `persistKey`: `string` (default `undefined`) — Remember the user's layout (column order, widths, hidden and pinned columns, pinned rows) in localStorage under this key.

- `autosizeColumns(keys?: string[]): void` — Fit columns to their content, as the column menu's Autosize does. All resizable columns when `keys` is omitted. The other columns keep their widths (flex ones are held where they are).

  ```ts
  api.autosizeColumns(['sym', 'name']);
  ```

---
Page: https://bonguynvan.github.io/bo-grid/docs/column-sizing/ · All pages: https://bonguynvan.github.io/bo-grid/llms.txt
