# Export - Theme-matched

One xlsx, light or dark - styles read from the same --sg-* tokens the grid renders with.

Requires `@svgrid/enterprise` (and `@svgrid/grid`). Category: Data Export & Import. Live demo: https://svgrid.com/demos/56-export-theme-matched/

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

## About this example

A theme-matched Excel export from the Svelte 5 data grid. Click export and the xlsx header row, body rows and zebra stripes match whichever theme the grid is showing, light or dark, because the styles are read from the live --sg-* CSS tokens the grid renders with. Re-theme the page and the next export follows without any export configuration.

Click any export button - the resulting file's header row, body rows, and zebra stripes match whichever theme the grid is showing (light or dark). The styles come from the live `--sg-*` CSS tokens the grid already renders with, so a re-theme of the page re-themes the export.

## Imports, features and API used

- Imports: `@svgrid/grid`, `@svgrid/enterprise`, `../shared/seed`
- Table features registered: `rowSortingFeature`, `columnFilteringFeature`
- Columns: `company` (Company), `orderId` (Order ID), `product` (Product), `sellDate` (Sell date), `quantity` (Qty), `price` (Price), `country` (Country)
- SvGridApi methods called: `api.exportData()`

## Frequently asked questions

### Where do the export colours come from?

From the computed --sg-* custom properties on the grid element at export time: header background, row background, alternate row background, border and text colours. They become cell fills and fonts in the workbook.

### Can I override the export styling?

Yes. exportData accepts a styles object with header, rows and per-cell styles that take precedence over the theme tokens when you want the file to look different from the screen.

### Does this apply to PDF too?

Yes. The same token snapshot styles the PDF header and rows, so the two formats look alike.

## Related documentation

- [Theme-matched xlsx export](https://svgrid.com/docs/recipes/theme-matched-export.md)

## Related articles

- [What's New in SvGrid Enterprise - Export, Pivot, Import, Print, and AI](https://svgrid.com/blog/whats-new-svgrid-enterprise/) - A practical look at @svgrid/enterprise - pivot tables, Excel and PDF export, data import, printing, and an AI assistant. Covers when each feature earns its place and what to watch out for in production.
- [Importing CSV into a Svelte Data Grid](https://svgrid.com/blog/importing-csv-into-the-grid/) - Parse a user-uploaded CSV file into objects, map its headers to grid columns, validate rows before committing, and handle the edge cases that actually bite you in production.
- [CSV vs XLSX vs TSV - Which Export Format?](https://svgrid.com/blog/csv-vs-xlsx-vs-tsv/) - A practical breakdown of CSV, XLSX, and TSV for data grid exports - when each format earns its keep, what each one silently loses, and how to wire all three in SvGrid.

## Source code (56-export-theme-matched.svelte)

```svelte
<script lang="ts">
  /**
   * 56. Export - theme-matched styles (Pro)
   * --------------------------------------
   * Click any export button - the resulting file's header row, body rows,
   * and zebra stripes match whichever theme the grid is showing (light or
   * dark). The styles come from the live `--sg-*` CSS tokens the grid
   * already renders with, so a re-theme of the page re-themes the export.
   */
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    columnFilteringFeature,
    type GridColumns,
  } from '@svgrid/grid'
  import {
    installEnterprise,
    type EnterpriseGridApi,
    type ExportStyles,
    type ExportFormat,
  } from '@svgrid/enterprise'
  import { makeOrders, type Order } from '../shared/seed'

  const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
  const rows = makeOrders(80)
  let api = $state<EnterpriseGridApi<typeof features, Order> | null>(null)
  let busy = $state<ExportFormat | null>(null)
  let lastExport = $state<string | null>(null)

  function readToken(name: string, fallback: string): string {
    if (typeof window === 'undefined') return fallback
    const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim()
    return v || fallback
  }
  function currentTheme(): 'light' | 'dark' {
    if (typeof document === 'undefined') return 'light'
    return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light'
  }

  function themeStyles(): ExportStyles {
    const fg       = readToken('--sg-fg',         '#0f172a')
    const bg       = readToken('--sg-bg',         '#ffffff')
    const headerBg = readToken('--sg-header-bg',  '#f1f5f9')
    const headerFg = readToken('--sg-header-fg',  '#0f172a')
    const rowAltBg = readToken('--sg-row-alt-bg', '#f8fafc')
    const border   = readToken('--sg-border',     '#e2e8f0')
    return {
      headerRow: {
        color: headerFg, backgroundColor: headerBg,
        fontWeight: 'bold', border: `1px solid ${border}`, textAlign: 'left',
      },
      rows: {
        color: fg, backgroundColor: bg, border: `1px solid ${border}`,
      },
      rowAlternate: { backgroundColor: rowAltBg },
    }
  }

  async function doExport(format: ExportFormat) {
    if (!api) return
    busy = format
    lastExport = null
    try {
      await api.exportData({
        format,
        filename: `orders-${currentTheme()}`,
        styles: themeStyles(),
      })
      lastExport = `${format.toUpperCase()} - ${currentTheme()} theme`
    } catch (err) {
      console.error('[export]', err)
    } finally {
      busy = null
    }
  }

  const columns: GridColumns<Order> = [
    { field: 'company', header: 'Company', editorType: 'text', width: 160 },
    { field: 'orderId', header: 'Order ID', editorType: 'text', width: 140 },
    { field: 'product', header: 'Product', editorType: 'text', width: 180 },
    {
      field: 'sellDate', header: 'Sell date', editorType: 'date', width: 120,
      format: { type: 'date', pattern: 'y-m-d' },
    },
    { field: 'quantity', header: 'Qty', editorType: 'number', width: 80 },
    {
      field: 'price', header: 'Price', editorType: 'number', width: 110,
      format: { type: 'currency', currency: 'USD' },
    },
    { field: 'country', header: 'Country', editorType: 'text', width: 110 },
  ]
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <div class="flex flex-wrap items-center gap-2 text-sm shrink-0">
    <span class="font-medium">Export with current theme:</span>
    {#each ['xlsx', 'pdf', 'html'] as fmt (fmt)}
      <button
        type="button"
        onclick={() => doExport(fmt as ExportFormat)}
        disabled={busy !== null}
        class="rounded-md border px-3 py-1.5 disabled:opacity-50 ex-btn"
      >
        {busy === fmt ? 'Exporting…' : fmt.toUpperCase()}
      </button>
    {/each}
    {#if lastExport}
      <span class="text-xs text-emerald-600 dark:text-emerald-400">✓ {lastExport}</span>
    {/if}
    <span class="ml-auto ex-hint">
      Flip the gallery theme (sidebar sun/moon) then re-export to compare.
    </span>
  </div>

  <div class="flex-1 min-h-0">
    <SvGrid responsive={true}
      columnResize
      data={rows}
      columns={columns}
      features={features}
      filterMode="menu"
      selectionMode="cell"
      showRowNumbers={true}
      showPagination={true}
      pageSize={25}
      enableInlineEditing={false}
      enableCellSelection={true}
      rowHeight={36}
      containerHeight="100%"
      fitColumns={true}
      onApiReady={(next) => (api = installEnterprise(next))}
    />
  </div>
</section>

<style>
  /* Toolbar chrome follows the same --sg-* tokens the export reads. */
  .ex-btn {
    border-color: var(--sg-border, #cbd5e1);
    background: var(--sg-bg, #ffffff);
    color: var(--sg-fg, #0f172a);
  }
  .ex-btn:hover:not(:disabled) { background: var(--sg-row-hover-bg, #f8fafc); }
  .ex-hint { color: var(--sg-muted, #64748b); }
</style>
```

[View this example on GitHub](https://github.com/sv-grid/sv-grid/blob/main/examples/src/demos/56-export-theme-matched.svelte)
