Documentation
Column Resizing
Let users adjust column widths by dragging header dividers — or double-click to auto-fit.
Enable resizing
Set columnResizing to let users drag header dividers. Double-click a handle to auto-fit that column to its content.
React TSX
Copy
<SimpleTable columns={columns} rows={rows} height="400px" columnResizing={true} />
Angular
Copy
<simple-table[columns]="columns"[rows]="rows"height="400px"[columnResizing]="true"></simple-table>
Vue SFC
Copy
<SimpleTable:columns="columns":rows="rows"height="400px":column-resizing="true"/>
Svelte
Copy
<SimpleTable {columns} {rows} height="400px" columnResizing={true} />
Solid TSX
Copy
<SimpleTable columns={columns} rows={rows} height="400px" columnResizing={true} />
TypeScript
Copy
const table = new SimpleTableVanilla(container, {columns,rows,height: "400px",columnResizing: true,});table.mount();
Persist column widths
Use onColumnWidthChange to save widths whenever the user resizes or auto-fits a column.
React TSX
Copy
const handleColumnWidthChange = (headers) => {const widths = Object.fromEntries(headers.map((h) => [h.accessor, h.width]));localStorage.setItem("columnWidths", JSON.stringify(widths));};<SimpleTablecolumnResizingcolumns={columns}rows={rows}onColumnWidthChange={handleColumnWidthChange}/>
Angular
Copy
const handleColumnWidthChange = (headers) => {const widths = Object.fromEntries(headers.map((h) => [h.accessor, h.width]));localStorage.setItem("columnWidths", JSON.stringify(widths));};<simple-table[columnResizing]="true"[columns]="columns"[rows]="rows"(columnWidthChange)="handleColumnWidthChange($event)"></simple-table>
Vue SFC
Copy
<script setup>const handleColumnWidthChange = (headers) => {const widths = Object.fromEntries(headers.map((h) => [h.accessor, h.width]));localStorage.setItem("columnWidths", JSON.stringify(widths));};</script><template><SimpleTable:column-resizing="true":columns="columns":rows="rows"@column-width-change="handleColumnWidthChange"/></template>
Svelte
Copy
<script>const handleColumnWidthChange = (headers) => {const widths = Object.fromEntries(headers.map((h) => [h.accessor, h.width]));localStorage.setItem("columnWidths", JSON.stringify(widths));};</script><SimpleTablecolumnResizing={true}{columns}{rows}onColumnWidthChange={handleColumnWidthChange}/>
Solid TSX
Copy
const handleColumnWidthChange = (headers) => {const widths = Object.fromEntries(headers.map((h) => [h.accessor, h.width]));localStorage.setItem("columnWidths", JSON.stringify(widths));};<SimpleTablecolumnResizingcolumns={columns}rows={rows}onColumnWidthChange={handleColumnWidthChange}/>
TypeScript
Copy
const handleColumnWidthChange = (headers) => {const widths = Object.fromEntries(headers.map((h) => [h.accessor, h.width]));localStorage.setItem("columnWidths", JSON.stringify(widths));};new SimpleTableVanilla(container, {columns,rows,columnResizing: true,onColumnWidthChange: handleColumnWidthChange,});
Example
Drag dividers or double-click to auto-fit. This demo saves widths to localStorage.
React TSX
Copy
1import { useState, useEffect } from "react";2import { SimpleTable } from "@simple-table/react";3import type { Theme, ReactColumnDef } from "@simple-table/react";4import {5 columnResizingHeaders,6 columnResizingData,7 COLUMN_RESIZING_STORAGE_KEY,8 type OceanStaff9} from "./column-resizing.demo-data";10import "@simple-table/react/styles.css";1112const ColumnResizingDemo = ({13 height = "400px",14 theme15}: {16 height?: string | number;17 theme?: Theme;18}) => {19 const [headers, setHeaders] = useState(() => columnResizingHeaders);20 const [saveMessage, setSaveMessage] = useState("");2122 useEffect(() => {23 try {24 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);25 if (saved) {26 const widthMap = JSON.parse(saved);27 setHeaders(28 columnResizingHeaders.map((h) => ({29 ...h,30 width: (widthMap as Record<string, number | string | undefined>)[h.accessor] ?? h.width31 })),32 );33 }34 } catch {35 // ignore36 }37 }, []);3839 const handleColumnWidthChange = (updatedHeaders: ReactColumnDef<OceanStaff>[]) => {40 try {41 const widthMap = updatedHeaders.reduce(42 (acc, h) => {43 acc[h.accessor] = h.width;44 return acc;45 },46 {} as Record<string, number | string>,47 );48 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));49 setHeaders(updatedHeaders);50 setSaveMessage("Column widths saved!");51 setTimeout(() => setSaveMessage(""), 2000);52 } catch {53 setSaveMessage("Failed to save widths");54 setTimeout(() => setSaveMessage(""), 2000);55 }56 };5758 return (59 <div style={{ position: "relative", height: "100%" }}>60 {saveMessage && (61 <div62 style={{63 position: "absolute",64 top: 8,65 right: 8,66 background: "#10b981",67 color: "white",68 padding: "8px 16px",69 borderRadius: 6,70 fontSize: 14,71 fontWeight: 500,72 zIndex: 1000,73 boxShadow: "0 2px 8px rgba(0,0,0,0.15)"74 }}75 >76 {saveMessage}77 </div>78 )}79 <SimpleTable80 columnResizing81 columns={headers}82 rows={columnResizingData}83 height={height}84 theme={theme}85 getRowId={({ row }) => row.id}86 onColumnWidthChange={handleColumnWidthChange}87 />88 </div>89 );90};9192export default ColumnResizingDemo;
Angularcolumn-resizing-demo.component.ts
Copy
1import { NgIf } from "@angular/common";2import { Component, Input, OnInit } from "@angular/core";3import { SimpleTableImports } from "@simple-table/angular";4import type { AngularColumnDef, GetRowIdParams, Theme } from "@simple-table/angular";5import { columnResizingHeaders, columnResizingData, COLUMN_RESIZING_STORAGE_KEY } from "./column-resizing.demo-data";6import "@simple-table/angular/styles.css";7import type { OceanStaff } from "./column-resizing.demo-data";89@Component({10 selector: "column-resizing-demo",11 standalone: true,12 imports: [SimpleTableImports, NgIf],13 template: `14 <div style="position: relative; height: 100%">15 <div16 *ngIf="saveMessage"17 style="position: absolute; top: 8px; right: 8px; background: #10b981; color: white; padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; z-index: 1000; box-shadow: 0 2px 8px rgba(0,0,0,0.15);"18 >19 {{ saveMessage }}20 </div>21 <simple-table22 [getRowId]="getRowId"23 [columnResizing]="true"24 [rows]="rows"25 [columns]="headers"26 [height]="height"27 [theme]="theme"28 (columnWidthChange)="handleColumnWidthChange($event)"29 ></simple-table>30 </div>31 `,32})33export class ColumnResizingDemoComponent implements OnInit {34 @Input() height: string | number = "400px";35 @Input() theme?: Theme;3637 readonly rows: OceanStaff[] = columnResizingData;38 headers: AngularColumnDef<OceanStaff>[] = [...columnResizingHeaders];39 saveMessage = "";4041 handleColumnWidthChange = (updatedHeaders: AngularColumnDef<OceanStaff>[]) => {42 try {43 const widthMap: Record<string, unknown> = {};44 for (const h of updatedHeaders) widthMap[h.accessor] = h.width;45 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));46 this.headers = updatedHeaders;47 this.saveMessage = "Column widths saved!";48 setTimeout(() => { this.saveMessage = ""; }, 2000);49 } catch {50 this.saveMessage = "Failed to save widths";51 setTimeout(() => { this.saveMessage = ""; }, 2000);52 }53 };5455 ngOnInit() {56 try {57 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);58 if (saved) {59 const widthMap = JSON.parse(saved) as Record<string, number | string | undefined>;60 this.headers = columnResizingHeaders.map((h) => ({61 ...h,62 width: widthMap[h.accessor] ?? h.width,63 }));64 }65 } catch { /* ignore */ }66 }6768 getRowId = ({ row }: GetRowIdParams<OceanStaff>) => row.id;69}707172// column-resizing.demo-data.ts73// Self-contained demo table setup for this example.74import type { AngularColumnDef } from "@simple-table/angular";7576export interface OceanStaff {77 id: number;78 name: string;79 age: number;80 role: string;81 department: string;82 startDate: string;83}8485export const COLUMN_RESIZING_STORAGE_KEY = "columnResizingDemo_widths";8687export const columnResizingHeaders: AngularColumnDef<OceanStaff>[] = [88 { accessor: "id", label: "ID", width: 60, type: "number" },89 { accessor: "name", label: "First Name", width: "1fr", minWidth: 100, type: "string" },90 { accessor: "age", label: "Age", width: "1fr", minWidth: 50, type: "string" },91 { accessor: "role", label: "Role", width: 150, align: "right", type: "number" },92 { accessor: "department", label: "Department", width: "1fr", minWidth: 100, type: "string" },93 { accessor: "startDate", label: "Start Date", width: 150, type: "date" },94];9596export const columnResizingData = [97 { id: 1, name: "Dr. Marina Silva", age: 38, role: "Marine Biologist", department: "Research", startDate: "2019-03-15" },98 { id: 2, name: "Captain Alex Torres", age: 45, role: "Research Vessel Captain", department: "Operations", startDate: "2017-08-20" },99 { id: 3, name: "Dr. Coral Chen", age: 34, role: "Oceanographer", department: "Research", startDate: "2020-01-12" },100 { id: 4, name: "Finn O'Brien", age: 27, role: "Research Assistant", department: "Research", startDate: "2022-06-08" },101 { id: 5, name: "Reef Nakamura", age: 31, role: "Dive Safety Officer", department: "Safety", startDate: "2021-02-14" },102 { id: 6, name: "Tide Rodriguez", age: 29, role: "Equipment Specialist", department: "Technical", startDate: "2021-09-03" },103 { id: 7, name: "Dr. Ocean Williams", age: 42, role: "Research Director", department: "Leadership", startDate: "2016-05-10" },104 { id: 8, name: "Wave Petrov", age: 26, role: "Data Analyst", department: "Analysis", startDate: "2022-11-22" },105 { id: 9, name: "Pearl Kim", age: 33, role: "Laboratory Manager", department: "Laboratory", startDate: "2020-07-18" },106 { id: 10, name: "Current Hassan", age: 28, role: "Field Coordinator", department: "Operations", startDate: "2021-12-05" },107 { id: 11, name: "Abyss Thompson", age: 30, role: "ROV Operator", department: "Technical", startDate: "2021-04-20" },108 { id: 12, name: "Dr. Depth Martinez", age: 39, role: "Senior Researcher", department: "Research", startDate: "2018-10-14" },109];110111export const columnResizingConfig = {112 headers: columnResizingHeaders,113 rows: columnResizingData,114};115
Vue SFC
Copy
1<template>2 <div style="position: relative; height: 100%">3 <div4 v-if="saveMessage"5 style="position: absolute; top: 8px; right: 8px; background: #10b981; color: white; padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; z-index: 1000; box-shadow: 0 2px 8px rgba(0,0,0,0.15);"6 >7 {{ saveMessage }}8 </div>9 <SimpleTable10 :column-resizing="true"11 :columns="headers"12 :rows="columnResizingData"13 :get-row-id="getRowId"14 :height="height"15 :theme="theme"16 @column-width-change="handleColumnWidthChange"17 />18 </div>19</template>2021<script setup lang="ts">22import { ref, onMounted } from "vue";23import { SimpleTable } from "@simple-table/vue";24import type { Theme, VueColumnDef, GetRowIdParams } from "@simple-table/vue";25import {26 columnResizingHeaders,27 columnResizingData,28 COLUMN_RESIZING_STORAGE_KEY,29} from "./column-resizing.demo-data";30import type { OceanStaff } from "./column-resizing.demo-data";31import "@simple-table/vue/styles.css";3233withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {34 height: "400px",35});3637const headers = ref<VueColumnDef<OceanStaff>[]>([...columnResizingHeaders]);38const saveMessage = ref("");3940const getRowId = ({ row }: GetRowIdParams<OceanStaff>) => row.id;4142onMounted(() => {43 try {44 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);45 if (saved) {46 const widthMap = JSON.parse(saved);47 headers.value = columnResizingHeaders.map((h) => ({48 ...h,49 width: widthMap[h.accessor] ?? h.width,50 }));51 }52 } catch { /* ignore */ }53});5455function handleColumnWidthChange(updatedHeaders: VueColumnDef<OceanStaff>[]) {56 try {57 const widthMap = updatedHeaders.reduce(58 (acc: Record<string, unknown>, h) => { acc[h.accessor] = h.width; return acc; },59 {},60 );61 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));62 headers.value = updatedHeaders;63 saveMessage.value = "Column widths saved!";64 setTimeout(() => { saveMessage.value = ""; }, 2000);65 } catch {66 saveMessage.value = "Failed to save widths";67 setTimeout(() => { saveMessage.value = ""; }, 2000);68 }69}70</script>
Svelte
Copy
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, SvelteColumnDef, GetRowIdParams } from "@simple-table/svelte";4 import { columnResizingHeaders, columnResizingData, COLUMN_RESIZING_STORAGE_KEY } from "./column-resizing.demo-data";5 import type { OceanStaff } from "./column-resizing.demo-data";6 import "@simple-table/svelte/styles.css";78 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();910 let headers: SvelteColumnDef<OceanStaff>[] = $state([...columnResizingHeaders]);11 let saveMessage = $state("");1213 const getRowId = ({ row }: GetRowIdParams<OceanStaff>) => row.id;1415 $effect(() => {16 try {17 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);18 if (saved) {19 const widthMap = JSON.parse(saved);20 headers = columnResizingHeaders.map((h) => ({ ...h, width: widthMap[h.accessor] ?? h.width }));21 }22 } catch { /* ignore */ }23 });2425 function handleColumnWidthChange(updatedHeaders: SvelteColumnDef<OceanStaff>[]) {26 try {27 const widthMap: Record<string, unknown> = {};28 for (const h of updatedHeaders) widthMap[h.accessor] = h.width;29 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));30 headers = updatedHeaders;31 saveMessage = "Column widths saved!";32 setTimeout(() => { saveMessage = ""; }, 2000);33 } catch {34 saveMessage = "Failed to save widths";35 setTimeout(() => { saveMessage = ""; }, 2000);36 }37 }38</script>3940<div style="position: relative; height: 100%">41 {#if saveMessage}42 <div style="position: absolute; top: 8px; right: 8px; background: #10b981; color: white; padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; z-index: 1000; box-shadow: 0 2px 8px rgba(0,0,0,0.15);">43 {saveMessage}44 </div>45 {/if}46 <SimpleTable47 columnResizing={true}48 columns={headers}49 rows={columnResizingData}50 getRowId={getRowId}51 {height}52 {theme}53 onColumnWidthChange={handleColumnWidthChange}54 />55</div>
Solid TSX
Copy
1import { createSignal, onMount, onCleanup } from "solid-js";2import { SimpleTable } from "@simple-table/solid";3import type { Theme, SolidColumnDef } from "@simple-table/solid";4import {5 columnResizingHeaders,6 columnResizingData,7 COLUMN_RESIZING_STORAGE_KEY,8 type OceanStaff,9} from "./column-resizing.demo-data";10import "@simple-table/solid/styles.css";1112export default function ColumnResizingDemo(props: { height?: string | number; theme?: Theme }) {13 const [headers, setHeaders] = createSignal([...columnResizingHeaders]);14 const [saveMessage, setSaveMessage] = createSignal("");1516 let messageTimer: ReturnType<typeof setTimeout> | undefined;1718 const clearMessageTimer = () => {19 if (messageTimer !== undefined) {20 clearTimeout(messageTimer);21 messageTimer = undefined;22 }23 };2425 onMount(() => {26 try {27 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);28 if (saved) {29 const widthMap = JSON.parse(saved) as Record<string, number | string | undefined>;30 setHeaders(31 columnResizingHeaders.map((h) => ({ ...h, width: widthMap[h.accessor] ?? h.width })),32 );33 }34 } catch {35 /* ignore */36 }37 });3839 onCleanup(() => {40 clearMessageTimer();41 });4243 const handleColumnWidthChange = (updatedHeaders: SolidColumnDef<OceanStaff>[]) => {44 try {45 const widthMap: Record<string, unknown> = {};46 for (const h of updatedHeaders) widthMap[h.accessor] = h.width;47 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));48 setHeaders(updatedHeaders);49 setSaveMessage("Column widths saved!");50 clearMessageTimer();51 messageTimer = setTimeout(() => setSaveMessage(""), 2000);52 } catch {53 setSaveMessage("Failed to save widths");54 clearMessageTimer();55 messageTimer = setTimeout(() => setSaveMessage(""), 2000);56 }57 };5859 return (60 <div style={{ position: "relative", height: "100%" }}>61 {saveMessage() && (62 <div63 style={{64 position: "absolute",65 top: "8px",66 right: "8px",67 background: "#10b981",68 color: "white",69 padding: "8px 16px",70 "border-radius": "6px",71 "font-size": "14px",72 "font-weight": "500",73 "z-index": 1000,74 "box-shadow": "0 2px 8px rgba(0,0,0,0.15)",75 }}76 >77 {saveMessage()}78 </div>79 )}80 <SimpleTable81 columnResizing82 columns={headers()}83 getRowId={({ row }) => row.id}84 rows={columnResizingData}85 height={props.height ?? "400px"}86 theme={props.theme}87 onColumnWidthChange={handleColumnWidthChange}88 />89 </div>90 );91}
TypeScriptColumnResizingDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { OceanStaff } from "./column-resizing.demo-data";3import type { Theme, ColumnDef, GetRowIdParams } from "simple-table-core";4import { columnResizingHeaders, columnResizingData, COLUMN_RESIZING_STORAGE_KEY } from "./column-resizing.demo-data";5import "simple-table-core/styles.css";678const getRowId = ({ row }: GetRowIdParams<OceanStaff>) => row.id;9export function renderColumnResizingDemo(10 container: HTMLElement,11 options?: { height?: string | number; theme?: Theme },12): SimpleTableVanilla<OceanStaff> {13 const wrapper = document.createElement("div");14 wrapper.style.position = "relative";15 wrapper.style.height = "100%";1617 const toast = document.createElement("div");18 Object.assign(toast.style, {19 position: "absolute", top: "8px", right: "8px", background: "#10b981", color: "white",20 padding: "8px 16px", borderRadius: "6px", fontSize: "14px", fontWeight: "500",21 zIndex: "1000", boxShadow: "0 2px 8px rgba(0,0,0,0.15)", display: "none",22 });23 wrapper.appendChild(toast);2425 const tableContainer = document.createElement("div");26 wrapper.appendChild(tableContainer);27 container.appendChild(wrapper);2829 let headers: ColumnDef<OceanStaff>[] = [...columnResizingHeaders];30 try {31 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);32 if (saved) {33 const widthMap = JSON.parse(saved);34 headers = columnResizingHeaders.map((h) => ({ ...h, width: widthMap[h.accessor] ?? h.width }));35 }36 } catch { /* ignore */ }3738 const table = new SimpleTableVanilla(tableContainer, {39 getRowId,40 columns: headers,41 rows: columnResizingData,42 height: options?.height ?? "400px",43 theme: options?.theme,44 columnResizing: true,45 onColumnWidthChange: (updatedHeaders: ColumnDef<OceanStaff>[]) => {46 try {47 const widthMap: Record<string, unknown> = {};48 for (const h of updatedHeaders) widthMap[h.accessor] = h.width;49 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));50 toast.textContent = "Column widths saved!";51 toast.style.display = "block";52 setTimeout(() => { toast.style.display = "none"; }, 2000);53 } catch {54 toast.textContent = "Failed to save widths";55 toast.style.display = "block";56 setTimeout(() => { toast.style.display = "none"; }, 2000);57 }58 },59 });6061 return table;62}636465// column-resizing.demo-data.ts66// Self-contained demo table setup for this example.67import type { ColumnDef } from "simple-table-core";6869export interface OceanStaff {70 id: number;71 name: string;72 age: number;73 role: string;74 department: string;75 startDate: string;76}7778export const COLUMN_RESIZING_STORAGE_KEY = "columnResizingDemo_widths";7980export const columnResizingHeaders: ColumnDef<OceanStaff>[] = [81 { accessor: "id", label: "ID", width: 60, type: "number" },82 { accessor: "name", label: "First Name", width: "1fr", minWidth: 100, type: "string" },83 { accessor: "age", label: "Age", width: "1fr", minWidth: 50, type: "string" },84 { accessor: "role", label: "Role", width: 150, align: "right", type: "number" },85 { accessor: "department", label: "Department", width: "1fr", minWidth: 100, type: "string" },86 { accessor: "startDate", label: "Start Date", width: 150, type: "date" },87];8889export const columnResizingData = [90 { id: 1, name: "Dr. Marina Silva", age: 38, role: "Marine Biologist", department: "Research", startDate: "2019-03-15" },91 { id: 2, name: "Captain Alex Torres", age: 45, role: "Research Vessel Captain", department: "Operations", startDate: "2017-08-20" },92 { id: 3, name: "Dr. Coral Chen", age: 34, role: "Oceanographer", department: "Research", startDate: "2020-01-12" },93 { id: 4, name: "Finn O'Brien", age: 27, role: "Research Assistant", department: "Research", startDate: "2022-06-08" },94 { id: 5, name: "Reef Nakamura", age: 31, role: "Dive Safety Officer", department: "Safety", startDate: "2021-02-14" },95 { id: 6, name: "Tide Rodriguez", age: 29, role: "Equipment Specialist", department: "Technical", startDate: "2021-09-03" },96 { id: 7, name: "Dr. Ocean Williams", age: 42, role: "Research Director", department: "Leadership", startDate: "2016-05-10" },97 { id: 8, name: "Wave Petrov", age: 26, role: "Data Analyst", department: "Analysis", startDate: "2022-11-22" },98 { id: 9, name: "Pearl Kim", age: 33, role: "Laboratory Manager", department: "Laboratory", startDate: "2020-07-18" },99 { id: 10, name: "Current Hassan", age: 28, role: "Field Coordinator", department: "Operations", startDate: "2021-12-05" },100 { id: 11, name: "Abyss Thompson", age: 30, role: "ROV Operator", department: "Technical", startDate: "2021-04-20" },101 { id: 12, name: "Dr. Depth Martinez", age: 39, role: "Senior Researcher", department: "Research", startDate: "2018-10-14" },102];103104export const columnResizingConfig = {105 headers: columnResizingHeaders,106 rows: columnResizingData,107};108
Props
Column Resizing Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
columnResizingboolean | Optional | Enables dragging header dividers to resize columns. Double-click a handle to auto-fit that column to its content. | |
onColumnWidthChange | Optional | Fires after resize or double-click auto-size with the updated column defs. Use it to persist widths. |