Provided cell editors

The grid ships nine built-in editors, selected via editorType on the column definition. Each editor renders inside the cell while editing and falls back to the default text render (or your cell snippet) when the cell is read-only.

Open the live example: List + chips editors (Editing)

editorType Renders Stored value
'text' <input type="text"> string
'number' <input type="text" inputmode="decimal"> number | null
'date' <input type="date"> ISO YYYY-MM-DD string
'datetime' <input type="datetime-local"> ISO 8601 string
'checkbox' Themed checkbox button boolean
'list' Custom dropdown (single / multi) scalar or array
'chips' Removable tag picker array of values
'color' Native OS color picker #rrggbb string
'rating' 5-star clickable widget number 0-5

Text editor - editorType: 'text'

<input type="text">. Accepts any string. On commit the raw value is stored.

{ field: 'firstName', header: 'First name', editorType: 'text' }

Number editor - editorType: 'number'

A text input with inputmode="decimal", coerced to a number at commit.

It is deliberately not <input type="number">. That element runs the HTML value-sanitization algorithm, so it only ever reports a value that parses as a valid float - and every intermediate state on the way to 12.5 is not one. The element reported "" for 12., the editor read that into its draft, and the decimal point the user had just typed disappeared: typing 12.5 produced 125. The same applied to a lone - on the way to a negative, and 1e on the way to 1e3.

A text input keeps the raw keystrokes, and parseEditorValue coerces at commit exactly as it already did. Characters that can never be part of a number are rejected as you type, which is the one guarantee type="number" did provide.

The trade-off is the native increment buttons, which a text input cannot render. inputmode="decimal" keeps the numeric keypad on mobile.

Non-numeric input is still rejected at commit (parseEditorValue returns null and the cell stays at its previous value).

The filter row and filter menu still use <input type="number"> for numeric columns, so a decimal is awkward to type there for the same reason. That is a separate surface and a separate decision, not an oversight.

{ field: 'age', header: 'Age', editorType: 'number' }

// often paired with a display format:
{
  field: 'salary', header: 'Salary',
  editorType: 'number',
  format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } },
}

Date editor - editorType: 'date'

<input type="date">. The value is stored as ISO YYYY-MM-DD.

{
  field: 'joinedAt', header: 'Joined',
  editorType: 'date',
  format: { type: 'date', pattern: 'y-m-d' },
}

Datetime editor - editorType: 'datetime'

<input type="datetime-local">. The value is stored as ISO 8601 with a Z suffix.

{
  field: 'updatedAt', header: 'Updated',
  editorType: 'datetime',
  format: { type: 'datetime', pattern: 'medium' },
}

Checkbox editor - editorType: 'checkbox'

A themed button that toggles between true and false. Renders centred in the cell.

{ field: 'active', header: 'Active', editorType: 'checkbox' }

The editor takes focus when it opens, so it works from the keyboard alone: Space or Enter toggles and commits, Esc leaves the value as it was.

List editor - editorType: 'list'

Single-select dropdown. Accepts an array of strings/numbers or { value, label, color } objects. Set editorMultiple: true for a multi-select.

{
  field: 'priority', header: 'Priority',
  editorType: 'list',
  editorOptions: ['low', 'med', 'high', 'urgent'],
}

// labelled options:
{
  field: 'status', header: 'Status',
  editorType: 'list',
  editorOptions: [
    { value: 'open',        label: 'Open' },
    { value: 'in_progress', label: 'In progress' },
    { value: 'done',        label: 'Done' },
  ],
}

Chips editor - editorType: 'chips'

Removable-token picker for multi-select. The value is always an array.

{
  field: 'tags', header: 'Tags',
  editorType: 'chips',
  editorOptions: ['frontend', 'backend', 'design', 'infra', 'docs'],
}

Async option lists

Anywhere editorOptions is accepted it may also return a Promise, for lists that live on the server. Both the static and the per-row form support it:

// One request for the whole column.
{ field: 'assignee', editorType: 'rich-select',
  editorOptions: fetch('/api/users').then((r) => r.json()) }

// Per row - a cascade, where the list depends on another cell.
{ field: 'city', editorType: 'select',
  editorOptions: (row) => fetch(`/api/cities?country=${row.country}`).then((r) => r.json()) }

While the request is in flight the dropdown shows Loading… rather than "No options", which would read as "nothing to pick". The cell keeps rendering its raw value.

Results are cached so reopening an editor never refetches. A static source is cached per column; a cascade is cached per row and per that row's data, so editing the cell it depends on supersedes the entry and the next open refetches

When the list changes server-side rather than in the row, invalidate explicitly:

api.refreshEditorOptions('city') // one column
api.refreshEditorOptions()       // everything

A rejected request settles the editor on an empty list, so a failed lookup never leaves it spinning.

Open the live example: Async editor options (Editing)

Color editor - editorType: 'color'

Native HTML color picker. The cell stores a #rrggbb string. Clicking the cell opens the OS color overlay; the value commits as soon as the overlay closes (no need to blur the cell first).

{ field: 'brandColor', header: 'Brand', editorType: 'color' }

Complete example

A minimal grid where each row has its own swatch. The custom cell snippet shows the colour next to the hex value while not editing; the editor takes over on double-click.

<script lang="ts">
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    renderSnippet,
    type ColumnDef,
  } from '@svgrid/grid'

  type Brand = { id: string; name: string; color: string }

  let rows = $state<Brand[]>([
    { id: 'b1', name: 'Acme',     color: '#6366f1' },
    { id: 'b2', name: 'Helios',   color: '#10b981' },
    { id: 'b3', name: 'Crescent', color: '#f59e0b' },
  ])

  const features = tableFeatures({ rowSortingFeature })

  const columns: ColumnDef<typeof features, Brand>[] = [
    { field: 'name',  header: 'Brand', editorType: 'text',  width: 200 },
    {
      field: 'color', header: 'Color',
      editorType: 'color',
      width: 180,
      cell: (ctx) => renderSnippet(Swatch, { row: ctx.row.original }),
    },
  ]
</script>

{#snippet Swatch(props: { row: Brand })}
  <span style="display: inline-flex; align-items: center; gap: 8px;">
    <span style="width: 18px; height: 18px; border-radius: 4px; background: {props.row.color}; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.18);"></span>
    <code style="font-family: ui-monospace, Menlo, monospace; font-size: 11.5px;">{props.row.color}</code>
  </span>
{/snippet}

<SvGrid
  data={rows}
  columns={columns}
  features={features}
  enableInlineEditing={true}
  enableCellSelection={true}
  containerHeight="100%"
/>

Theme tokens

The color editor inherits the grid's editor chrome. There are no extra tokens; the swatch fills the cell so the clickable area is the whole cell, not the OS default 25 × 13 swatch.

Rating editor - editorType: 'rating'

A row of 5 clickable stars plus a clear button. The cell stores an integer 0-5. Clicking a star commits immediately - no blur required.

{ field: 'csat', header: 'CSAT', editorType: 'rating' }

It is a radiogroup, so it also works from the keyboard: the star holding the current value takes focus when the editor opens, the arrow keys (plus Home and End) move across the five, Enter commits and Esc cancels.

Complete example

<script lang="ts">
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    renderSnippet,
    type ColumnDef,
  } from '@svgrid/grid'

  type Review = { id: string; product: string; rating: number }

  let rows = $state<Review[]>([
    { id: 'r1', product: 'Aurora keyboard',  rating: 5 },
    { id: 'r2', product: 'Helios monitor',   rating: 4 },
    { id: 'r3', product: 'Crescent mouse',   rating: 2 },
  ])

  const features = tableFeatures({ rowSortingFeature })

  const columns: ColumnDef<typeof features, Review>[] = [
    { field: 'product', header: 'Product', editorType: 'text', width: 220 },
    {
      field: 'rating', header: 'Rating',
      editorType: 'rating',
      width: 160,
      cell: (ctx) => renderSnippet(Stars, { row: ctx.row.original }),
    },
  ]
</script>

{#snippet Stars(props: { row: Review })}
  <span aria-label={`${props.row.rating} of 5`}>
    {#each [1, 2, 3, 4, 5] as n (n)}
      <span style="color: {props.row.rating >= n ? '#f59e0b' : '#cbd5e1'}; font-size: 16px;">★</span>
    {/each}
  </span>
{/snippet}

<SvGrid
  data={rows}
  columns={columns}
  features={features}
  enableInlineEditing={true}
  enableCellSelection={true}
  containerHeight="100%"
/>

Theme tokens

The rating editor reads three optional CSS custom properties so the star colors can match your design system:

Token Default Used for
--sg-rating-empty #cbd5e1 Unselected stars
--sg-rating-on #f59e0b Selected stars
--sg-rating-hover #fbbf24 Hover preview

Override per theme:

[data-theme='dark'] {
  --sg-rating-empty: #475569;
  --sg-rating-on:    #fbbf24;
}

Disabling an editor on a per-cell basis

editable accepts a callback. Return false to lock the cell - the editor never opens, even if you double-click. This composes with all the editor types above.

{
  field: 'salary', header: 'Salary',
  editorType: 'number',
  // Only admins can edit salaries.
  editable: (ctx) => currentUser.role === 'admin',
}

For declarative when-style rules across many columns, see the Conditional form schema recipe.

See also

Live examples

  • List + chips editors - Two built-in editors with single & multi-select: dropdown (list) and removable tokens (chips), with options or free-form.
  • Async editor options - editorOptions can return a Promise - for the whole column, or per row for a cascade. The dropdown shows Loading… while the request is in flight, results are cached (per column, or per column+row) so reopening never refetches, and api.refreshEditorOptions() invalidates when the server list changes.
  • Cell types showcase - Every editor in one grid: color picker, date picker, 5-star rating, mood feedback, list/chips, number formatting, status badge.
  • Custom cell editors - Three hand-rolled editors: native colour picker bound to a tag swatch, 5-star rating, emoji feedback mood. All write back through api.setCellValue.

Related articles