Editing and Creating
Actions | |||||
|---|---|---|---|---|---|
<script lang="ts"> import { Button } from 'shadcn-svelte-tanstack-table'; import { MRT_ActionMenuItem, ShadcnSvelteTable, createShadcnSvelteTable, type MRT_ColumnDef, type MRT_Row, type MRT_TableInstance, } from 'shadcn-svelte-tanstack-table'; import { type Person, makeData, states } from '../data/makeData';
let data = $state(makeData(12)); let validationErrors = $state<Record<string, string>>({});
const columns: MRT_ColumnDef<Person>[] = [ // the first 8 characters of the UUID keep the column narrow { accessorFn: (row) => row.id.slice(0, 8), enableEditing: false, header: 'Id', id: 'id', size: 110 }, { accessorKey: 'firstName', header: 'First Name' }, { accessorKey: 'lastName', header: 'Last Name' }, { accessorKey: 'email', header: 'Email' }, { accessorKey: 'state', editSelectOptions: states, editVariant: 'select', header: 'State', }, ];
/** Returns an error per invalid field. */ const validate = (values: Record<string, unknown>) => { const errors: Record<string, string> = {}; if (!values.firstName) errors.firstName = 'First name is required'; if (!values.lastName) errors.lastName = 'Last name is required'; if (!/^\S+@\S+\.\S+$/.test(String(values.email ?? ''))) errors.email = 'Enter a valid email'; return errors; };
// The table instance is needed to start creating from the toolbar button. const table = createShadcnSvelteTable<Person>({ columns, createDisplayMode: 'modal', get data() { return data; }, editDisplayMode: 'modal', enableEditing: true, enableRowActions: true, getRowId: (row) => row.id, onCreatingRowCancel: () => (validationErrors = {}), onCreatingRowSave: ({ exitCreatingMode, values }) => { validationErrors = validate(values); if (Object.keys(validationErrors).length) return; // keep the dialog open data = [{ ...(values as unknown as Person), id: crypto.randomUUID() }, ...data]; exitCreatingMode(); }, onEditingRowCancel: () => (validationErrors = {}), onEditingRowSave: ({ exitEditingMode, row, values }) => { validationErrors = validate(values); if (Object.keys(validationErrors).length) return; data = data.map((person) => (person.id === row.id ? { ...person, ...values } : person)); exitEditingMode(); }, // show the errors on the fields editTextFieldProps: ({ cell }) => ({ input$onfocus: () => (validationErrors = { ...validationErrors, [cell.column.id]: '' }), invalid: !!validationErrors[cell.column.id], }), rowActionMenuItems: rowActions, topToolbarCustomActions: createButton, });</script>
{#snippet rowActions(ctx: { closeMenu: () => void; row: MRT_Row<Person>; table: MRT_TableInstance<Person>;})} <MRT_ActionMenuItem label="Delete" onclick={() => { data = data.filter((person) => person.id !== ctx.row.id); ctx.closeMenu(); }} table={ctx.table} />{/snippet}
{#snippet createButton()} <Button onclick={() => table.setCreatingRow(true)}>Create new user</Button>{/snippet}
{#if Object.values(validationErrors).some(Boolean)} <p role="alert" style="color: tomato"> {Object.values(validationErrors).filter(Boolean).join(' · ')} </p>{/if}<ShadcnSvelteTable {table} />Edit modes
Section titled “Edit modes”enableEditing turns editing on (a function lets you decide per row). editDisplayMode picks how:
| Mode | What happens |
|---|---|
modal | A dialog with a field per column (default) |
row | The row’s cells become inputs, with save and cancel buttons |
cell | Double-click a cell. It saves when the input loses focus or on Enter |
table | Every cell is an input all the time |
custom | You build the UI |
Disable a column with enableEditing: false, make it a dropdown with editVariant: 'select' and
editSelectOptions, or replace its input with an Edit snippet.
Saving
Section titled “Saving”The table never changes your data. When the user saves, onEditingRowSave receives the typed values. Write them
back, then call exitEditingMode():
onEditingRowSave: ({ exitEditingMode, row, values }) => { data = data.map((person) => (person.id === row.id ? { ...person, ...values } : person)); exitEditingMode();},Keep the dialog open by returning without calling exitEditingMode() (for example after a validation error). Make
this async to wait for a request, and pass isSaving in state to show a spinner on the save button.
onEditingRowCancel runs when the user cancels.
In cell mode there is no save button. Use editTextFieldProps with an input$onblur handler to save a single
cell.
Creating rows
Section titled “Creating rows”Create a table instance, and call table.setCreatingRow(true) from a button:
<script lang="ts"> const table = createShadcnSvelteTable({ columns, get data() { return data }, createDisplayMode: 'modal', // or 'row' enableEditing: true, onCreatingRowSave: ({ exitCreatingMode, values }) => { data = [values as Person, ...data]; exitCreatingMode(); }, topToolbarCustomActions: createButton, });</script>
{#snippet createButton()} <button onclick={() => table.setCreatingRow(true)}>Create new user</button>{/snippet}
<ShadcnSvelteTable {table} />With createDisplayMode: 'row' the new row is inserted inline. Use positionCreatingRow ('top', 'bottom' or an
index) to choose where.
Customizing the dialog
Section titled “Customizing the dialog”editRowDialogContent and createRowDialogContent replace the dialog’s body. They receive internalEditCells, the
cells being edited:
{#snippet editRowDialogContent({ internalEditCells, row, table })} <h2>Edit {row.original.firstName}</h2> {#each internalEditCells as cell (cell.id)} <MRT_EditCellTextField {cell} {table} /> {/each} <MRT_EditActionButtons {row} {table} variant="text" />{/snippet}Customize the dialog itself with editRowDialogProps and createRowDialogProps.