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}/>
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>
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>
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: 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: 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: 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";3import type { Theme, CellChangeProps } from "@simple-table/react";4import { cellEditingConfig, type CellEditingEmployee } from "./cell-editing.demo-data";5import "@simple-table/react/styles.css";67const CellEditingDemo = ({8 height = "400px",9 theme10}: {11 height?: string | number;12 theme?: Theme;13}) => {14 const [data, setData] = useState([...cellEditingConfig.rows]);1516 const handleCellEdit = ({ accessor, newValue, row }: CellChangeProps<CellEditingEmployee>) => {17 setData((prev) =>18 prev.map((item) => (item.id === row.id ? { ...item, [accessor]: newValue } : item)),19 );20 };2122 return (23 <SimpleTable24 columns={cellEditingConfig.headers}25 getRowId={({ row }) => row.id}26 rows={data}27 height={height}28 theme={theme}29 onCellEdit={handleCellEdit}30 />31 );32};3334export default CellEditingDemo;
Angularcell-editing-demo.component.ts
Copy
1import { Component, Input } from "@angular/core";2import { SimpleTableComponent } from "@simple-table/angular";3import type { AngularColumnDef, CellChangeProps, GetRowIdParams, Theme } from "@simple-table/angular";4import { cellEditingConfig } from "./cell-editing.demo-data";5import "@simple-table/angular/styles.css";6import type { CellEditingEmployee } from "./cell-editing.demo-data";78@Component({9 selector: "cell-editing-demo",10 standalone: true,11 imports: [SimpleTableComponent],12 template: `13 <simple-table14 [getRowId]="getRowId"15 [rows]="data"16 [columns]="headers"17 [height]="height"18 [theme]="theme"19 (cellEdit)="onCellEdit($event)"20 ></simple-table>21 `,22})23export class CellEditingDemoComponent {24 @Input() height: string | number = "400px";25 @Input() theme?: Theme;2627 readonly headers: AngularColumnDef<CellEditingEmployee>[] = cellEditingConfig.headers;28 data = [...cellEditingConfig.rows];2930 onCellEdit({ accessor, newValue, row }: CellChangeProps<CellEditingEmployee>): void {31 this.data = this.data.map((item) =>32 item.id === row.id ? { ...item, [accessor]: newValue } : item33 );34 }3536 getRowId = ({ row }: GetRowIdParams<CellEditingEmployee>) => row.id;37}383940// cell-editing.demo-data.ts41// Self-contained demo table setup for this example.42import type { AngularColumnDef } from "@simple-table/angular";4344export interface CellEditingEmployee {45 id: number;46 firstName: string;47 lastName: string;48 role: string;49 hireDate: string;50 isActive: boolean;51 salary: number;52}5354export const cellEditingHeaders: AngularColumnDef<CellEditingEmployee>[] = [55 { accessor: "firstName", label: "First Name", width: "1fr", minWidth: 100, editable: true, type: "string" },56 { accessor: "lastName", label: "Last Name", width: 120, editable: true, type: "string" },57 { accessor: "role", label: "Role", width: 120, editable: true, type: "enum", enumOptions: [58 { label: "Developer", value: "Developer" },59 { label: "Designer", value: "Designer" },60 { label: "Manager", value: "Manager" },61 { label: "Marketing", value: "Marketing" },62 { label: "QA", value: "QA" },63 ]},64 { accessor: "hireDate", label: "Hire Date", width: 120, editable: true, type: "date" },65 { accessor: "isActive", label: "Active", width: 100, editable: true, type: "boolean" },66 { accessor: "salary", label: "Salary", width: 120, editable: true, type: "number" },67];6869export const cellEditingData: CellEditingEmployee[] = [70 { id: 1, firstName: "Ranger", lastName: "Wilde", role: "Manager", hireDate: "2019-03-12", isActive: true, salary: 89000 },71 { id: 2, firstName: "Safari", lastName: "Brooks", role: "Designer", hireDate: "2021-07-18", isActive: true, salary: 74000 },72 { id: 3, firstName: "Forest", lastName: "Rivers", role: "Manager", hireDate: "2018-11-08", isActive: true, salary: 94000 },73 { id: 4, firstName: "Savanna", lastName: "Fields", role: "Developer", hireDate: "2022-02-14", isActive: false, salary: 81000 },74 { id: 5, firstName: "Canyon", lastName: "Stone", role: "Marketing", hireDate: "2021-09-20", isActive: true, salary: 73000 },75 { id: 6, firstName: "Meadow", lastName: "Vale", role: "QA", hireDate: "2020-06-25", isActive: true, salary: 79000 },76 { id: 7, firstName: "Ridge", lastName: "Peak", role: "Manager", hireDate: "2019-01-20", isActive: true, salary: 92000 },77 { id: 8, firstName: "Tundra", lastName: "Frost", role: "Developer", hireDate: "2022-05-03", isActive: false, salary: 85000 },78 { id: 9, firstName: "Prairie", lastName: "Wind", role: "Designer", hireDate: "2021-10-12", isActive: true, salary: 77000 },79 { id: 10, firstName: "Delta", lastName: "Flow", role: "Developer", hireDate: "2020-08-17", isActive: true, salary: 83000 },80 { id: 11, firstName: "Grove", lastName: "Shade", role: "Designer", hireDate: "2022-01-09", isActive: true, salary: 76000 },81 { id: 12, firstName: "Cliff", lastName: "Edge", role: "QA", hireDate: "2019-12-04", isActive: false, salary: 82000 },82];8384export const cellEditingConfig = {85 headers: cellEditingHeaders,86 rows: cellEditingData,87};88
Vue SFC
Copy
1<template>2 <SimpleTable3 :columns="cellEditingConfig.headers"4 :rows="data"5 :get-row-id="getRowId"6 :height="height"7 :theme="theme"8 @cell-edit="handleCellEdit"9 />10</template>1112<script setup lang="ts">13import { ref } from "vue";14import { SimpleTable } from "@simple-table/vue";15import type { Theme, CellChangeProps, GetRowIdParams } from "@simple-table/vue";16import { cellEditingConfig } from "./cell-editing.demo-data";17import type { CellEditingEmployee } from "./cell-editing.demo-data";18import "@simple-table/vue/styles.css";1920withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {21 height: "400px",22});2324const getRowId = ({ row }: GetRowIdParams<CellEditingEmployee>) => row.id;2526const data = ref<CellEditingEmployee[]>([...cellEditingConfig.rows]);2728const handleCellEdit = ({ accessor, newValue, row }: CellChangeProps<CellEditingEmployee>) => {29 data.value = data.value.map((item) =>30 item.id === row.id ? { ...item, [accessor]: newValue } : item31 );32};33</script>
Svelte
Copy
1<script lang="ts">2 import { SimpleTable } from "@simple-table/svelte";3 import type { Theme, CellChangeProps, GetRowIdParams } from "@simple-table/svelte";4 import { cellEditingConfig } from "./cell-editing.demo-data";5 import type { CellEditingEmployee } from "./cell-editing.demo-data";6 import "@simple-table/svelte/styles.css";78 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();9 let data = $state<CellEditingEmployee[]>([...cellEditingConfig.rows]);1011 const getRowId = ({ row }: GetRowIdParams<CellEditingEmployee>) => row.id;1213 function handleCellEdit({ accessor, newValue, row }: CellChangeProps<CellEditingEmployee>) {14 data = data.map((item) =>15 item.id === row.id ? { ...item, [accessor]: newValue } : item16 );17 }18</script>1920<SimpleTable21 columns={cellEditingConfig.headers}22 rows={data}23 getRowId={getRowId}24 {height}25 {theme}26 onCellEdit={handleCellEdit}27/>
Solid TSX
Copy
1import { createSignal } from "solid-js";2import { SimpleTable } from "@simple-table/solid";3import type { Theme } from "@simple-table/solid";4import { cellEditingConfig } from "./cell-editing.demo-data";5import "@simple-table/solid/styles.css";67export default function CellEditingDemo(props: { height?: string | number; theme?: Theme }) {8 const [data, setData] = createSignal([...cellEditingConfig.rows]);910 return (11 <SimpleTable12 columns={cellEditingConfig.headers}13 getRowId={({ row }) => row.id}14 rows={data()}15 height={props.height ?? "400px"}16 theme={props.theme}17 onCellEdit={({ accessor, newValue, row }) => {18 setData((prev) =>19 prev.map((item) => (item.id === row.id ? { ...item, [accessor]: newValue } : item)),20 );21 }}22 />23 );24}
TypeScriptCellEditingDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { CellEditingEmployee } from "./cell-editing.demo-data";3import type { Theme, CellChangeProps, GetRowIdParams } from "simple-table-core";4import { cellEditingConfig } from "./cell-editing.demo-data";5import "simple-table-core/styles.css";678const getRowId = ({ row }: GetRowIdParams<CellEditingEmployee>) => row.id;9export function renderCellEditingDemo(10 container: HTMLElement,11 options?: { height?: string | number; theme?: Theme }12): SimpleTableVanilla<CellEditingEmployee> {13 let rows = [...cellEditingConfig.rows];1415 const table = new SimpleTableVanilla(container, {16 getRowId,17 columns: cellEditingConfig.headers,18 rows,19 height: options?.height ?? "400px",20 theme: options?.theme,21 onCellEdit: ({ accessor, newValue, row }: CellChangeProps<CellEditingEmployee>) => {22 rows = rows.map((item) =>23 item.id === row.id ? { ...item, [accessor]: newValue } : item24 );25 table.update({ rows });26 },27 });28 return table;29}303132// cell-editing.demo-data.ts33// Self-contained demo table setup for this example.34import type { ColumnDef } from "simple-table-core";3536export interface CellEditingEmployee {37 id: number;38 firstName: string;39 lastName: string;40 role: string;41 hireDate: string;42 isActive: boolean;43 salary: number;44}4546export const cellEditingHeaders: ColumnDef<CellEditingEmployee>[] = [47 { accessor: "firstName", label: "First Name", width: "1fr", minWidth: 100, editable: true, type: "string" },48 { accessor: "lastName", label: "Last Name", width: 120, editable: true, type: "string" },49 { accessor: "role", label: "Role", width: 120, editable: true, type: "enum", enumOptions: [50 { label: "Developer", value: "Developer" },51 { label: "Designer", value: "Designer" },52 { label: "Manager", value: "Manager" },53 { label: "Marketing", value: "Marketing" },54 { label: "QA", value: "QA" },55 ]},56 { accessor: "hireDate", label: "Hire Date", width: 120, editable: true, type: "date" },57 { accessor: "isActive", label: "Active", width: 100, editable: true, type: "boolean" },58 { accessor: "salary", label: "Salary", width: 120, editable: true, type: "number" },59];6061export const cellEditingData: CellEditingEmployee[] = [62 { id: 1, firstName: "Ranger", lastName: "Wilde", role: "Manager", hireDate: "2019-03-12", isActive: true, salary: 89000 },63 { id: 2, firstName: "Safari", lastName: "Brooks", role: "Designer", hireDate: "2021-07-18", isActive: true, salary: 74000 },64 { id: 3, firstName: "Forest", lastName: "Rivers", role: "Manager", hireDate: "2018-11-08", isActive: true, salary: 94000 },65 { id: 4, firstName: "Savanna", lastName: "Fields", role: "Developer", hireDate: "2022-02-14", isActive: false, salary: 81000 },66 { id: 5, firstName: "Canyon", lastName: "Stone", role: "Marketing", hireDate: "2021-09-20", isActive: true, salary: 73000 },67 { id: 6, firstName: "Meadow", lastName: "Vale", role: "QA", hireDate: "2020-06-25", isActive: true, salary: 79000 },68 { id: 7, firstName: "Ridge", lastName: "Peak", role: "Manager", hireDate: "2019-01-20", isActive: true, salary: 92000 },69 { id: 8, firstName: "Tundra", lastName: "Frost", role: "Developer", hireDate: "2022-05-03", isActive: false, salary: 85000 },70 { id: 9, firstName: "Prairie", lastName: "Wind", role: "Designer", hireDate: "2021-10-12", isActive: true, salary: 77000 },71 { id: 10, firstName: "Delta", lastName: "Flow", role: "Developer", hireDate: "2020-08-17", isActive: true, salary: 83000 },72 { id: 11, firstName: "Grove", lastName: "Shade", role: "Designer", hireDate: "2022-01-09", isActive: true, salary: 76000 },73 { id: 12, firstName: "Cliff", lastName: "Edge", role: "QA", hireDate: "2019-12-04", isActive: false, salary: 82000 },74];7576export const cellEditingConfig = {77 headers: cellEditingHeaders,78 rows: cellEditingData,79};80
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. |