Skip to content

Table columns

Choose which columns to show and keep that choice for later visits in the same browser.

InvoiceClientIssuedAmount
INV-1041Northwind Traders2026-09-211280
INV-1042Blue Yonder Airlines2026-09-22460.5
INV-1043Contoso2026-09-222150
INV-1044Fabrikam2026-09-2390
<script setup lang="ts">import { ref } from 'vue'import { Plus } from '@lucide/vue'import type { TableColumn } from 'grani'import { UiCard, UiTableToolbar, UiButton, UiTable, useTableColumns } from 'grani' interface Invoice {  id: string  client: string  issued: string  amount: number  note: string} const ROWS: Invoice[] = [  {    id: 'INV-1041',    client: 'Northwind Traders',    issued: '2026-09-21',    amount: 1280,    note: 'Paid by card',  },  { id: 'INV-1042', client: 'Blue Yonder Airlines', issued: '2026-09-22', amount: 460.5, note: '' },  {    id: 'INV-1043',    client: 'Contoso',    issued: '2026-09-22',    amount: 2150,    note: 'Second reminder sent',  },  { id: 'INV-1044', client: 'Fabrikam', issued: '2026-09-23', amount: 90, note: '' },] const columns: TableColumn<Invoice>[] = [  { key: 'id', label: 'Invoice', width: '110px' },  { key: 'client', label: 'Client', truncate: true },  { key: 'issued', label: 'Issued', width: '120px' },  { key: 'amount', label: 'Amount', align: 'end', width: '110px' },  { key: 'note', label: 'Note', truncate: true },] // The invoice number says what a row is, so it cannot be hidden; the note is off until asked for.const query = ref('') const cols = useTableColumns('demo-invoices', columns, {  required: ['id'],  hiddenByDefault: ['note'],})</script> <template>  <UiCard :padded="false" class="w-full">    <UiTableToolbar v-model:search="query" :columns="cols" class="border-b border-border">      <template #end>        <UiButton><Plus />New invoice</UiButton>      </template>    </UiTableToolbar>    <UiTable :columns="cols.visible.value" :items="ROWS" />  </UiCard></template>

Also called column picker, column visibility, show hide columns.

Usage

<UiTableColumns :columns="columns" />

With GraniResolver in the Vite config, a template needs no import: set it up once.

API

Name · typeDefaultDescription
columns*TableColumns<T>The columns state from useTableColumns: every column, the hidden ones, which may be hidden. The table draws its visible.
sizexs | sm | mdStep of the control ladder for the button: the toolbar row's, the app's size (createGrani({ size })) when unset.

Attributes: class and style stay on the outer element; the rest (native attributes, aria-*, data-testid) reach the control inside it.

When to use another component

When you needTake
Narrowing the rowsFilter
A periodDate range
One choice among a fewSelect
A second menu beside a toolbarNever; the toolbar draws it from columns