Documentation
Cell Editing
Edit values in place. Mark columns editable, then persist changes with onCellEdit.
Mark columns editable
Set editable: true on each column users can change. Column type picks the editor (string, number, boolean, date, or enum).
TypeScript
Copy
{accessor: "firstName",label: "First Name",width: "1fr",type: "string",editable: true,}
TypeScript
Copy
{accessor: "firstName",label: "First Name",width: "1fr",type: "string",editable: true,}
TypeScript
Copy
{accessor: "firstName",label: "First Name",width: "1fr",type: "string",editable: true,}
TypeScript
Copy
{accessor: "firstName",label: "First Name",width: "1fr",type: "string",editable: true,}
TypeScript
Copy
{accessor: "firstName",label: "First Name",width: "1fr",type: "string",editable: true,}
TypeScript
Copy
{accessor: "firstName",label: "First Name",width: "1fr",type: "string",editable: true,}
Handle onCellEdit
Update your data when onCellEdit fires. It receives accessor, newValue, and the row.
React TSX
Copy
const handleCellEdit = ({ accessor, newValue, row }) => {setRows((prev) =>prev.map((r) => (r.id === row.id ? { ...r, [accessor]: newValue } : r)));};<SimpleTablecolumns={columns}rows={rows}onCellEdit={handleCellEdit}/>
Vue SFC
Copy
<script setup>const handleCellEdit = ({ accessor, newValue, row }) => {setRows((prev) =>prev.map((r) => (r.id === row.id ? { ...r, [accessor]: newValue } : r)));};</script><template><SimpleTable:columns="columns":rows="rows"@cell-edit="handleCellEdit"/></template>
Angular
Copy
const handleCellEdit = ({ accessor, newValue, row }) => {setRows((prev) =>prev.map((r) => (r.id === row.id ? { ...r, [accessor]: newValue } : r)));};<simple-table[columns]="columns"[rows]="rows"(cellEdit)="handleCellEdit($event)"></simple-table>
Svelte
Copy
<script>const handleCellEdit = ({ accessor, newValue, row }) => {setRows((prev) =>prev.map((r) => (r.id === row.id ? { ...r, [accessor]: newValue } : r)));};</script><SimpleTable{columns}{rows}onCellEdit={handleCellEdit}/>
Solid TSX
Copy
const handleCellEdit = ({ accessor, newValue, row }) => {setRows((prev) =>prev.map((r) => (r.id === row.id ? { ...r, [accessor]: newValue } : r)));};<SimpleTablecolumns={columns}rows={rows()}onCellEdit={handleCellEdit}/>
TypeScript
Copy
const handleCellEdit = ({ accessor, newValue, row }) => {setRows((prev) =>prev.map((r) => (r.id === row.id ? { ...r, [accessor]: newValue } : r)));};new SimpleTableVanilla(container, {columns,rows,onCellEdit: handleCellEdit,});
Editors by type
Use type for the matching control. Enum columns also need enumOptions.
TypeScript
Copy
const columns: ReactColumnDef[] = [{ accessor: "firstName", label: "First Name", type: "string", editable: true },{ accessor: "salary", label: "Salary", type: "number", editable: true },{ accessor: "isActive", label: "Active", type: "boolean", editable: true },{ accessor: "hireDate", label: "Hire Date", type: "date", editable: true },{accessor: "role",label: "Role",type: "enum",editable: true,enumOptions: [{ label: "Developer", value: "Developer" },{ label: "Designer", value: "Designer" },{ label: "Manager", value: "Manager" },],},];
TypeScript
Copy
const columns: VueColumnDef[] = [{ accessor: "firstName", label: "First Name", type: "string", editable: true },{ accessor: "salary", label: "Salary", type: "number", editable: true },{ accessor: "isActive", label: "Active", type: "boolean", editable: true },{ accessor: "hireDate", label: "Hire Date", type: "date", editable: true },{accessor: "role",label: "Role",type: "enum",editable: true,enumOptions: [{ label: "Developer", value: "Developer" },{ label: "Designer", value: "Designer" },{ label: "Manager", value: "Manager" },],},];
TypeScript
Copy
const columns: AngularColumnDef[] = [{ accessor: "firstName", label: "First Name", type: "string", editable: true },{ accessor: "salary", label: "Salary", type: "number", editable: true },{ accessor: "isActive", label: "Active", type: "boolean", editable: true },{ accessor: "hireDate", label: "Hire Date", type: "date", editable: true },{accessor: "role",label: "Role",type: "enum",editable: true,enumOptions: [{ label: "Developer", value: "Developer" },{ label: "Designer", value: "Designer" },{ label: "Manager", value: "Manager" },],},];
TypeScript
Copy
const columns: SvelteColumnDef[] = [{ accessor: "firstName", label: "First Name", type: "string", editable: true },{ accessor: "salary", label: "Salary", type: "number", editable: true },{ accessor: "isActive", label: "Active", type: "boolean", editable: true },{ accessor: "hireDate", label: "Hire Date", type: "date", editable: true },{accessor: "role",label: "Role",type: "enum",editable: true,enumOptions: [{ label: "Developer", value: "Developer" },{ label: "Designer", value: "Designer" },{ label: "Manager", value: "Manager" },],},];
TypeScript
Copy
const columns: SolidColumnDef[] = [{ accessor: "firstName", label: "First Name", type: "string", editable: true },{ accessor: "salary", label: "Salary", type: "number", editable: true },{ accessor: "isActive", label: "Active", type: "boolean", editable: true },{ accessor: "hireDate", label: "Hire Date", type: "date", editable: true },{accessor: "role",label: "Role",type: "enum",editable: true,enumOptions: [{ label: "Developer", value: "Developer" },{ label: "Designer", value: "Designer" },{ label: "Manager", value: "Manager" },],},];
TypeScript
Copy
const columns: ColumnDef[] = [{ accessor: "firstName", label: "First Name", type: "string", editable: true },{ accessor: "salary", label: "Salary", type: "number", editable: true },{ accessor: "isActive", label: "Active", type: "boolean", editable: true },{ accessor: "hireDate", label: "Hire Date", type: "date", editable: true },{accessor: "role",label: "Role",type: "enum",editable: true,enumOptions: [{ label: "Developer", value: "Developer" },{ label: "Designer", value: "Designer" },{ label: "Manager", value: "Manager" },],},];
Copy and paste
Ctrl/⌘+C copies selected cells; paste from spreadsheets works too. Only columns with editable: true accept pasted values — others are skipped.
Example
Double-click a cell to edit. Use Code or StackBlitz for the full example.
React TSX
Copy
1import { useState } from "react";2import {SimpleTable} from "@simple-table/react";import type { Theme, CellChangeProps } from "@simple-table/react";3import { cellEditingConfig } from "./cell-editing.demo-data";4import "@simple-table/react/styles.css";56const CellEditingDemo = ({7 height = "400px",8 theme,9}: {10 height?: string | number;11 theme?: Theme;12}) => {13 const [data, setData] = useState([...cellEditingConfig.rows]);1415 const handleCellEdit = ({ accessor, newValue, row }: CellChangeProps) => {16 setData((prev) =>17 prev.map((item) =>18 item.id === row.id ? { ...item, [accessor]: newValue } : item19 )20 );21 };2223 return (24 <SimpleTable25 columns={cellEditingConfig.headers}26 rows={data}27 height={height}28 theme={theme}29 onCellEdit={handleCellEdit}30 />31 );32};3334export default CellEditingDemo;
Vue SFC
Copy
1<template>2 <SimpleTable3 :columns="cellEditingConfig.headers"4 :rows="data"5 :height="height"6 :theme="theme"7 @cell-edit="handleCellEdit"8 />9</template>1011<script setup lang="ts">12import { ref } from "vue";13import {SimpleTable} from "@simple-table/vue";import type { Theme, CellChangeProps } from "@simple-table/vue";14import { cellEditingConfig } from "./cell-editing.demo-data";15import "@simple-table/vue/styles.css";1617withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {18 height: "400px",19});2021const data = ref([...cellEditingConfig.rows]);2223const handleCellEdit = ({ accessor, newValue, row }: CellChangeProps) => {24 data.value = data.value.map((item) =>25 item.id === row.id ? { ...item, [accessor]: newValue } : item26 );27};28</script>
Angularcell-editing-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, CellChangeProps, Theme } from "@simple-table/angular";3import { cellEditingConfig } from "./cell-editing.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "cell-editing-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <simple-table12 [rows]="data"13 [columns]="headers"14 [height]="height"15 [theme]="theme"16 (cellEdit)="onCellEdit($event)"17 ></simple-table>18 `,19})20export class CellEditingDemoComponent {21 @Input() height: string | number = "400px";22 @Input() theme?: Theme;2324 readonly headers: AngularColumnDef[] = cellEditingConfig.headers;25 data = [...cellEditingConfig.rows];2627 onCellEdit({ accessor, newValue, row }: CellChangeProps): void {28 this.data = this.data.map((item) =>29 item.id === row.id ? { ...item, [accessor]: newValue } : item30 );31 }32}333435// cell-editing.demo-data.ts36// Self-contained demo table setup for this example.37import type { AngularColumnDef } from "@simple-table/angular";383940export const cellEditingHeaders: AngularColumnDef[] = [41 { accessor: "firstName", label: "First Name", width: "1fr", minWidth: 100, editable: true, type: "string" },42 { accessor: "lastName", label: "Last Name", width: 120, editable: true, type: "string" },43 { accessor: "role", label: "Role", width: 120, editable: true, type: "enum", enumOptions: [44 { label: "Developer", value: "Developer" },45 { label: "Designer", value: "Designer" },46 { label: "Manager", value: "Manager" },47 { label: "Marketing", value: "Marketing" },48 { label: "QA", value: "QA" },49 ]},50 { accessor: "hireDate", label: "Hire Date", width: 120, editable: true, type: "date" },51 { accessor: "isActive", label: "Active", width: 100, editable: true, type: "boolean" },52 { accessor: "salary", label: "Salary", width: 120, editable: true, type: "number" },53];5455export const cellEditingData = [56 { id: 1, firstName: "Ranger", lastName: "Wilde", role: "Manager", hireDate: "2019-03-12", isActive: true, salary: 89000 },57 { id: 2, firstName: "Safari", lastName: "Brooks", role: "Designer", hireDate: "2021-07-18", isActive: true, salary: 74000 },58 { id: 3, firstName: "Forest", lastName: "Rivers", role: "Manager", hireDate: "2018-11-08", isActive: true, salary: 94000 },59 { id: 4, firstName: "Savanna", lastName: "Fields", role: "Developer", hireDate: "2022-02-14", isActive: false, salary: 81000 },60 { id: 5, firstName: "Canyon", lastName: "Stone", role: "Marketing", hireDate: "2021-09-20", isActive: true, salary: 73000 },61 { id: 6, firstName: "Meadow", lastName: "Vale", role: "QA", hireDate: "2020-06-25", isActive: true, salary: 79000 },62 { id: 7, firstName: "Ridge", lastName: "Peak", role: "Manager", hireDate: "2019-01-20", isActive: true, salary: 92000 },63 { id: 8, firstName: "Tundra", lastName: "Frost", role: "Developer", hireDate: "2022-05-03", isActive: false, salary: 85000 },64 { id: 9, firstName: "Prairie", lastName: "Wind", role: "Designer", hireDate: "2021-10-12", isActive: true, salary: 77000 },65 { id: 10, firstName: "Delta", lastName: "Flow", role: "Developer", hireDate: "2020-08-17", isActive: true, salary: 83000 },66 { id: 11, firstName: "Grove", lastName: "Shade", role: "Designer", hireDate: "2022-01-09", isActive: true, salary: 76000 },67 { id: 12, firstName: "Cliff", lastName: "Edge", role: "QA", hireDate: "2019-12-04", isActive: false, salary: 82000 },68];6970export const cellEditingConfig = {71 headers: cellEditingHeaders,72 rows: cellEditingData,73} as const;74
Svelte
Copy
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme, CellChangeProps } from "@simple-table/svelte";3 import { cellEditingConfig } from "./cell-editing.demo-data";4 import "@simple-table/svelte/styles.css";56 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();7 let data = $state([...cellEditingConfig.rows]);89 function handleCellEdit({ accessor, newValue, row }: CellChangeProps) {10 data = data.map((item) =>11 item.id === row.id ? { ...item, [accessor]: newValue } : item12 );13 }14</script>1516<SimpleTable17 columns={cellEditingConfig.headers}18 rows={data}19 {height}20 {theme}21 onCellEdit={handleCellEdit}22/>
Solid TSX
Copy
1import { createSignal } from "solid-js";2import {SimpleTable} from "@simple-table/solid";import type { Theme, CellChangeProps } from "@simple-table/solid";3import { cellEditingConfig } from "./cell-editing.demo-data";4import "@simple-table/solid/styles.css";56export default function CellEditingDemo(props: { height?: string | number; theme?: Theme }) {7 const [data, setData] = createSignal([...cellEditingConfig.rows]);89 const handleCellEdit = ({ accessor, newValue, row }: CellChangeProps) => {10 setData((prev) =>11 prev.map((item) => (item.id === row.id ? { ...item, [accessor]: newValue } : item))12 );13 };1415 return (16 <SimpleTable17 columns={cellEditingConfig.headers}18 rows={data()}19 height={props.height ?? "400px"}20 theme={props.theme}21 onCellEdit={handleCellEdit}22 />23 );24}
TypeScriptCellEditingDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, CellChangeProps } from "simple-table-core";3import { cellEditingConfig } from "./cell-editing.demo-data";4import "simple-table-core/styles.css";56export function renderCellEditingDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): SimpleTableVanilla {10 let rows = [...cellEditingConfig.rows];1112 const table = new SimpleTableVanilla(container, {13 columns: cellEditingConfig.headers,14 rows,15 height: options?.height ?? "400px",16 theme: options?.theme,17 onCellEdit: ({ accessor, newValue, row }: CellChangeProps) => {18 rows = rows.map((item) =>19 item.id === row.id ? { ...item, [accessor]: newValue } : item20 );21 table.update({ rows });22 },23 });24 return table;25}262728// cell-editing.demo-data.ts29// Self-contained demo table setup for this example.30import type { ColumnDef } from "simple-table-core";313233export const cellEditingHeaders: ColumnDef[] = [34 { accessor: "firstName", label: "First Name", width: "1fr", minWidth: 100, editable: true, type: "string" },35 { accessor: "lastName", label: "Last Name", width: 120, editable: true, type: "string" },36 { accessor: "role", label: "Role", width: 120, editable: true, type: "enum", enumOptions: [37 { label: "Developer", value: "Developer" },38 { label: "Designer", value: "Designer" },39 { label: "Manager", value: "Manager" },40 { label: "Marketing", value: "Marketing" },41 { label: "QA", value: "QA" },42 ]},43 { accessor: "hireDate", label: "Hire Date", width: 120, editable: true, type: "date" },44 { accessor: "isActive", label: "Active", width: 100, editable: true, type: "boolean" },45 { accessor: "salary", label: "Salary", width: 120, editable: true, type: "number" },46];4748export const cellEditingData = [49 { id: 1, firstName: "Ranger", lastName: "Wilde", role: "Manager", hireDate: "2019-03-12", isActive: true, salary: 89000 },50 { id: 2, firstName: "Safari", lastName: "Brooks", role: "Designer", hireDate: "2021-07-18", isActive: true, salary: 74000 },51 { id: 3, firstName: "Forest", lastName: "Rivers", role: "Manager", hireDate: "2018-11-08", isActive: true, salary: 94000 },52 { id: 4, firstName: "Savanna", lastName: "Fields", role: "Developer", hireDate: "2022-02-14", isActive: false, salary: 81000 },53 { id: 5, firstName: "Canyon", lastName: "Stone", role: "Marketing", hireDate: "2021-09-20", isActive: true, salary: 73000 },54 { id: 6, firstName: "Meadow", lastName: "Vale", role: "QA", hireDate: "2020-06-25", isActive: true, salary: 79000 },55 { id: 7, firstName: "Ridge", lastName: "Peak", role: "Manager", hireDate: "2019-01-20", isActive: true, salary: 92000 },56 { id: 8, firstName: "Tundra", lastName: "Frost", role: "Developer", hireDate: "2022-05-03", isActive: false, salary: 85000 },57 { id: 9, firstName: "Prairie", lastName: "Wind", role: "Designer", hireDate: "2021-10-12", isActive: true, salary: 77000 },58 { id: 10, firstName: "Delta", lastName: "Flow", role: "Developer", hireDate: "2020-08-17", isActive: true, salary: 83000 },59 { id: 11, firstName: "Grove", lastName: "Shade", role: "Designer", hireDate: "2022-01-09", isActive: true, salary: 76000 },60 { id: 12, firstName: "Cliff", lastName: "Edge", role: "QA", hireDate: "2019-12-04", isActive: false, salary: 82000 },61];6263export const cellEditingConfig = {64 headers: cellEditingHeaders,65 rows: cellEditingData,66} as const;67
Props
Cell Editing Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
ColumnDef.editableboolean | Optional | When true, cells in the column can be edited (and accept paste). | |
ColumnDef.type | Optional | Chooses the cell editor and validation for editable columns. Options: string number boolean date enum | |
ColumnDef.enumOptionsEnumOption[] | Optional | Dropdown options for enum columns. | |
onCellEdit | Optional | Fires when a cell value changes. Update your rows from the callback. |