Documentation
Column Resizing
Column resizing allows users to adjust column widths to better view and interact with data according to their preferences.
Interactive Demo
Try resizing columns by dragging the dividers or double-clicking them to auto-fit. Your column widths are automatically saved to localStorage and will persist when you refresh the page!
1import { useState, useEffect } from "react";2import { SimpleTable } from "@simple-table/react";3import type { Theme, ReactHeaderObject } from "@simple-table/react";4import {5 columnResizingHeaders,6 columnResizingData,7 COLUMN_RESIZING_STORAGE_KEY,8} from "./column-resizing.demo-data";9import "@simple-table/react/styles.css";1011const ColumnResizingDemo = ({12 height = "400px",13 theme,14}: {15 height?: string | number;16 theme?: Theme;17}) => {18 const [headers, setHeaders] = useState(() => columnResizingHeaders);19 const [saveMessage, setSaveMessage] = useState("");2021 useEffect(() => {22 try {23 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);24 if (saved) {25 const widthMap = JSON.parse(saved);26 setHeaders(27 columnResizingHeaders.map((h) => ({28 ...h,29 width: (widthMap as Record<string, number | string | undefined>)[h.accessor] ?? h.width,30 })),31 );32 }33 } catch {34 // ignore35 }36 }, []);3738 const handleColumnWidthChange = (updatedHeaders: ReactHeaderObject[]) => {39 try {40 const widthMap = updatedHeaders.reduce(41 (acc, h) => {42 acc[h.accessor] = h.width;43 return acc;44 },45 {} as Record<string, number | string>,46 );47 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));48 setHeaders(updatedHeaders);49 setSaveMessage("Column widths saved!");50 setTimeout(() => setSaveMessage(""), 2000);51 } catch {52 setSaveMessage("Failed to save widths");53 setTimeout(() => setSaveMessage(""), 2000);54 }55 };5657 return (58 <div style={{ position: "relative", height: "100%" }}>59 {saveMessage && (60 <div61 style={{62 position: "absolute",63 top: 8,64 right: 8,65 background: "#10b981",66 color: "white",67 padding: "8px 16px",68 borderRadius: 6,69 fontSize: 14,70 fontWeight: 500,71 zIndex: 1000,72 boxShadow: "0 2px 8px rgba(0,0,0,0.15)",73 }}74 >75 {saveMessage}76 </div>77 )}78 <SimpleTable79 columnResizing80 defaultHeaders={headers}81 rows={columnResizingData}82 height={height}83 theme={theme}84 onColumnWidthChange={handleColumnWidthChange}85 />86 </div>87 );88};8990export default ColumnResizingDemo;
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 :default-headers="headers"12 :rows="columnResizingData"13 :height="height"14 :theme="theme"15 @column-width-change="handleColumnWidthChange"16 />17 </div>18</template>1920<script setup lang="ts">21import { ref, onMounted } from "vue";22import { SimpleTable } from "@simple-table/vue";23import type { Theme, VueHeaderObject } from "@simple-table/vue";24import { columnResizingHeaders, columnResizingData, COLUMN_RESIZING_STORAGE_KEY } from "./column-resizing.demo-data";25import "@simple-table/vue/styles.css";2627withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {28 height: "400px",29});3031const headers = ref<VueHeaderObject[]>([...columnResizingHeaders]);32const saveMessage = ref("");3334onMounted(() => {35 try {36 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);37 if (saved) {38 const widthMap = JSON.parse(saved);39 headers.value = columnResizingHeaders.map((h) => ({40 ...h,41 width: widthMap[h.accessor] ?? h.width,42 }));43 }44 } catch { /* ignore */ }45});4647function handleColumnWidthChange(updatedHeaders: VueHeaderObject[]) {48 try {49 const widthMap = updatedHeaders.reduce(50 (acc: Record<string, unknown>, h) => { acc[h.accessor] = h.width; return acc; },51 {},52 );53 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));54 headers.value = updatedHeaders;55 saveMessage.value = "Column widths saved!";56 setTimeout(() => { saveMessage.value = ""; }, 2000);57 } catch {58 saveMessage.value = "Failed to save widths";59 setTimeout(() => { saveMessage.value = ""; }, 2000);60 }61}62</script>
1import { NgIf } from "@angular/common";2import { Component, Input, OnInit } from "@angular/core";3import { SimpleTableComponent } from "@simple-table/angular";4import type { AngularHeaderObject, Row, Theme } from "@simple-table/angular";5import { columnResizingHeaders, columnResizingData, COLUMN_RESIZING_STORAGE_KEY } from "./column-resizing.demo-data";6import "@simple-table/angular/styles.css";78@Component({9 selector: "column-resizing-demo",10 standalone: true,11 imports: [SimpleTableComponent, NgIf],12 template: `13 <div style="position: relative; height: 100%">14 <div15 *ngIf="saveMessage"16 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);"17 >18 {{ saveMessage }}19 </div>20 <simple-table21 [columnResizing]="true"22 [rows]="rows"23 [defaultHeaders]="headers"24 [height]="height"25 [theme]="theme"26 [onColumnWidthChange]="handleColumnWidthChange"27 ></simple-table>28 </div>29 `,30})31export class ColumnResizingDemoComponent implements OnInit {32 @Input() height: string | number = "400px";33 @Input() theme?: Theme;3435 readonly rows: Row[] = columnResizingData;36 headers: AngularHeaderObject[] = [...columnResizingHeaders];37 saveMessage = "";3839 handleColumnWidthChange = (updatedHeaders: AngularHeaderObject[]) => {40 try {41 const widthMap: Record<string, unknown> = {};42 for (const h of updatedHeaders) widthMap[h.accessor] = h.width;43 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));44 this.headers = updatedHeaders;45 this.saveMessage = "Column widths saved!";46 setTimeout(() => { this.saveMessage = ""; }, 2000);47 } catch {48 this.saveMessage = "Failed to save widths";49 setTimeout(() => { this.saveMessage = ""; }, 2000);50 }51 };5253 ngOnInit() {54 try {55 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);56 if (saved) {57 const widthMap = JSON.parse(saved) as Record<string, number | string | undefined>;58 this.headers = columnResizingHeaders.map((h) => ({59 ...h,60 width: widthMap[h.accessor] ?? h.width,61 }));62 }63 } catch { /* ignore */ }64 }65}666768// column-resizing.demo-data.ts69// Self-contained demo table setup for this example.70import type { AngularHeaderObject } from "@simple-table/angular";717273export const COLUMN_RESIZING_STORAGE_KEY = "columnResizingDemo_widths";7475export const columnResizingHeaders: AngularHeaderObject[] = [76 { accessor: "id", label: "ID", width: 60, type: "number" },77 { accessor: "name", label: "First Name", width: "1fr", minWidth: 100, type: "string" },78 { accessor: "age", label: "Age", width: "1fr", minWidth: 50, type: "string" },79 { accessor: "role", label: "Role", width: 150, align: "right", type: "number" },80 { accessor: "department", label: "Department", width: "1fr", minWidth: 100, type: "string" },81 { accessor: "startDate", label: "Start Date", width: 150, type: "date" },82];8384export const columnResizingData = [85 { id: 1, name: "Dr. Marina Silva", age: 38, role: "Marine Biologist", department: "Research", startDate: "2019-03-15" },86 { id: 2, name: "Captain Alex Torres", age: 45, role: "Research Vessel Captain", department: "Operations", startDate: "2017-08-20" },87 { id: 3, name: "Dr. Coral Chen", age: 34, role: "Oceanographer", department: "Research", startDate: "2020-01-12" },88 { id: 4, name: "Finn O'Brien", age: 27, role: "Research Assistant", department: "Research", startDate: "2022-06-08" },89 { id: 5, name: "Reef Nakamura", age: 31, role: "Dive Safety Officer", department: "Safety", startDate: "2021-02-14" },90 { id: 6, name: "Tide Rodriguez", age: 29, role: "Equipment Specialist", department: "Technical", startDate: "2021-09-03" },91 { id: 7, name: "Dr. Ocean Williams", age: 42, role: "Research Director", department: "Leadership", startDate: "2016-05-10" },92 { id: 8, name: "Wave Petrov", age: 26, role: "Data Analyst", department: "Analysis", startDate: "2022-11-22" },93 { id: 9, name: "Pearl Kim", age: 33, role: "Laboratory Manager", department: "Laboratory", startDate: "2020-07-18" },94 { id: 10, name: "Current Hassan", age: 28, role: "Field Coordinator", department: "Operations", startDate: "2021-12-05" },95 { id: 11, name: "Abyss Thompson", age: 30, role: "ROV Operator", department: "Technical", startDate: "2021-04-20" },96 { id: 12, name: "Dr. Depth Martinez", age: 39, role: "Senior Researcher", department: "Research", startDate: "2018-10-14" },97];9899export const columnResizingConfig = {100 headers: columnResizingHeaders,101 rows: columnResizingData,102} as const;103
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, SvelteHeaderObject } from "@simple-table/svelte";4 import { columnResizingHeaders, columnResizingData, COLUMN_RESIZING_STORAGE_KEY } from "./column-resizing.demo-data";5 import "@simple-table/svelte/styles.css";67 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();89 let headers: SvelteHeaderObject[] = $state([...columnResizingHeaders]);10 let saveMessage = $state("");1112 $effect(() => {13 try {14 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);15 if (saved) {16 const widthMap = JSON.parse(saved);17 headers = columnResizingHeaders.map((h) => ({ ...h, width: widthMap[h.accessor] ?? h.width }));18 }19 } catch { /* ignore */ }20 });2122 function handleColumnWidthChange(updatedHeaders: SvelteHeaderObject[]) {23 try {24 const widthMap: Record<string, unknown> = {};25 for (const h of updatedHeaders) widthMap[h.accessor] = h.width;26 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));27 headers = updatedHeaders;28 saveMessage = "Column widths saved!";29 setTimeout(() => { saveMessage = ""; }, 2000);30 } catch {31 saveMessage = "Failed to save widths";32 setTimeout(() => { saveMessage = ""; }, 2000);33 }34 }35</script>3637<div style="position: relative; height: 100%">38 {#if saveMessage}39 <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);">40 {saveMessage}41 </div>42 {/if}43 <SimpleTable44 columnResizing={true}45 defaultHeaders={headers}46 rows={columnResizingData}47 {height}48 {theme}49 onColumnWidthChange={handleColumnWidthChange}50 />51</div>
1import { createSignal, onMount, onCleanup } from "solid-js";2import { SimpleTable } from "@simple-table/solid";3import type { Theme, SolidHeaderObject } from "@simple-table/solid";4import { columnResizingHeaders, columnResizingData, COLUMN_RESIZING_STORAGE_KEY } from "./column-resizing.demo-data";5import "@simple-table/solid/styles.css";67export default function ColumnResizingDemo(props: { height?: string | number; theme?: Theme }) {8 const [headers, setHeaders] = createSignal([...columnResizingHeaders]);9 const [saveMessage, setSaveMessage] = createSignal("");1011 let messageTimer: ReturnType<typeof setTimeout> | undefined;1213 const clearMessageTimer = () => {14 if (messageTimer !== undefined) {15 clearTimeout(messageTimer);16 messageTimer = undefined;17 }18 };1920 onMount(() => {21 try {22 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);23 if (saved) {24 const widthMap = JSON.parse(saved) as Record<string, number | string | undefined>;25 setHeaders(26 columnResizingHeaders.map((h) => ({ ...h, width: widthMap[h.accessor] ?? h.width })),27 );28 }29 } catch {30 /* ignore */31 }32 });3334 onCleanup(() => {35 clearMessageTimer();36 });3738 const handleColumnWidthChange = (updatedHeaders: SolidHeaderObject[]) => {39 try {40 const widthMap: Record<string, unknown> = {};41 for (const h of updatedHeaders) widthMap[h.accessor] = h.width;42 localStorage.setItem(COLUMN_RESIZING_STORAGE_KEY, JSON.stringify(widthMap));43 setHeaders(updatedHeaders);44 setSaveMessage("Column widths saved!");45 clearMessageTimer();46 messageTimer = setTimeout(() => setSaveMessage(""), 2000);47 } catch {48 setSaveMessage("Failed to save widths");49 clearMessageTimer();50 messageTimer = setTimeout(() => setSaveMessage(""), 2000);51 }52 };5354 return (55 <div style={{ position: "relative", height: "100%" }}>56 {saveMessage() && (57 <div58 style={{59 position: "absolute",60 top: "8px",61 right: "8px",62 background: "#10b981",63 color: "white",64 padding: "8px 16px",65 "border-radius": "6px",66 "font-size": "14px",67 "font-weight": "500",68 "z-index": 1000,69 "box-shadow": "0 2px 8px rgba(0,0,0,0.15)",70 }}71 >72 {saveMessage()}73 </div>74 )}75 <SimpleTable76 columnResizing77 defaultHeaders={headers()}78 rows={columnResizingData}79 height={props.height ?? "400px"}80 theme={props.theme}81 onColumnWidthChange={handleColumnWidthChange}82 />83 </div>84 );85}
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, HeaderObject } from "simple-table-core";3import { columnResizingHeaders, columnResizingData, COLUMN_RESIZING_STORAGE_KEY } from "./column-resizing.demo-data";4import "simple-table-core/styles.css";56export function renderColumnResizingDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme },9): SimpleTableVanilla {10 const wrapper = document.createElement("div");11 wrapper.style.position = "relative";12 wrapper.style.height = "100%";1314 const toast = document.createElement("div");15 Object.assign(toast.style, {16 position: "absolute", top: "8px", right: "8px", background: "#10b981", color: "white",17 padding: "8px 16px", borderRadius: "6px", fontSize: "14px", fontWeight: "500",18 zIndex: "1000", boxShadow: "0 2px 8px rgba(0,0,0,0.15)", display: "none",19 });20 wrapper.appendChild(toast);2122 const tableContainer = document.createElement("div");23 wrapper.appendChild(tableContainer);24 container.appendChild(wrapper);2526 let headers: HeaderObject[] = [...columnResizingHeaders];27 try {28 const saved = localStorage.getItem(COLUMN_RESIZING_STORAGE_KEY);29 if (saved) {30 const widthMap = JSON.parse(saved);31 headers = columnResizingHeaders.map((h) => ({ ...h, width: widthMap[h.accessor] ?? h.width }));32 }33 } catch { /* ignore */ }3435 const table = new SimpleTableVanilla(tableContainer, {36 defaultHeaders: headers,37 rows: columnResizingData,38 height: options?.height ?? "400px",39 theme: options?.theme,40 columnResizing: true,41 onColumnWidthChange: (updatedHeaders: HeaderObject[]) => {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 toast.textContent = "Column widths saved!";47 toast.style.display = "block";48 setTimeout(() => { toast.style.display = "none"; }, 2000);49 } catch {50 toast.textContent = "Failed to save widths";51 toast.style.display = "block";52 setTimeout(() => { toast.style.display = "none"; }, 2000);53 }54 },55 });5657 return table;58}596061// column-resizing.demo-data.ts62// Self-contained demo table setup for this example.63import type { HeaderObject } from "simple-table-core";646566export const COLUMN_RESIZING_STORAGE_KEY = "columnResizingDemo_widths";6768export const columnResizingHeaders: HeaderObject[] = [69 { accessor: "id", label: "ID", width: 60, type: "number" },70 { accessor: "name", label: "First Name", width: "1fr", minWidth: 100, type: "string" },71 { accessor: "age", label: "Age", width: "1fr", minWidth: 50, type: "string" },72 { accessor: "role", label: "Role", width: 150, align: "right", type: "number" },73 { accessor: "department", label: "Department", width: "1fr", minWidth: 100, type: "string" },74 { accessor: "startDate", label: "Start Date", width: 150, type: "date" },75];7677export const columnResizingData = [78 { id: 1, name: "Dr. Marina Silva", age: 38, role: "Marine Biologist", department: "Research", startDate: "2019-03-15" },79 { id: 2, name: "Captain Alex Torres", age: 45, role: "Research Vessel Captain", department: "Operations", startDate: "2017-08-20" },80 { id: 3, name: "Dr. Coral Chen", age: 34, role: "Oceanographer", department: "Research", startDate: "2020-01-12" },81 { id: 4, name: "Finn O'Brien", age: 27, role: "Research Assistant", department: "Research", startDate: "2022-06-08" },82 { id: 5, name: "Reef Nakamura", age: 31, role: "Dive Safety Officer", department: "Safety", startDate: "2021-02-14" },83 { id: 6, name: "Tide Rodriguez", age: 29, role: "Equipment Specialist", department: "Technical", startDate: "2021-09-03" },84 { id: 7, name: "Dr. Ocean Williams", age: 42, role: "Research Director", department: "Leadership", startDate: "2016-05-10" },85 { id: 8, name: "Wave Petrov", age: 26, role: "Data Analyst", department: "Analysis", startDate: "2022-11-22" },86 { id: 9, name: "Pearl Kim", age: 33, role: "Laboratory Manager", department: "Laboratory", startDate: "2020-07-18" },87 { id: 10, name: "Current Hassan", age: 28, role: "Field Coordinator", department: "Operations", startDate: "2021-12-05" },88 { id: 11, name: "Abyss Thompson", age: 30, role: "ROV Operator", department: "Technical", startDate: "2021-04-20" },89 { id: 12, name: "Dr. Depth Martinez", age: 39, role: "Senior Researcher", department: "Research", startDate: "2018-10-14" },90];9192export const columnResizingConfig = {93 headers: columnResizingHeaders,94 rows: columnResizingData,95} as const;96
Basic Implementation
Column resizing is enabled by adding the columnResizing prop to the SimpleTable component. Users can resize columns by dragging the column dividers in the header row.
Column Resizing Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
columnResizingboolean | Optional | Enables column resizing functionality. When true, users can resize columns by dragging the column dividers in the header row. Users can also double-click resize handles to automatically fit columns to their content width. | |
onColumnWidthChange | Optional | Callback triggered when column widths change through user resizing or double-click auto-sizing. With `@simple-table/react`, receives `ReactHeaderObject[]`. Angular, Svelte, and Solid adapters use `AngularHeaderObject[]`, `SvelteHeaderObject[]`, and `SolidHeaderObject[]` respectively. |
Double-Click Auto-Size
When column resizing is enabled, users can double-click on any resize handle to automatically fit that column to its content width. This provides a quick way to optimize column sizes without manual dragging.
Tip
The auto-size feature calculates the optimal width based on the column's content, including both the header text and cell values. This is especially useful for columns with varying content lengths.
Persisting Column Widths
Use the onColumnWidthChange callback to save user column width preferences. This callback is triggered whenever columns are resized (either by dragging or double-clicking) and receives the updated headers array with new width values.
Note
When autoExpandColumns is enabled, the resize handle is removed from the last column since all columns scale proportionally to fill the container width.