Documentation
Column Visibility
Hide columns by default, or let users show and hide them from the column editor.
Hide a column by default
Set hide: true on a column def. Users can still show it again when the column editor is enabled.
TypeScript
Copy
{accessor: "internalId",label: "Internal ID",width: 100,hide: true,}
TypeScript
Copy
{accessor: "internalId",label: "Internal ID",width: 100,hide: true,}
TypeScript
Copy
{accessor: "internalId",label: "Internal ID",width: 100,hide: true,}
TypeScript
Copy
{accessor: "internalId",label: "Internal ID",width: 100,hide: true,}
TypeScript
Copy
{accessor: "internalId",label: "Internal ID",width: 100,hide: true,}
TypeScript
Copy
{accessor: "internalId",label: "Internal ID",width: 100,hide: true,}
Enable the column editor
Set enableColumnEditor so users can search, toggle, and reorder columns from the Columns panel.
React TSX
Copy
<SimpleTable columns={columns} rows={rows} height="400px" enableColumnEditor={true} />
Angular
Copy
<simple-table[columns]="columns"[rows]="rows"height="400px"[enableColumnEditor]="true"></simple-table>
Vue SFC
Copy
<SimpleTable:columns="columns":rows="rows"height="400px":enable-column-editor="true"/>
Svelte
Copy
<SimpleTable {columns} {rows} height="400px" enableColumnEditor={true} />
Solid TSX
Copy
<SimpleTable columns={columns} rows={rows} height="400px" enableColumnEditor={true} />
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows,height: "400px",enableColumnEditor: true,});
Open the editor on load
Pair with enableColumnEditorInitOpen to open the panel when the table mounts.
React TSX
Copy
<SimpleTable columns={columns} rows={rows} height="400px" enableColumnEditor={true} enableColumnEditorInitOpen={true} />
Angular
Copy
<simple-table[columns]="columns"[rows]="rows"height="400px"[enableColumnEditor]="true"[enableColumnEditorInitOpen]="true"></simple-table>
Vue SFC
Copy
<SimpleTable:columns="columns":rows="rows"height="400px":enable-column-editor="true":enable-column-editor-init-open="true"/>
Svelte
Copy
<SimpleTable {columns} {rows} height="400px" enableColumnEditor={true} enableColumnEditorInitOpen={true} />
Solid TSX
Copy
<SimpleTable columns={columns} rows={rows} height="400px" enableColumnEditor={true} enableColumnEditorInitOpen={true} />
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows,height: "400px",enableColumnEditor: true,enableColumnEditorInitOpen: true,});
Exclude from table and editor
Use excludeFromRender for data you still want in CSV export but not in the grid or visibility menu (e.g. ids).
TypeScript
Copy
{accessor: "id",label: "ID",width: 80,excludeFromRender: true,}
TypeScript
Copy
{accessor: "id",label: "ID",width: 80,excludeFromRender: true,}
TypeScript
Copy
{accessor: "id",label: "ID",width: 80,excludeFromRender: true,}
TypeScript
Copy
{accessor: "id",label: "ID",width: 80,excludeFromRender: true,}
TypeScript
Copy
{accessor: "id",label: "ID",width: 80,excludeFromRender: true,}
TypeScript
Copy
{accessor: "id",label: "ID",width: 80,excludeFromRender: true,}
Example
Open the Columns panel to toggle visibility and reorder columns.
React TSX
Copy
1import {2 SimpleTable,3 type ColumnEditorRowRendererProps,4 type ColumnVisibilityState,5 type Theme6} from "@simple-table/react";7import { useMemo, useCallback } from "react";8import {9 columnVisibilityConfig,10 getColumnVisibilityDemoHeaders,11 loadColumnVisibilityDemoSaved,12 saveColumnVisibilityDemoState,13 type VisibilityEmployee14} from "./column-visibility.demo-data";15import "@simple-table/react/styles.css";1617const ColumnVisibilityDemo = ({18 height = "400px",19 theme20}: {21 height?: string | number;22 theme?: Theme;23}) => {24 const headers = useMemo(25 () => getColumnVisibilityDemoHeaders(loadColumnVisibilityDemoSaved()),26 [],27 );2829 const onColumnVisibilityChange = useCallback((state: ColumnVisibilityState) => {30 saveColumnVisibilityDemoState(state);31 }, []);3233 return (34 <SimpleTable35 columns={headers}36 rows={columnVisibilityConfig.rows}37 enableColumnEditor38 enableColumnEditorInitOpen39 height={height}40 theme={theme}41 getRowId={({ row }) => row.id}42 onColumnVisibilityChange={onColumnVisibilityChange}43 columnEditorConfig={{44 ...columnVisibilityConfig.tableProps.columnEditorConfig,45 rowRenderer: ({ components }: ColumnEditorRowRendererProps) => (46 <div47 style={{48 width: "100%",49 display: "flex",50 alignItems: "center",51 justifyContent: "space-between",52 gap: "8px",53 paddingRight: "8px"54 }}55 >56 <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>57 {components?.expandIcon}58 {components?.checkbox}59 {components?.labelContent}60 </div>61 <div>{components?.dragIcon}</div>62 </div>63 )64 }}65 />66 );67};6869export default ColumnVisibilityDemo;
Angularcolumn-visibility-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import { SimpleTableImports } from "@simple-table/angular";3import type { AngularColumnDef, ColumnVisibilityState, GetRowIdParams, Theme } from "@simple-table/angular";4import { columnVisibilityConfig, getColumnVisibilityDemoHeaders, loadColumnVisibilityDemoSaved, saveColumnVisibilityDemoState } from "./column-visibility.demo-data";5import { MarketingColumnEditorRowComponent } from "./marketing-column-editor-row.component";6import "@simple-table/angular/styles.css";7import type { VisibilityEmployee } from "./column-visibility.demo-data";89@Component({10 selector: "column-visibility-demo",11 standalone: true,12 imports: [SimpleTableImports],13 template: `14 <simple-table15 [getRowId]="getRowId"16 [rows]="rows"17 [columns]="headers"18 [height]="height"19 [theme]="theme"20 [enableColumnEditor]="tableProps.enableColumnEditor"21 [enableColumnEditorInitOpen]="tableProps.enableColumnEditorInitOpen"22 [columnEditorConfig]="columnEditorConfig"23 (columnVisibilityChange)="onVisibilityChange($event)"24 ></simple-table>25 `,26})27export class ColumnVisibilityDemoComponent {28 @Input() height: string | number = "400px";29 @Input() theme?: Theme;3031 readonly rows: VisibilityEmployee[] = columnVisibilityConfig.rows;32 readonly headers: AngularColumnDef<VisibilityEmployee>[] = getColumnVisibilityDemoHeaders(33 loadColumnVisibilityDemoSaved(),34 );35 readonly tableProps = columnVisibilityConfig.tableProps;36 readonly columnEditorConfig = {37 ...columnVisibilityConfig.tableProps.columnEditorConfig,38 rowRenderer: MarketingColumnEditorRowComponent,39 };4041 readonly onVisibilityChange = (state: ColumnVisibilityState) => {42 saveColumnVisibilityDemoState(state);43 };4445 getRowId = ({ row }: GetRowIdParams<VisibilityEmployee>) => row.id;46}474849// column-visibility.demo-data.ts50// Self-contained demo table setup for this example (aligned with simple-table-marketing column visibility demo).51import type { ColumnVisibilityState, AngularColumnDef, ValueFormatterProps } from "@simple-table/angular";5253export interface VisibilityEmployee {54 id: number;55 firstName: string;56 lastName: string;57 email: string;58 phone: string;59 role: string;60 department: string;61 location: string;62 startDate: string;63}6465export const COLUMN_VISIBILITY_DEMO_STORAGE_KEY = "columnVisibilityDemo";6667export function loadColumnVisibilityDemoSaved(): ColumnVisibilityState {68 if (typeof window === "undefined") return {};69 try {70 const saved = localStorage.getItem(COLUMN_VISIBILITY_DEMO_STORAGE_KEY);71 return saved ? JSON.parse(saved) : {};72 } catch {73 return {};74 }75}7677export function saveColumnVisibilityDemoState(state: ColumnVisibilityState): void {78 if (typeof window === "undefined") return;79 try {80 localStorage.setItem(COLUMN_VISIBILITY_DEMO_STORAGE_KEY, JSON.stringify(state));81 } catch {82 /* ignore */83 }84}8586export const columnVisibilityData: VisibilityEmployee[] = [87 { id: 1, firstName: "Alice", lastName: "Johnson", email: "alice@example.com", phone: "555-0101", role: "Engineer", department: "Engineering", location: "NYC", startDate: "2021-03-15" },88 { id: 2, firstName: "Bob", lastName: "Martinez", email: "bob@example.com", phone: "555-0102", role: "Designer", department: "Design", location: "LA", startDate: "2022-07-22" },89 { id: 3, firstName: "Clara", lastName: "Chen", email: "clara@example.com", phone: "555-0103", role: "PM", department: "Product", location: "SF", startDate: "2020-01-10" },90 { id: 4, firstName: "David", lastName: "Kim", email: "david@example.com", phone: "555-0104", role: "Engineer", department: "Engineering", location: "CHI", startDate: "2019-11-05" },91 { id: 5, firstName: "Elena", lastName: "Rossi", email: "elena@example.com", phone: "555-0105", role: "Analyst", department: "Analytics", location: "BOS", startDate: "2023-02-14" },92 { id: 6, firstName: "Frank", lastName: "Müller", email: "frank@example.com", phone: "555-0106", role: "Engineer", department: "Engineering", location: "SEA", startDate: "2021-09-30" },93 { id: 7, firstName: "Grace", lastName: "Park", email: "grace@example.com", phone: "555-0107", role: "Designer", department: "Design", location: "AUS", startDate: "2022-04-18" },94 { id: 8, firstName: "Henry", lastName: "Patel", email: "henry@example.com", phone: "555-0108", role: "Lead", department: "Engineering", location: "DEN", startDate: "2018-05-20" },95];9697export const columnVisibilityHeaders: AngularColumnDef<VisibilityEmployee, any>[] = [98 { accessor: "id", label: "ID", width: 60, type: "number" },99 { accessor: "firstName", label: "First Name", width: 120, type: "string" },100 { accessor: "lastName", label: "Last Name", width: 120, type: "string" },101 { accessor: "email", label: "Email", width: 200, type: "string" },102 { accessor: "phone", label: "Phone", width: 120, type: "string", hide: true },103 { accessor: "role", label: "Role", width: 130, type: "string" },104 { accessor: "department", label: "Department", width: 140, type: "string" },105 { accessor: "location", label: "Location", width: 100, type: "string", hide: true },106 {107 accessor: "startDate",108 label: "Start Date",109 width: 130,110 type: "date",111 valueFormatter: ({ value }: ValueFormatterProps<VisibilityEmployee, string>) => new Date(value).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }),112 },113];114115/** Applies saved visibility + marketing-style defaults (email hidden when unset). */116export function getColumnVisibilityDemoHeaders(117 savedVisibility: ColumnVisibilityState = loadColumnVisibilityDemoSaved(),118): AngularColumnDef<VisibilityEmployee>[] {119 return columnVisibilityHeaders.map((header) => ({120 ...header,121 hide:122 savedVisibility[header.accessor] === false ||123 (savedVisibility[header.accessor] === undefined && header.accessor === "email") ||124 (savedVisibility[header.accessor] === undefined && header.hide === true),125 }));126}127128export const columnVisibilityConfig = {129 headers: columnVisibilityHeaders,130 rows: columnVisibilityData,131 tableProps: {132 enableColumnEditor: true,133 enableColumnEditorInitOpen: true,134 columnEditorConfig: {135 text: "Manage Columns",136 searchEnabled: true,137 searchPlaceholder: "Search columns…",138 },139 },140};141142143// marketing-column-editor-row.component.ts144import {145 AfterViewInit,146 Component,147 ElementRef,148 Input,149 OnChanges,150 ViewChild,151} from "@angular/core";152import type { ColumnEditorRowRendererProps } from "@simple-table/angular";153154function attach(slot: unknown, host: HTMLElement | undefined): void {155 if (!host) return;156 host.replaceChildren();157 if (slot == null) return;158 if (typeof slot === "string") {159 host.textContent = slot;160 } else if (slot instanceof Node) {161 host.appendChild(slot);162 }163}164165/** Marketing-style column-editor row; slots are framework-provided nodes or strings. */166@Component({167 selector: "st-examples-marketing-column-editor-row",168 standalone: true,169 template: `170 <div171 style="width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;padding-right:8px;"172 >173 <div style="display:flex;align-items:center;gap:8px;">174 <span #expandHost></span>175 <span #checkboxHost></span>176 <span #labelHost></span>177 </div>178 <span #dragHost></span>179 </div>180 `,181})182export class MarketingColumnEditorRowComponent implements AfterViewInit, OnChanges {183 @Input({ required: true }) accessor!: ColumnEditorRowRendererProps["accessor"];184 @Input({ required: true }) header!: ColumnEditorRowRendererProps["header"];185 @Input({ required: true }) components!: ColumnEditorRowRendererProps["components"];186 @Input() panelSection?: ColumnEditorRowRendererProps["panelSection"];187 @Input() essential?: ColumnEditorRowRendererProps["essential"];188 @Input() canToggleVisibility?: ColumnEditorRowRendererProps["canToggleVisibility"];189 @Input() allowColumnPinning?: ColumnEditorRowRendererProps["allowColumnPinning"];190 @Input() pinControl?: ColumnEditorRowRendererProps["pinControl"];191192 @ViewChild("expandHost") expandRef?: ElementRef<HTMLSpanElement>;193 @ViewChild("checkboxHost") checkboxRef?: ElementRef<HTMLSpanElement>;194 @ViewChild("labelHost") labelRef?: ElementRef<HTMLSpanElement>;195 @ViewChild("dragHost") dragRef?: ElementRef<HTMLSpanElement>;196197 ngAfterViewInit(): void {198 this.syncSlots();199 }200201 ngOnChanges(): void {202 this.syncSlots();203 }204205 private syncSlots(): void {206 const c = this.components;207 attach(c.expandIcon, this.expandRef?.nativeElement);208 attach(c.checkbox, this.checkboxRef?.nativeElement);209 attach(c.labelContent, this.labelRef?.nativeElement);210 attach(c.dragIcon, this.dragRef?.nativeElement);211 }212}213
Vue SFCColumnVisibilityDemo.vue
Copy
1<script setup lang="ts">2import { SimpleTable } from "@simple-table/vue";3import type { ColumnVisibilityState, Theme, GetRowIdParams } from "@simple-table/vue";4import MarketingColumnEditorRow from "./MarketingColumnEditorRow.vue";5import {6 columnVisibilityConfig,7 getColumnVisibilityDemoHeaders,8 loadColumnVisibilityDemoSaved,9 saveColumnVisibilityDemoState,10} from "./column-visibility.demo-data";11import type { VisibilityEmployee } from "./column-visibility.demo-data";12import "@simple-table/vue/styles.css";1314const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {15 height: "400px",16});1718const columns = getColumnVisibilityDemoHeaders(loadColumnVisibilityDemoSaved());1920const columnEditorConfig = {21 ...columnVisibilityConfig.tableProps.columnEditorConfig,22 rowRenderer: MarketingColumnEditorRow,23};2425const getRowId = ({ row }: GetRowIdParams<VisibilityEmployee>) => row.id;2627function onColumnVisibilityChange(state: ColumnVisibilityState) {28 saveColumnVisibilityDemoState(state);29}30</script>3132<template>33 <SimpleTable34 :columns="columns"35 :rows="columnVisibilityConfig.rows"36 :get-row-id="getRowId"37 :enable-column-editor="columnVisibilityConfig.tableProps.enableColumnEditor"38 :enable-column-editor-init-open="columnVisibilityConfig.tableProps.enableColumnEditorInitOpen"39 :column-editor-config="columnEditorConfig"40 :height="props.height"41 :theme="props.theme"42 :on-column-visibility-change="onColumnVisibilityChange"43 />44</template>454647// MarketingColumnEditorRow.vue48<script lang="ts">49import { defineComponent, h } from "vue";50import type { ColumnEditorRowRendererProps } from "@simple-table/vue";5152export default defineComponent({53 name: "MarketingColumnEditorRow",54 props: {55 header: { type: Object, required: true },56 components: { type: Object, required: true },57 },58 setup(props: ColumnEditorRowRendererProps) {59 return () =>60 h(61 "div",62 {63 style: {64 width: "100%",65 display: "flex",66 alignItems: "center",67 justifyContent: "space-between",68 gap: "8px",69 paddingRight: "8px",70 },71 },72 [73 h(74 "div",75 {76 style: {77 display: "flex",78 alignItems: "center",79 gap: "8px",80 },81 },82 [83 props.components.expandIcon,84 props.components.checkbox,85 props.components.labelContent,86 ].filter((v) => v != null && v !== false),87 ),88 h(89 "div",90 { style: { display: "flex", alignItems: "center" } },91 [props.components.dragIcon].filter((v) => v != null && v !== false),92 ),93 ],94 );95 },96});97</script>98
SvelteColumnVisibilityDemo.svelte
Copy
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, GetRowIdParams } from "@simple-table/svelte";4 import MarketingColumnEditorRow from "./MarketingColumnEditorRow.svelte";5 import { columnVisibilityConfig } from "./column-visibility.demo-data";6 import type { VisibilityEmployee } from "./column-visibility.demo-data";7 import "@simple-table/svelte/styles.css";89 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();1011 const columns = columnVisibilityConfig.headers;12 const getRowId = ({ row }: GetRowIdParams<VisibilityEmployee>) => row.id;1314 const columnEditorConfig = {15 ...columnVisibilityConfig.tableProps.columnEditorConfig,16 rowRenderer: MarketingColumnEditorRow,17 };18</script>1920<SimpleTable21 {columns}22 rows={columnVisibilityConfig.rows}23 {getRowId}24 enableColumnEditor={columnVisibilityConfig.tableProps.enableColumnEditor}25 enableColumnEditorInitOpen={columnVisibilityConfig.tableProps.enableColumnEditorInitOpen}26 columnEditorConfig={columnEditorConfig}27 {height}28 {theme}29/>303132// MarketingColumnEditorRow.svelte33<script lang="ts">34 import type { ColumnEditorRowRendererProps } from "@simple-table/svelte";3536 let { components }: ColumnEditorRowRendererProps = $props();3738 let expandHost: HTMLSpanElement | undefined = $state();39 let checkboxHost: HTMLSpanElement | undefined = $state();40 let labelHost: HTMLSpanElement | undefined = $state();41 let dragHost: HTMLSpanElement | undefined = $state();4243 function attach(slot: unknown, host: HTMLElement | undefined): void {44 if (!host) return;45 host.replaceChildren();46 if (slot == null) return;47 if (typeof slot === "string") {48 host.textContent = slot;49 } else if (slot instanceof Node) {50 host.appendChild(slot);51 }52 }5354 $effect(() => {55 attach(components.expandIcon, expandHost);56 });57 $effect(() => {58 attach(components.checkbox, checkboxHost);59 });60 $effect(() => {61 attach(components.labelContent, labelHost);62 });63 $effect(() => {64 attach(components.dragIcon, dragHost);65 });66</script>6768<div69 style="width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;padding-right:8px;"70>71 <div style="display:flex;align-items:center;gap:8px;">72 <span bind:this={expandHost}></span>73 <span bind:this={checkboxHost}></span>74 <span bind:this={labelHost}></span>75 </div>76 <span bind:this={dragHost}></span>77</div>78
Solid TSXColumnVisibilityDemo.tsx
Copy
1import {2 SimpleTable,3 type ColumnVisibilityState,4 type Theme,5} from "@simple-table/solid";6import {7 columnVisibilityConfig,8 getColumnVisibilityDemoHeaders,9 loadColumnVisibilityDemoSaved,10 saveColumnVisibilityDemoState,11} from "./column-visibility.demo-data";12import MarketingColumnEditorRow from "./MarketingColumnEditorRow";13import "@simple-table/solid/styles.css";1415export default function ColumnVisibilityDemo(props: { height?: string | number; theme?: Theme }) {16 const headers = () => getColumnVisibilityDemoHeaders(loadColumnVisibilityDemoSaved());1718 const onColumnVisibilityChange = (state: ColumnVisibilityState) => {19 saveColumnVisibilityDemoState(state);20 };2122 return (23 <SimpleTable24 columns={headers()}25 getRowId={({ row }) => row.id}26 rows={columnVisibilityConfig.rows}27 enableColumnEditor28 enableColumnEditorInitOpen29 height={props.height ?? "400px"}30 theme={props.theme}31 onColumnVisibilityChange={onColumnVisibilityChange}32 columnEditorConfig={{33 ...columnVisibilityConfig.tableProps.columnEditorConfig,34 rowRenderer: MarketingColumnEditorRow,35 }}36 />37 );38}394041// MarketingColumnEditorRow.tsx42import type { ColumnEditorRowRendererProps } from "@simple-table/solid";43import { createEffect } from "solid-js";4445function attach(slot: unknown, host: HTMLElement | undefined): void {46 if (!host) return;47 host.replaceChildren();48 if (slot == null) return;49 if (typeof slot === "string") {50 host.textContent = slot;51 } else if (slot instanceof Node) {52 host.appendChild(slot);53 }54}5556function SlotHost(props: { slot: () => unknown }) {57 let host: HTMLSpanElement | undefined;58 createEffect(() => {59 attach(props.slot(), host);60 });61 return (62 <span63 ref={(el) => {64 host = el;65 attach(props.slot(), el);66 }}67 />68 );69}7071export default function MarketingColumnEditorRow(props: ColumnEditorRowRendererProps) {72 return (73 <div74 style={{75 width: "100%",76 display: "flex",77 "align-items": "center",78 "justify-content": "space-between",79 gap: "8px",80 "padding-right": "8px",81 }}82 >83 <div style={{ display: "flex", "align-items": "center", gap: "8px" }}>84 <SlotHost slot={() => props.components.expandIcon} />85 <SlotHost slot={() => props.components.checkbox} />86 <SlotHost slot={() => props.components.labelContent} />87 </div>88 <SlotHost slot={() => props.components.dragIcon} />89 </div>90 );91}92
TypeScriptColumnVisibilityDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, ColumnEditorRowRendererProps, ColumnVisibilityState, GetRowIdParams } from "simple-table-core";3import {4 columnVisibilityConfig,5 getColumnVisibilityDemoHeaders,6 loadColumnVisibilityDemoSaved,7 saveColumnVisibilityDemoState,8} from "./column-visibility.demo-data";9import type { VisibilityEmployee } from "./column-visibility.demo-data";10import "simple-table-core/styles.css";1112function attachSlot(slot: unknown, host: HTMLElement): void {13 host.replaceChildren();14 if (slot == null) return;15 if (typeof slot === "string") {16 host.textContent = slot;17 } else if (slot instanceof Node) {18 host.appendChild(slot);19 }20}2122function buildMarketingStyleColumnEditorRowRenderer({23 components,24}: ColumnEditorRowRendererProps): HTMLElement {25 const row = document.createElement("div");26 Object.assign(row.style, {27 width: "100%",28 display: "flex",29 alignItems: "center",30 justifyContent: "space-between",31 gap: "8px",32 paddingRight: "8px",33 });3435 const left = document.createElement("div");36 Object.assign(left.style, { display: "flex", alignItems: "center", gap: "8px" });3738 for (const slot of [components.expandIcon, components.checkbox, components.labelContent]) {39 const host = document.createElement("span");40 attachSlot(slot, host);41 left.appendChild(host);42 }4344 row.appendChild(left);4546 const dragHost = document.createElement("span");47 attachSlot(components.dragIcon, dragHost);48 row.appendChild(dragHost);4950 return row;51}5253const getRowId = ({ row }: GetRowIdParams<VisibilityEmployee>) => row.id;5455export function renderColumnVisibilityDemo(56 container: HTMLElement,57 options?: { height?: string | number; theme?: Theme },58): SimpleTableVanilla<VisibilityEmployee> {59 return new SimpleTableVanilla(container, {60 getRowId,61 columns: getColumnVisibilityDemoHeaders(loadColumnVisibilityDemoSaved()),62 rows: columnVisibilityConfig.rows,63 height: options?.height ?? "400px",64 theme: options?.theme,65 enableColumnEditor: columnVisibilityConfig.tableProps.enableColumnEditor,66 enableColumnEditorInitOpen: columnVisibilityConfig.tableProps.enableColumnEditorInitOpen,67 onColumnVisibilityChange: (state: ColumnVisibilityState) => {68 saveColumnVisibilityDemoState(state);69 },70 columnEditorConfig: {71 ...columnVisibilityConfig.tableProps.columnEditorConfig,72 rowRenderer: buildMarketingStyleColumnEditorRowRenderer,73 },74 });75}767778// column-visibility.demo-data.ts79// Self-contained demo table setup for this example (aligned with simple-table-marketing column visibility demo).80import type { ColumnVisibilityState, ColumnDef } from "simple-table-core";8182export interface VisibilityEmployee {83 id: number;84 firstName: string;85 lastName: string;86 email: string;87 phone: string;88 role: string;89 department: string;90 location: string;91 startDate: string;92}9394export const COLUMN_VISIBILITY_DEMO_STORAGE_KEY = "columnVisibilityDemo";9596export function loadColumnVisibilityDemoSaved(): ColumnVisibilityState {97 if (typeof window === "undefined") return {};98 try {99 const saved = localStorage.getItem(COLUMN_VISIBILITY_DEMO_STORAGE_KEY);100 return saved ? JSON.parse(saved) : {};101 } catch {102 return {};103 }104}105106export function saveColumnVisibilityDemoState(state: ColumnVisibilityState): void {107 if (typeof window === "undefined") return;108 try {109 localStorage.setItem(COLUMN_VISIBILITY_DEMO_STORAGE_KEY, JSON.stringify(state));110 } catch {111 /* ignore */112 }113}114115export const columnVisibilityData: VisibilityEmployee[] = [116 { id: 1, firstName: "Alice", lastName: "Johnson", email: "alice@example.com", phone: "555-0101", role: "Engineer", department: "Engineering", location: "NYC", startDate: "2021-03-15" },117 { id: 2, firstName: "Bob", lastName: "Martinez", email: "bob@example.com", phone: "555-0102", role: "Designer", department: "Design", location: "LA", startDate: "2022-07-22" },118 { id: 3, firstName: "Clara", lastName: "Chen", email: "clara@example.com", phone: "555-0103", role: "PM", department: "Product", location: "SF", startDate: "2020-01-10" },119 { id: 4, firstName: "David", lastName: "Kim", email: "david@example.com", phone: "555-0104", role: "Engineer", department: "Engineering", location: "CHI", startDate: "2019-11-05" },120 { id: 5, firstName: "Elena", lastName: "Rossi", email: "elena@example.com", phone: "555-0105", role: "Analyst", department: "Analytics", location: "BOS", startDate: "2023-02-14" },121 { id: 6, firstName: "Frank", lastName: "Müller", email: "frank@example.com", phone: "555-0106", role: "Engineer", department: "Engineering", location: "SEA", startDate: "2021-09-30" },122 { id: 7, firstName: "Grace", lastName: "Park", email: "grace@example.com", phone: "555-0107", role: "Designer", department: "Design", location: "AUS", startDate: "2022-04-18" },123 { id: 8, firstName: "Henry", lastName: "Patel", email: "henry@example.com", phone: "555-0108", role: "Lead", department: "Engineering", location: "DEN", startDate: "2018-05-20" },124];125126export const columnVisibilityHeaders: ColumnDef<VisibilityEmployee>[] = [127 { accessor: "id", label: "ID", width: 60, type: "number" },128 { accessor: "firstName", label: "First Name", width: 120, type: "string" },129 { accessor: "lastName", label: "Last Name", width: 120, type: "string" },130 { accessor: "email", label: "Email", width: 200, type: "string" },131 { accessor: "phone", label: "Phone", width: 120, type: "string", hide: true },132 { accessor: "role", label: "Role", width: 130, type: "string" },133 { accessor: "department", label: "Department", width: 140, type: "string" },134 { accessor: "location", label: "Location", width: 100, type: "string", hide: true },135 {136 accessor: "startDate",137 label: "Start Date",138 width: 130,139 type: "date",140 valueFormatter: ({ value }) => new Date(String(value)).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }),141 },142];143144/** Applies saved visibility + marketing-style defaults (email hidden when unset). */145export function getColumnVisibilityDemoHeaders(146 savedVisibility: ColumnVisibilityState = loadColumnVisibilityDemoSaved(),147): ColumnDef<VisibilityEmployee>[] {148 return columnVisibilityHeaders.map((header) => ({149 ...header,150 hide:151 savedVisibility[header.accessor] === false ||152 (savedVisibility[header.accessor] === undefined && header.accessor === "email") ||153 (savedVisibility[header.accessor] === undefined && header.hide === true),154 }));155}156157export const columnVisibilityConfig = {158 headers: columnVisibilityHeaders,159 rows: columnVisibilityData,160 tableProps: {161 enableColumnEditor: true,162 enableColumnEditorInitOpen: true,163 columnEditorConfig: {164 text: "Manage Columns",165 searchEnabled: true,166 searchPlaceholder: "Search columns…",167 },168 },169};170
Custom column editor layout
Pass customRenderer on columnEditorConfig to replace the default popout. See ColumnEditorConfig.
React TSX
Copy
<SimpleTableenableColumnEditorcolumns={columns}rows={rows}columnEditorConfig={{customRenderer: ({ searchSection, listSection, resetColumns }) => (<>{searchSection}{listSection}<button type="button" onClick={resetColumns}>Reset</button></>),}}/>
Angular
Copy
columnEditorConfig = {customRenderer: ({ searchSection, listSection, resetColumns }) => {// return a custom layout using searchSection + listSection},};<simple-table[enableColumnEditor]="true"[columnEditorConfig]="columnEditorConfig"[columns]="columns"[rows]="rows"></simple-table>
Vue SFC
Copy
<script setup>const columnEditorConfig = {customRenderer: ({ searchSection, listSection, resetColumns }) => [searchSection,listSection,// optional: your own reset control calling resetColumns()],};</script><template><SimpleTable:enable-column-editor="true":column-editor-config="columnEditorConfig":columns="columns":rows="rows"/></template>
Svelte
Copy
<script>const columnEditorConfig = {customRenderer: ({ searchSection, listSection, resetColumns }) => {// return a custom layout using searchSection + listSection},};</script><SimpleTableenableColumnEditor={true}columnEditorConfig={columnEditorConfig}{columns}{rows}/>
Solid TSX
Copy
<SimpleTableenableColumnEditorcolumns={columns}rows={rows}columnEditorConfig={{customRenderer: ({ searchSection, listSection, resetColumns }) => (<>{searchSection}{listSection}<button type="button" onClick={resetColumns}>Reset</button></>),}}/>
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows,enableColumnEditor: true,columnEditorConfig: {customRenderer: ({ searchSection, listSection, resetColumns }) => {const root = document.createElement("div");if (searchSection) root.appendChild(searchSection);if (listSection) root.appendChild(listSection);// optional: add a reset button that calls resetColumns()return root;},},});
React TSX
Copy
1import { SimpleTable } from "@simple-table/react";2import type {3 Theme,4 ReactColumnEditorConfig,5 ColumnEditorRowRendererProps6} from "@simple-table/react";7import {8 columnEditorCustomRendererConfig,9 type ColumnEditorCustomRendererEmployee10} from "./column-editor-custom-renderer.demo-data";11import "@simple-table/react/styles.css";1213const CustomRowRenderer = ({ header, components }: ColumnEditorRowRendererProps) => (14 <div15 style={{16 display: "flex",17 alignItems: "center",18 gap: 8,19 padding: "6px 8px",20 borderRadius: 6,21 background: "#f8fafc",22 marginBottom: 423 }}24 >25 {components.checkbox != null && <span>{components.checkbox}</span>}26 <span style={{ flex: 1, fontSize: 13, fontWeight: 500 }}>{header.label}</span>27 {components.dragIcon != null && (28 <span style={{ cursor: "grab", opacity: 0.5 }}>{components.dragIcon}</span>29 )}30 </div>31);3233const columnEditorConfig: ReactColumnEditorConfig = {34 text: "Manage Columns",35 searchEnabled: true,36 searchPlaceholder: "Search columns…",37 rowRenderer: CustomRowRenderer38};3940const ColumnEditorCustomRendererDemo = ({41 height = "400px",42 theme43}: {44 height?: string | number;45 theme?: Theme;46}) => {47 return (48 <SimpleTable49 columns={columnEditorCustomRendererConfig.headers}50 getRowId={({ row }) => row.id}51 rows={columnEditorCustomRendererConfig.rows}52 enableColumnEditor53 columnEditorConfig={columnEditorConfig}54 height={height}55 theme={theme}56 />57 );58};5960export default ColumnEditorCustomRendererDemo;
Angularcolumn-editor-custom-renderer-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import { SimpleTableComponent } from "@simple-table/angular";3import type { AngularColumnDef, AngularColumnEditorConfig, GetRowIdParams, Theme } from "@simple-table/angular";4import {5 columnEditorCustomRendererConfig,6 COLUMN_EDITOR_SEARCH_PLACEHOLDER,7 COLUMN_EDITOR_TEXT,8} from "./column-editor-custom-renderer.demo-data";9import { ColumnEditorCustomRowComponent } from "./column-editor-custom-row.component";10import "@simple-table/angular/styles.css";11import type { ColumnEditorCustomRendererEmployee } from "./column-editor-custom-renderer.demo-data";1213@Component({14 selector: "column-editor-custom-renderer-demo",15 standalone: true,16 imports: [SimpleTableComponent],17 template: `18 <simple-table19 [getRowId]="getRowId"20 [rows]="rows"21 [columns]="headers"22 [height]="height"23 [theme]="theme"24 [enableColumnEditor]="true"25 [columnEditorConfig]="editorConfig"26 ></simple-table>27 `,28})29export class ColumnEditorCustomRendererDemoComponent {30 @Input() height: string | number = "400px";31 @Input() theme?: Theme;3233 readonly rows: ColumnEditorCustomRendererEmployee[] = columnEditorCustomRendererConfig.rows;34 readonly headers: AngularColumnDef<ColumnEditorCustomRendererEmployee>[] = columnEditorCustomRendererConfig.headers;35 readonly editorConfig: AngularColumnEditorConfig = {36 text: COLUMN_EDITOR_TEXT,37 searchEnabled: true,38 searchPlaceholder: COLUMN_EDITOR_SEARCH_PLACEHOLDER,39 rowRenderer: ColumnEditorCustomRowComponent,40 };4142 getRowId = ({ row }: GetRowIdParams<ColumnEditorCustomRendererEmployee>) => row.id;43}444546// column-editor-custom-renderer.demo-data.ts47// Self-contained demo table setup for this example.48import type { AngularColumnDef, ValueFormatterProps } from "@simple-table/angular";4950export interface ColumnEditorCustomRendererEmployee {51 id: number;52 name: string;53 email: string;54 role: string;55 salary: number;56 department: string;57 status: string;58}5960export const columnEditorCustomRendererData: ColumnEditorCustomRendererEmployee[] = [61 {62 id: 1,63 name: "Alice Johnson",64 email: "alice@example.com",65 role: "Engineer",66 salary: 125000,67 department: "Engineering",68 status: "active",69 },70 {71 id: 2,72 name: "Bob Martinez",73 email: "bob@example.com",74 role: "Designer",75 salary: 98000,76 department: "Design",77 status: "active",78 },79 {80 id: 3,81 name: "Clara Chen",82 email: "clara@example.com",83 role: "PM",84 salary: 115000,85 department: "Product",86 status: "inactive",87 },88 {89 id: 4,90 name: "David Kim",91 email: "david@example.com",92 role: "Engineer",93 salary: 132000,94 department: "Engineering",95 status: "active",96 },97 {98 id: 5,99 name: "Elena Rossi",100 email: "elena@example.com",101 role: "Analyst",102 salary: 89000,103 department: "Analytics",104 status: "active",105 },106 {107 id: 6,108 name: "Frank Müller",109 email: "frank@example.com",110 role: "Engineer",111 salary: 118000,112 department: "Engineering",113 status: "inactive",114 },115 {116 id: 7,117 name: "Grace Park",118 email: "grace@example.com",119 role: "Designer",120 salary: 105000,121 department: "Design",122 status: "active",123 },124 {125 id: 8,126 name: "Henry Patel",127 email: "henry@example.com",128 role: "Lead",129 salary: 145000,130 department: "Engineering",131 status: "active",132 },133];134135export const columnEditorCustomRendererHeaders: AngularColumnDef<ColumnEditorCustomRendererEmployee, any>[] = [136 { accessor: "id", label: "ID", width: 60, type: "number" },137 { accessor: "name", label: "Name", width: 170, type: "string", sortable: true },138 { accessor: "email", label: "Email", width: 200, type: "string" },139 { accessor: "role", label: "Role", width: 130, type: "string", sortable: true },140 {141 accessor: "salary",142 label: "Salary",143 width: 130,144 type: "number",145 sortable: true,146 valueFormatter: ({ value }: ValueFormatterProps<ColumnEditorCustomRendererEmployee, number>) => `$${value.toLocaleString()}`,147 },148 { accessor: "department", label: "Department", width: 140, type: "string", sortable: true },149 { accessor: "status", label: "Status", width: 100, type: "string" },150];151152export const columnEditorCustomRendererConfig = {153 headers: columnEditorCustomRendererHeaders,154 rows: columnEditorCustomRendererData,155 tableProps: {156 enableColumnEditor: true,157 },158};159160export const COLUMN_EDITOR_TEXT = "Manage Columns";161export const COLUMN_EDITOR_SEARCH_PLACEHOLDER = "Search columns…";162163164// column-editor-custom-row.component.ts165import {166 AfterViewInit,167 Component,168 ElementRef,169 Input,170 OnChanges,171 ViewChild,172} from "@angular/core";173import type { ColumnEditorRowRendererProps } from "@simple-table/angular";174175function attach(slot: unknown, host: HTMLElement | undefined): void {176 if (!host) return;177 host.replaceChildren();178 if (slot == null) return;179 if (typeof slot === "string") {180 host.textContent = slot;181 } else if (slot instanceof Node) {182 host.appendChild(slot);183 }184}185186@Component({187 standalone: true,188 selector: "demo-column-editor-custom-row",189 template: `190 <div191 style="display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;background:#f8fafc;margin-bottom:4px;"192 >193 @if (components.checkbox) {194 <span #checkboxHost></span>195 }196 <span style="flex:1;font-size:13px;font-weight:500;">{{ header.label }}</span>197 @if (components.dragIcon) {198 <span #dragHost style="cursor:grab;opacity:0.5;"></span>199 }200 </div>201 `,202})203export class ColumnEditorCustomRowComponent implements AfterViewInit, OnChanges {204 @Input({ required: true }) header!: ColumnEditorRowRendererProps["header"];205 @Input({ required: true }) components!: ColumnEditorRowRendererProps["components"];206 @Input() accessor?: ColumnEditorRowRendererProps["accessor"];207 @Input() panelSection?: ColumnEditorRowRendererProps["panelSection"];208 @Input() essential?: ColumnEditorRowRendererProps["essential"];209 @Input() canToggleVisibility?: ColumnEditorRowRendererProps["canToggleVisibility"];210 @Input() allowColumnPinning?: ColumnEditorRowRendererProps["allowColumnPinning"];211 @Input() pinControl?: ColumnEditorRowRendererProps["pinControl"];212213 @ViewChild("checkboxHost") checkboxRef?: ElementRef<HTMLSpanElement>;214 @ViewChild("dragHost") dragRef?: ElementRef<HTMLSpanElement>;215216 ngAfterViewInit(): void {217 this.syncSlots();218 }219220 ngOnChanges(): void {221 this.syncSlots();222 }223224 private syncSlots(): void {225 attach(this.components.checkbox, this.checkboxRef?.nativeElement);226 attach(this.components.dragIcon, this.dragRef?.nativeElement);227 }228}229
Vue SFCColumnEditorCustomRendererDemo.vue
Copy
1<script setup lang="ts">2import { SimpleTable } from "@simple-table/vue";3import type { Theme, GetRowIdParams } from "@simple-table/vue";4import {5 columnEditorCustomRendererConfig,6 COLUMN_EDITOR_TEXT,7 COLUMN_EDITOR_SEARCH_PLACEHOLDER,8} from "./column-editor-custom-renderer.demo-data";9import type { ColumnEditorCustomRendererEmployee } from "./column-editor-custom-renderer.demo-data";10import ColumnEditorCustomRow from "./ColumnEditorCustomRow.vue";11import "@simple-table/vue/styles.css";1213const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {14 height: "400px",15});1617const getRowId = ({ row }: GetRowIdParams<ColumnEditorCustomRendererEmployee>) => row.id;1819const editorConfig = {20 text: COLUMN_EDITOR_TEXT,21 searchEnabled: true,22 searchPlaceholder: COLUMN_EDITOR_SEARCH_PLACEHOLDER,23 rowRenderer: ColumnEditorCustomRow,24};25</script>2627<template>28 <SimpleTable29 :columns="columnEditorCustomRendererConfig.headers"30 :rows="columnEditorCustomRendererConfig.rows"31 :get-row-id="getRowId"32 :enable-column-editor="true"33 :column-editor-config="editorConfig"34 :height="props.height"35 :theme="props.theme"36 />37</template>383940// ColumnEditorCustomRow.vue41<script lang="ts">42import { defineComponent, h } from "vue";43import type { ColumnEditorRowRendererProps } from "@simple-table/vue";4445export default defineComponent({46 name: "ColumnEditorCustomRow",47 props: {48 header: { type: Object, required: true },49 components: { type: Object, required: true },50 },51 setup(props: ColumnEditorRowRendererProps) {52 return () =>53 h(54 "div",55 {56 style: {57 display: "flex",58 alignItems: "center",59 gap: "8px",60 padding: "6px 8px",61 borderRadius: "6px",62 background: "#f8fafc",63 marginBottom: "4px",64 },65 },66 [67 props.components.checkbox != null68 ? h("span", null, [props.components.checkbox as object])69 : null,70 h(71 "span",72 { style: { flex: "1", fontSize: "13px", fontWeight: "500" } },73 props.header.label,74 ),75 props.components.dragIcon != null76 ? h(77 "span",78 { style: { cursor: "grab", opacity: "0.5" } },79 [props.components.dragIcon as object],80 )81 : null,82 ].filter(Boolean),83 );84 },85});86</script>87
SvelteColumnEditorCustomRendererDemo.svelte
Copy
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, GetRowIdParams } from "@simple-table/svelte";4 import {5 columnEditorCustomRendererConfig,6 COLUMN_EDITOR_TEXT,7 COLUMN_EDITOR_SEARCH_PLACEHOLDER,8 } from "./column-editor-custom-renderer.demo-data";9 import type { ColumnEditorCustomRendererEmployee } from "./column-editor-custom-renderer.demo-data";10 import ColumnEditorCustomRow from "./ColumnEditorCustomRow.svelte";11 import "@simple-table/svelte/styles.css";1213 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();1415 const getRowId = ({ row }: GetRowIdParams<ColumnEditorCustomRendererEmployee>) => row.id;1617 const editorConfig = {18 text: COLUMN_EDITOR_TEXT,19 searchEnabled: true,20 searchPlaceholder: COLUMN_EDITOR_SEARCH_PLACEHOLDER,21 rowRenderer: ColumnEditorCustomRow,22 };23</script>2425<SimpleTable26 columns={columnEditorCustomRendererConfig.headers}27 rows={columnEditorCustomRendererConfig.rows}28 {getRowId}29 enableColumnEditor={true}30 columnEditorConfig={editorConfig}31 {height}32 {theme}33/>343536// ColumnEditorCustomRow.svelte37<script lang="ts">38 import type { ColumnEditorRowRendererProps } from "@simple-table/svelte";3940 let { header, components }: ColumnEditorRowRendererProps = $props();4142 let checkboxHost: HTMLSpanElement | undefined = $state(undefined);43 let dragHost: HTMLSpanElement | undefined = $state(undefined);4445 function attach(slot: unknown, host: HTMLElement | undefined): void {46 if (!host) return;47 host.replaceChildren();48 if (slot == null) return;49 if (typeof slot === "string") {50 host.textContent = slot;51 } else if (slot instanceof Node) {52 host.appendChild(slot);53 }54 }5556 $effect(() => {57 attach(components.checkbox, checkboxHost);58 });5960 $effect(() => {61 attach(components.dragIcon, dragHost);62 });63</script>6465<div66 style="display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; background: #f8fafc; margin-bottom: 4px;"67>68 {#if components.checkbox}69 <span bind:this={checkboxHost}></span>70 {/if}71 <span style="flex: 1; font-size: 13px; font-weight: 500;">{header.label}</span>72 {#if components.dragIcon}73 <span bind:this={dragHost} style="cursor: grab; opacity: 0.5;"></span>74 {/if}75</div>76
Solid TSX
Copy
1import {2 SimpleTable,3 type ColumnEditorRowRendererProps,4 type SolidColumnEditorConfig,5 type Theme,6} from "@simple-table/solid";7import { createEffect, type JSX } from "solid-js";8import {9 columnEditorCustomRendererConfig,10 COLUMN_EDITOR_TEXT,11 COLUMN_EDITOR_SEARCH_PLACEHOLDER,12} from "./column-editor-custom-renderer.demo-data";13import "@simple-table/solid/styles.css";1415function attach(slot: unknown, host: HTMLElement | undefined): void {16 if (!host) return;17 host.replaceChildren();18 if (slot == null) return;19 if (typeof slot === "string") {20 host.textContent = slot;21 } else if (slot instanceof Node) {22 host.appendChild(slot);23 }24}2526function SlotHost(props: { slot: () => unknown; style?: JSX.CSSProperties | string }) {27 let host: HTMLSpanElement | undefined;28 createEffect(() => {29 attach(props.slot(), host);30 });31 return (32 <span33 ref={(el) => {34 host = el;35 attach(props.slot(), el);36 }}37 style={props.style}38 />39 );40}4142const CustomRowRenderer = (p: ColumnEditorRowRendererProps) => (43 <div44 style={{45 display: "flex",46 "align-items": "center",47 gap: "8px",48 padding: "6px 8px",49 "border-radius": "6px",50 background: "#f8fafc",51 "margin-bottom": "4px",52 }}53 >54 {p.components.checkbox && <SlotHost slot={() => p.components.checkbox} />}55 <span style={{ flex: "1", "font-size": "13px", "font-weight": "500" }}>{p.header.label}</span>56 {p.components.dragIcon && (57 <SlotHost slot={() => p.components.dragIcon} style={{ cursor: "grab", opacity: "0.5" }} />58 )}59 </div>60);6162const columnEditorConfig: SolidColumnEditorConfig = {63 text: COLUMN_EDITOR_TEXT,64 searchEnabled: true,65 searchPlaceholder: COLUMN_EDITOR_SEARCH_PLACEHOLDER,66 rowRenderer: CustomRowRenderer,67};6869export default function ColumnEditorCustomRendererDemo(props: {70 height?: string | number;71 theme?: Theme;72}) {73 return (74 <SimpleTable75 columns={columnEditorCustomRendererConfig.headers}76 getRowId={({ row }) => row.id}77 rows={columnEditorCustomRendererConfig.rows}78 enableColumnEditor79 columnEditorConfig={columnEditorConfig}80 height={props.height ?? "400px"}81 theme={props.theme}82 />83 );84}
TypeScriptColumnEditorCustomRendererDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, ColumnEditorRowRendererProps, GetRowIdParams } from "simple-table-core";3import {4 columnEditorCustomRendererConfig,5 COLUMN_EDITOR_TEXT,6 COLUMN_EDITOR_SEARCH_PLACEHOLDER,7} from "./column-editor-custom-renderer.demo-data";8import type { ColumnEditorCustomRendererEmployee } from "./column-editor-custom-renderer.demo-data";9import "simple-table-core/styles.css";1011function attachSlot(slot: unknown, host: HTMLElement): void {12 host.replaceChildren();13 if (slot == null) return;14 if (typeof slot === "string") {15 host.textContent = slot;16 } else if (slot instanceof Node) {17 host.appendChild(slot);18 }19}2021function buildVanillaColumnEditorRowRenderer({22 header,23 components,24}: ColumnEditorRowRendererProps): HTMLElement {25 const row = document.createElement("div");26 Object.assign(row.style, {27 display: "flex",28 alignItems: "center",29 gap: "8px",30 padding: "6px 8px",31 borderRadius: "6px",32 background: "#f8fafc",33 marginBottom: "4px",34 });3536 if (components.checkbox) {37 const checkboxHost = document.createElement("span");38 attachSlot(components.checkbox, checkboxHost);39 row.appendChild(checkboxHost);40 }4142 const label = document.createElement("span");43 Object.assign(label.style, { flex: "1", fontSize: "13px", fontWeight: "500" });44 label.textContent = header.label;45 row.appendChild(label);4647 if (components.dragIcon) {48 const dragHost = document.createElement("span");49 Object.assign(dragHost.style, { cursor: "grab", opacity: "0.5" });50 attachSlot(components.dragIcon, dragHost);51 row.appendChild(dragHost);52 }5354 return row;55}5657const getRowId = ({ row }: GetRowIdParams<ColumnEditorCustomRendererEmployee>) => row.id;5859export function renderColumnEditorCustomRendererDemo(60 container: HTMLElement,61 options?: { height?: string | number; theme?: Theme },62): SimpleTableVanilla<ColumnEditorCustomRendererEmployee> {63 return new SimpleTableVanilla(container, {64 getRowId,65 columns: columnEditorCustomRendererConfig.headers,66 rows: columnEditorCustomRendererConfig.rows,67 height: options?.height ?? "400px",68 theme: options?.theme,69 enableColumnEditor: true,70 columnEditorConfig: {71 text: COLUMN_EDITOR_TEXT,72 searchEnabled: true,73 searchPlaceholder: COLUMN_EDITOR_SEARCH_PLACEHOLDER,74 rowRenderer: buildVanillaColumnEditorRowRenderer,75 },76 });77}787980// column-editor-custom-renderer.demo-data.ts81// Self-contained demo table setup for this example.82import type { ColumnDef } from "simple-table-core";8384export interface ColumnEditorCustomRendererEmployee {85 id: number;86 name: string;87 email: string;88 role: string;89 salary: number;90 department: string;91 status: string;92}9394export const columnEditorCustomRendererData: ColumnEditorCustomRendererEmployee[] = [95 {96 id: 1,97 name: "Alice Johnson",98 email: "alice@example.com",99 role: "Engineer",100 salary: 125000,101 department: "Engineering",102 status: "active",103 },104 {105 id: 2,106 name: "Bob Martinez",107 email: "bob@example.com",108 role: "Designer",109 salary: 98000,110 department: "Design",111 status: "active",112 },113 {114 id: 3,115 name: "Clara Chen",116 email: "clara@example.com",117 role: "PM",118 salary: 115000,119 department: "Product",120 status: "inactive",121 },122 {123 id: 4,124 name: "David Kim",125 email: "david@example.com",126 role: "Engineer",127 salary: 132000,128 department: "Engineering",129 status: "active",130 },131 {132 id: 5,133 name: "Elena Rossi",134 email: "elena@example.com",135 role: "Analyst",136 salary: 89000,137 department: "Analytics",138 status: "active",139 },140 {141 id: 6,142 name: "Frank Müller",143 email: "frank@example.com",144 role: "Engineer",145 salary: 118000,146 department: "Engineering",147 status: "inactive",148 },149 {150 id: 7,151 name: "Grace Park",152 email: "grace@example.com",153 role: "Designer",154 salary: 105000,155 department: "Design",156 status: "active",157 },158 {159 id: 8,160 name: "Henry Patel",161 email: "henry@example.com",162 role: "Lead",163 salary: 145000,164 department: "Engineering",165 status: "active",166 },167];168169export const columnEditorCustomRendererHeaders: ColumnDef<ColumnEditorCustomRendererEmployee>[] = [170 { accessor: "id", label: "ID", width: 60, type: "number" },171 { accessor: "name", label: "Name", width: 170, type: "string", sortable: true },172 { accessor: "email", label: "Email", width: 200, type: "string" },173 { accessor: "role", label: "Role", width: 130, type: "string", sortable: true },174 {175 accessor: "salary",176 label: "Salary",177 width: 130,178 type: "number",179 sortable: true,180 valueFormatter: ({ value }) => `$${Number(value).toLocaleString()}`,181 },182 { accessor: "department", label: "Department", width: 140, type: "string", sortable: true },183 { accessor: "status", label: "Status", width: 100, type: "string" },184];185186export const columnEditorCustomRendererConfig = {187 headers: columnEditorCustomRendererHeaders,188 rows: columnEditorCustomRendererData,189 tableProps: {190 enableColumnEditor: true,191 },192};193194export const COLUMN_EDITOR_TEXT = "Manage Columns";195export const COLUMN_EDITOR_SEARCH_PLACEHOLDER = "Search columns…";196
Custom editor row layout
Use rowRenderer to control each row. Props are documented under ColumnEditorRowRendererProps.
React TSX
Copy
<SimpleTableenableColumnEditorcolumns={columns}rows={rows}columnEditorConfig={{rowRenderer: ({ components }) => (<div style={{ display: "flex", gap: 8, alignItems: "center" }}>{components.checkbox}{components.labelContent}{components.dragIcon}</div>),}}/>
Angular
Copy
columnEditorConfig = {rowRenderer: ({ components }) => {// return a custom row using components.checkbox, labelContent, dragIcon, pinControl},};<simple-table[enableColumnEditor]="true"[columnEditorConfig]="columnEditorConfig"[columns]="columns"[rows]="rows"></simple-table>
Vue SFC
Copy
<script setup>const columnEditorConfig = {rowRenderer: ({ components }) => {// return a custom row using components.checkbox, labelContent, dragIcon, pinControl},};</script><template><SimpleTable:enable-column-editor="true":column-editor-config="columnEditorConfig":columns="columns":rows="rows"/></template>
Svelte
Copy
<script>const columnEditorConfig = {rowRenderer: ({ components }) => {// return a custom row using components.checkbox, labelContent, dragIcon, pinControl},};</script><SimpleTableenableColumnEditor={true}columnEditorConfig={columnEditorConfig}{columns}{rows}/>
Solid TSX
Copy
<SimpleTableenableColumnEditorcolumns={columns}rows={rows}columnEditorConfig={{rowRenderer: ({ components }) => (<div style={{ display: "flex", gap: 8, alignItems: "center" }}>{components.checkbox}{components.labelContent}{components.dragIcon}</div>),}}/>
TypeScript
Copy
new SimpleTableVanilla(container, {columns,rows,enableColumnEditor: true,columnEditorConfig: {rowRenderer: ({ components }) => {const row = document.createElement("div");row.style.display = "flex";row.style.gap = "8px";row.style.alignItems = "center";if (components.checkbox) row.appendChild(components.checkbox);if (components.labelContent) row.appendChild(components.labelContent);if (components.dragIcon) row.appendChild(components.dragIcon);return row;},},});
Props
Column Visibility Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
ColumnDef.hideboolean | Optional | When true, the column starts hidden. | |
enableColumnEditorboolean | Optional | Shows the Columns panel so users can toggle and reorder columns. | |
enableColumnEditorInitOpenboolean | Optional | Opens the Columns panel on load. Requires enableColumnEditor. | |
columnEditorConfig.showToggleboolean | Optional | When false, hides the built-in Columns strip. Open the editor with tableRef.current.toggleColumnEditor(). Default: true. | |
onColumnVisibilityChange(visibilityState: ColumnVisibilityState) => void | Optional | Fires with a map of accessor → visible when visibility changes. Use it to sync or persist preferences. | |
ColumnDef.excludeFromRenderboolean | Optional | Omits the column from the table and column editor, but still includes it in CSV exports. |