# IMAGE: a picture inside the cell

Excel's IMAGE puts a picture IN a cell rather than floating one over it, so it sorts with its row, filters with it, copies as a formula and moves when the cells move. A product list whose thumbnail column reads the swatch beside it: sort by price and the pictures follow their rows. The source is a web address or a data URL, anything else stays text rather than becoming a broken image, and the second argument is the alt text a screen reader reads.

Requires `@svgrid/enterprise` (and `@svgrid/grid`). Category: Spreadsheet. Live demo: https://svgrid.com/demos/494-sheet-cell-images/

Install: `npm i @svgrid/grid @svgrid/enterprise` (Svelte 5).

## About this example

Excel's IMAGE function in a Svelte 5 spreadsheet: a picture IN a cell rather than floating over one, so it sorts with its row, filters with it, copies as a formula and moves when the cells move. A product list with a thumbnail column reading the swatch beside it. The source is a web address or a data URL, anything else stays text rather than becoming a broken image, and the second argument is the alt text. The formula goes into the .xlsx as _xlfn.IMAGE, where Excel keeps it. Enterprise, in @svgrid/enterprise.

Excel's `IMAGE` puts a picture IN a cell rather than floating one over it, and the difference is the whole point.

```text
=IMAGE(source, [alt])   the cell IS the picture. It sorts with its
                        row, filters with it, copies as a formula,
                        and moves when the cells move.
Insert > Picture        the other kind: an object anchored to a cell,
                        floating over whatever is under it.
```

A product list is the case this exists for. The thumbnail column holds a formula reading the swatch beside it, so a row sorted to the top takes its picture along and nothing has to be kept in step.

The source is a web address or a data URL; anything else stays text rather than becoming a broken image. The second argument is the alt text, worked out like any other argument.

Try: sort by Price with Data > Sort and watch the pictures follow their rows. Type =IMAGE(C2) into an empty cell for a copy of the first one. Save As: the formula goes into the .xlsx as Excel stores it.

## Imports, features and API used

- Imports: `@svgrid/enterprise`

## Frequently asked questions

### How is this different from Insert > Picture?

Insert > Picture anchors an object that floats over the cells. An IMAGE cell is the picture: it sorts with its row, filters with it, copies as a formula and moves when the cells move, with nothing to keep in step. Use the object for a logo on the sheet and IMAGE for a column of thumbnails.

### What can the source be?

A web address or a data URL. Anything else, a file path or a javascript: scheme, is left as text rather than drawn, because a cell showing its source says more than a broken image does. The source can be a reference or a formula, so a thumbnail column can read a URL column beside it.

### Does a screen reader learn what the picture is?

Yes, from the second argument, which is worked out like any other argument and becomes the image's alt text. Without it the picture has an empty alt, which is right for a decorative swatch and wrong for a picture that carries meaning, so give it one.

## Related documentation

- [The spreadsheet shell](https://svgrid.com/docs/help/cells/spreadsheet-shell.md)
- [Charts, sparklines and pictures](https://svgrid.com/docs/help/sheet/charts-and-objects.md)

## Related articles

- [A Fill Handle (Drag to Fill) in SvGrid](https://svgrid.com/blog/fill-handle-drag-to-fill/) - Build a working spreadsheet-style fill handle on top of SvGrid's cell selection and editing - pointer tracking, range highlighting, series fill, and undo/redo integration all covered.
- [Avatar and Image Cells in SvGrid](https://svgrid.com/blog/avatar-and-image-cells/) - How to render avatars, product thumbnails, and logos in grid cells with proper fallbacks, lazy loading, and accessible alt text using Svelte 5 snippets.
- [Spreadsheet-Style Cell Range Selection in SvGrid](https://svgrid.com/blog/cell-range-selection/) - How to enable drag-to-select cell ranges, read live selection state, and build a status-bar footer that sums and averages the selected values.

## Source code (494-sheet-cell-images.svelte)

```svelte
<script lang="ts">
  /**
   * 494. IMAGE: a picture inside the cell
   * -------------------------------------
   * Excel's `IMAGE` puts a picture IN a cell rather than floating one over
   * it, and the difference is the whole point.
   *
   *   =IMAGE(source, [alt])   the cell IS the picture. It sorts with its
   *                           row, filters with it, copies as a formula,
   *                           and moves when the cells move.
   *   Insert > Picture        the other kind: an object anchored to a cell,
   *                           floating over whatever is under it.
   *
   * A product list is the case this exists for. The thumbnail column holds
   * a formula reading the swatch beside it, so a row sorted to the top
   * takes its picture along and nothing has to be kept in step.
   *
   * The source is a web address or a data URL; anything else stays text
   * rather than becoming a broken image. The second argument is the alt
   * text, worked out like any other argument.
   *
   * Try: sort by Price with Data > Sort and watch the pictures follow their
   * rows. Type =IMAGE(C2) into an empty cell for a copy of the first one.
   * Save As: the formula goes into the .xlsx as Excel stores it.
   */
  import { SvSheet, createWorkbook, createSheetDocument } from '@svgrid/enterprise'

  /** Four flat SVG swatches as data URLs, so the demo needs no network. */
  const swatch = (fill: string, mark: string): string =>
    'data:image/svg+xml;base64,' + btoa(
      `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 32"><rect width="48" height="32" rx="4" fill="${fill}"/>`
      + `<text x="24" y="21" font-family="sans-serif" font-size="13" fill="#fff" text-anchor="middle">${mark}</text></svg>`,
    )

  const products = [
    ['Licence', '1200', swatch('#2563eb', 'L'), 'A blue licence swatch'],
    ['Support', '480', swatch('#16a34a', 'S'), 'A green support swatch'],
    ['Training', '950', swatch('#ea580c', 'T'), 'An orange training swatch'],
    ['Hosting', '260', swatch('#a855f7', 'H'), 'A purple hosting swatch'],
  ]

  const rows: string[][] = [
    // "Source (data URL)": the column is text on purpose, the address the
    // thumbnail reads, and the header says so before the truncated
    // `data:image/...` reads as a picture that failed to draw.
    ['Product', 'Price', 'Source (data URL)', 'Alt text', 'Thumbnail'],
    ...products.map((p, i) => [...p, `=IMAGE(C${i + 2}, D${i + 2})`]),
    ['', '', '', '', ''],
    ['Not an image source', '', 'ftp://example.com/a.png', 'Left as text', '=IMAGE(C7, D7)'],
  ]

  const wb = createWorkbook([{ name: 'Catalogue', cells: rows }])
  const doc = createSheetDocument({ workbook: wb })
  const sheet = doc.get('Catalogue')
  const at = { rowIdAt: (i: number) => `r${i}`, columnIdAt: (i: number) => String.fromCharCode(65 + i) }
  sheet.formats.set([[0, 0, 0, 4]], { bold: true, fill: '#e2e8f0', color: '#0f172a' }, at)
  sheet.formats.set([[1, 1, 4, 1]], { numFmt: '$#,##0' }, at)
  sheet.formats.set([[1, 2, 7, 3]], { color: '#64748b' }, at)
  sheet.widths.A = 130
  sheet.widths.C = 150
  sheet.widths.D = 190
  sheet.widths.E = 110
  // Room for a picture: Excel's IMAGE fits the cell, so the cell decides.
  for (let r = 1; r <= 4; r += 1) sheet.heights.set(r, 40)
  sheet.freeze = { rows: 1, cols: 1 }
  sheet.autoFilter = { range: [0, 0, 4, 4], filters: {} }
</script>

<SvSheet document={doc} height="100%" rows={14} columns={7} />
```

[View this example on GitHub](https://github.com/sv-grid/sv-grid/blob/main/examples/src/demos/494-sheet-cell-images.svelte)
