# Dynamic Columns

Grid columns can be dynamically generated based on user selection or other runtime conditions.

```tsx
import { createModel } from "cx/data";
import { Controller } from "cx/ui";
import { Grid, LookupField } from "cx/widgets";

interface Person {
  id: number;
  fullName: string;
  continent: string;
  browser: string;
  visits: number;
}

interface ColumnOption {
  id: string;
  text: string;
}

interface Column {
  header: string;
  field: string;
  sortable?: boolean;
  align?: "left" | "center" | "right";
}

interface PageModel {
  records: Person[];
  visibleColumns: string[];
  $record: Person;
}

const m = createModel<PageModel>();

// @columns
const columnOptions: ColumnOption[] = [
  { id: "fullName", text: "Name" },
  { id: "continent", text: "Continent" },
  { id: "browser", text: "Browser" },
  { id: "visits", text: "Visits" },
];

const allColumns: Column[] = [
  { header: "Name", field: "fullName", sortable: true },
  { header: "Continent", field: "continent", sortable: true },
  { header: "Browser", field: "browser", sortable: true },
  { header: "Visits", field: "visits", sortable: true, align: "right" },
];
// @columns-end

class PageController extends Controller {
  onInit() {
    this.store.set(m.records, [
      { id: 1, fullName: "Alice Johnson", continent: "Europe", browser: "Chrome", visits: 45 },
      { id: 2, fullName: "Bob Smith", continent: "Asia", browser: "Firefox", visits: 23 },
      { id: 3, fullName: "Carol White", continent: "North America", browser: "Safari", visits: 67 },
      { id: 4, fullName: "David Brown", continent: "Europe", browser: "Chrome", visits: 12 },
      { id: 5, fullName: "Eva Green", continent: "Asia", browser: "Edge", visits: 89 },
    ]);

    this.store.init(m.visibleColumns, ["fullName", "continent", "browser", "visits"]);
  }
}

export default (
  <div controller={PageController}>
    <div class="mb-4 flex items-center gap-2">
      <span>Visible Columns:</span>
      <LookupField values={m.visibleColumns} options={columnOptions} multiple style="width: 300px" />
    </div>

    <Grid
      records={m.records}
      style="width: 100%"
      border
      columnParams={m.visibleColumns}
      onGetColumns={(visibleColumns: string[]) =>
        allColumns.filter((c) => visibleColumns.includes(c.field))
      }
    />
  </div>
);

```

Use the dropdown to select which columns are visible.

## Why Use Dynamic Columns?

The `columns` property is not bindable, so you cannot simply bind it to store data. Using `columnParams` and `onGetColumns` allows:

- Dynamic column visibility based on user preferences
- Preserving grid state (scroll position, column widths) when columns change
- Generating columns from runtime data

## Using columnParams and onGetColumns

Pass your column configuration data to `columnParams` and generate columns in `onGetColumns`:

```tsx
<Grid
  records={m.records}
  columnParams={m.visibleColumns}
  onGetColumns={(visibleColumns: string[]) =>
    allColumns.filter((c) => visibleColumns.includes(c.field))
  }
/>
```

When `columnParams` changes, `onGetColumns` is called to regenerate the column list. The grid preserves its internal state during this update.

## Column Definition

Define all possible columns in an array:

```tsx
const allColumns = [
  { header: "Name", field: "fullName", sortable: true },
  { header: "Continent", field: "continent", sortable: true },
  { header: "Browser", field: "browser", sortable: true },
  { header: "Visits", field: "visits", sortable: true, align: "right" },
];
```

The `onGetColumns` callback filters or transforms this array based on `columnParams`.

## Configuration

| Property | Type | Description |
| -------- | ---- | ----------- |
| `columnParams` | `any` | Data passed to `onGetColumns`. Changes trigger column regeneration. |
| `onGetColumns` | `function` | Callback that receives `columnParams` and returns the columns array. |

## Alternative: ContentResolver

For more complex scenarios, you can use `ContentResolver` to regenerate the entire grid. However, this approach loses grid state (scroll position, resized column widths) on each change.

See also: [Column Reordering](/docs/tables/column-reordering), [Grid](/docs/tables/grid)