# Moving & hiding

> Let users drag columns into a new order and hide or show them from a column menu or a Columns panel, or drive visibility from your own UI.

Users can drag a column header to move the column, and hide or show columns from a per-column menu or a Columns panel. Your app can also hide columns itself through `hiddenColumns`, and the grid reports every change so you can save it.

Drag a header sideways to move its column. Open a header's ⋮ menu to sort, pin, autosize or hide it, and use the Columns button to bring hidden columns back:

Example: Moving and hiding columns (column-moving.svelte)

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

  const columns: ColumnDef[] = [
    { type: 'text', key: 'symbol', header: 'Symbol', width: 90 },
    { type: 'text', key: 'name', header: 'Company', width: 140 },
    { type: 'price', key: 'price', header: 'Price', width: 90 },
    { type: 'percent', key: 'change', header: 'Chg %', width: 90 },
    { type: 'volume', key: 'volume', header: 'Volume', width: 100 },
    { type: 'text', key: 'sector', header: 'Sector', flex: 1 },
  ];

  let rows = $state(quotes());
  let order = $state(columns.map((c) => c.key));
  let hidden = $state<string[]>([]);
</script>

<p class="state">
  Order: {order.join(' · ')}<br />
  Hidden: {hidden.length ? hidden.join(', ') : 'none'}
</p>
<Grid
  {rows}
  {columns}
  columnMenu
  columnsPanel
  height={260}
  onColumnReorder={(keys) => (order = keys)}
  onColumnVisibilityChange={(keys) => (hidden = keys)}
/>

<style>
  .state {
    margin: 0 0 10px;
    font-size: 12.5px;
    line-height: 1.6;
    opacity: 0.8;
  }
</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 }));
}
```

## Moving columns

Drag any column header to a new position. `onColumnReorder` receives the new order of column keys, and `persistKey` remembers it across reloads (in `localStorage`):

```svelte
<Grid {rows} {columns} persistKey="watchlist" onColumnReorder={(keys) => (columnOrder = keys)} height={480} />
```

The `columns` array you pass keeps its own order; the user's order is layered on top. `api.getState()` and `api.applyState()` capture and restore it along with widths, pins, sorts and filters (see [Grid API](https://bonguynvan.github.io/bo-grid/docs/grid-api/)).

## The column menu

`columnMenu` adds a ⋮ button to each header, also opened with <kbd>Alt</kbd>+<kbd>↓</kbd> on the focused column. It offers:

- **Sort** ascending or descending.
- **Pin** left or right, or unpin (see [Pinned columns](https://bonguynvan.github.io/bo-grid/docs/pinned-columns/)).
- **Autosize**: fit the column to its content.
- **Hide column**.
- **Filter**, when the grid also has `filterMenu` (see [Sorting & filtering](https://bonguynvan.github.io/bo-grid/docs/sorting-filtering/)).

## The Columns panel

`columnsPanel` adds a Columns button that opens a checklist of every column, to hide and show them. It is the place to bring back a column hidden from the menu.

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

## Hiding columns from your app

`hiddenColumns` takes the keys of columns to hide. It is controlled: build your own column picker and drive the prop. Columns the user hides at runtime are added to it, and `onColumnVisibilityChange` reports the full set of hidden keys whenever it changes:

```svelte
<script lang="ts">
  let hidden = $state(['bonus', 'rating']);
</script>

<Grid {rows} {columns} hiddenColumns={hidden} columnMenu onColumnVisibilityChange={(keys) => (hidden = keys)} height={480} />
```

Runtime hides and pins are remembered under `persistKey` too.

## Reference

- `onColumnReorder`: `(keys: string[]) => void` — Called with the new column-key order after a header drag-reorder.

  ```svelte
  <Grid {rows} {columns} height={480} onColumnReorder={(keys) => save('order', keys)} />
  ```

- `columnMenu`: `boolean` (default `false`) — Enable a per-column header menu (a ⋮ trigger) with sort, hide and (with `filterMenu`) filter actions.
- `columnsPanel`: `boolean` (default `false`) — Show a "Columns" button that opens a panel to toggle column visibility (the place to restore columns hidden via the menu). Lazy-loaded.
- `hiddenColumns`: `string[]` (default `[]`) — Column keys to hide (controlled). Build your own column-picker UI and drive this prop — the grid stays presentation-only. Composed (union) with columns the user hides at runtime via the column menu.

  ```svelte
  <Grid {rows} {columns} height={480} hiddenColumns={showVolume ? [] : ['vol']} />
  ```

- `onColumnVisibilityChange`: `(hidden: string[]) => void` — Called with all currently-hidden column keys whenever the runtime set changes (column menu hide/show).

  ```svelte
  <Grid {rows} {columns} height={480} columnMenu onColumnVisibilityChange={(hidden) => save('hidden', hidden)} />
  ```

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

- `getState(): GridState` — Snapshot of the user's layout: order, widths, hidden, pins, sorts, filters. A plain object, safe to `JSON.stringify` and store.

  ```ts
  localStorage.setItem('orders-layout', JSON.stringify(api.getState()));
  ```

- `applyState(state: unknown): boolean` — Restore a snapshot, reconciled against the columns as they are now. Returns false when the state is unusable (foreign version, not an object).

  ```ts
  const saved = localStorage.getItem('orders-layout');
  if (saved) api.applyState(JSON.parse(saved));
  ```

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