Web Components & Custom Elements

Ship SvGrid as a framework-agnostic web component (custom element) and use the Svelte 5 data grid in React, Vue, Angular, or plain HTML - no Svelte required in the host app.

SvGrid is authored in Svelte 5, but Svelte compiles to standards-based custom elements. That means you can wrap <SvGrid> once, build it to a single .js file, and drop <sv-grid> into any page or framework - the same way you'd use a native HTML element. If you searched for a "web component data grid" or a "framework-agnostic Svelte table," this is the integration path.

Why a custom element

If your team is on Svelte 5, use the component directly (First grid) - the custom element only exists to reach non-Svelte hosts.

Quick start (no build)

We publish a prebuilt, self-contained element as sv-grid-wc. Drop it in from a CDN and you're done - no bundler, no Svelte in your app:

<script type="module" src="https://unpkg.com/sv-grid-wc"></script>

<sv-grid id="grid" sortable filterable style="display:block;height:420px"></sv-grid>

<script type="module">
  const grid = document.getElementById('grid')
  grid.columns = [{ field: 'name', header: 'Name' }, { field: 'role', header: 'Role' }]
  grid.data = [{ name: 'Ada', role: 'Engineering' }, { name: 'Alan', role: 'Research' }]
</script>

Or via npm:

npm install sv-grid-wc
import 'sv-grid-wc' // registers <sv-grid> globally

The rest of this page shows how that package is built, so you can fork it, trim it, or extend the attribute surface for your own element.

1. The wrapper component

Create one Svelte file that declares the custom element and maps its properties onto <SvGrid>. Complex inputs (data, columns) are passed as JavaScript properties; simple toggles are exposed as attributes.

<!-- src/sv-grid-element.svelte -->
<svelte:options
  customElement={{
    tag: 'sv-grid',
    shadow: 'none',
    props: {
      data: { type: 'Array' },
      columns: { type: 'Array' },
      sortable: { type: 'Boolean', attribute: 'sortable' },
      filterable: { type: 'Boolean', attribute: 'filterable' },
      selectable: { type: 'Boolean', attribute: 'selectable' },
      editable: { type: 'Boolean', attribute: 'editable' },
      rowHeight: { type: 'Number', attribute: 'row-height' },
    },
  }}
/>

<script lang="ts">
  import {
    SvGrid,
    tableFeatures,
    rowSortingFeature,
    columnFilteringFeature,
    rowSelectionFeature,
  } from 'sv-grid-community'

  let {
    data = [],
    columns = [],
    sortable = true,
    filterable = true,
    selectable = false,
    editable = false,
    rowHeight = 36,
  } = $props()

  // Build the feature set from the boolean attributes.
  const features = $derived(
    tableFeatures({
      ...(sortable ? { rowSortingFeature } : {}),
      ...(filterable ? { columnFilteringFeature } : {}),
      ...(selectable ? { rowSelectionFeature } : {}),
    }),
  )

  // Re-emit grid callbacks as DOM CustomEvents so non-Svelte hosts can listen
  // with addEventListener('rowclick', ...). $host() is the custom element.
  function emit(name: string, detail: unknown) {
    $host().dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }))
  }
</script>

<div style="height: 100%; min-height: 320px;">
  <SvGrid
    {data}
    {columns}
    features={features}
    filterMode="menu"
    selectionMode="row"
    showRowSelection={selectable}
    enableInlineEditing={editable}
    {rowHeight}
    containerHeight="100%"
    fitColumns={true}
    onRowClick={(e) => emit('rowclick', e.row)}
    onRowSelectionChange={(_e, sel) => emit('selectionchange', sel)}
  />
</div>

shadow: 'none' keeps the grid in the light DOM so your page's CSS custom properties (--sg-*) theme it normally. See Styling for the trade-offs.

2. Build it to a single file

Enable custom-element compilation only for the wrapper (compiling every component as a custom element would break the internal grid). With @sveltejs/vite-plugin-svelte:

// vite.config.js
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [
    svelte({
      // Compile ONLY the wrapper as a custom element.
      dynamicCompileOptions({ filename }) {
        if (filename.endsWith('sv-grid-element.svelte')) {
          return { customElement: true }
        }
      },
    }),
  ],
  build: {
    lib: {
      entry: 'src/sv-grid-element.svelte',
      formats: ['es'],
      fileName: 'sv-grid-element',
    },
  },
})
npm run build   # -> dist/sv-grid-element.js

Host the resulting dist/sv-grid-element.js on your own server or a CDN.

3. Use it in plain HTML

<script type="module" src="/sv-grid-element.js"></script>

<sv-grid id="grid" sortable filterable style="display:block;height:420px"></sv-grid>

<script type="module">
  const grid = document.getElementById('grid')
  // Arrays/objects are set as properties, not attributes:
  grid.columns = [
    { field: 'name', header: 'Name', editorType: 'text', width: 200 },
    { field: 'role', header: 'Role', width: 160 },
    { field: 'salary', header: 'Salary', align: 'right',
      format: { type: 'currency', currency: 'USD' } },
  ]
  grid.data = [
    { name: 'Ada Lovelace', role: 'Engineering', salary: 145000 },
    { name: 'Alan Turing', role: 'Research', salary: 160000 },
  ]
  grid.addEventListener('rowclick', (e) => console.log('clicked row', e.detail))
</script>

4. Use it in React, Vue, Angular

The element is registered globally once you import the bundle. Each framework passes object props slightly differently:

React 19+ passes objects/arrays to custom elements directly:

import '/sv-grid-element.js'

export function Grid({ rows, columns }) {
  return <sv-grid sortable filterable .data={rows} .columns={columns} />
}

On React 18 and earlier, set the properties via a ref instead (ref.current.data = rows), since older React serializes props to attributes.

Vue 3 - bind as a property with .prop, and tell Vue it's a custom element (compilerOptions.isCustomElement / app.config for sv-):

<sv-grid sortable filterable :data.prop="rows" :columns.prop="columns" />

Angular - add CUSTOM_ELEMENTS_SCHEMA to the module/component, then bind properties:

<sv-grid [data]="rows" [columns]="columns" sortable filterable></sv-grid>

Styling and shadow DOM

Mode What happens When to use
shadow: 'none' (recommended) Grid renders in the light DOM. Page CSS and --sg-* theme tokens apply normally. Almost always - a data grid wants to inherit your app theme.
shadow: 'open' (Svelte default) Grid is encapsulated in a shadow root. Set --sg-* tokens on the sv-grid host; outside CSS won't leak in. Strict style isolation / embedding in third-party pages.

Theme either mode with the same custom properties:

sv-grid {
  --sg-bg: #fff;
  --sg-header-bg: #f8fafc;
  --sg-border: #e2e8f0;
}

See Tailwind integration for the full --sg-* token list.

Events

The wrapper above re-emits grid callbacks as DOM CustomEvents, so any host listens the standard way:

grid.addEventListener('rowclick', (e) => { /* e.detail = the row */ })
grid.addEventListener('selectionchange', (e) => { /* e.detail = selected rows */ })

Add more by calling emit('yourevent', detail) from any SvGrid callback.

Caveats

Frequently asked questions

Can I use SvGrid without Svelte?

Yes. Compile it to a custom element (web component) and use <sv-grid> in React, Vue, Angular, or plain HTML. The host app does not need Svelte installed - the element ships the runtime inside its own bundle.

Does SvGrid work as a web component / custom element?

Yes. Svelte 5 compiles components to standards-based custom elements. Wrap <SvGrid> in a file with <svelte:options customElement="sv-grid">, build it to a single .js, and register <sv-grid> globally.

How do I use SvGrid in React, Vue, or Angular?

Build the custom element, import the bundle once, then render <sv-grid> and set data / columns as properties. React 19, Vue (:data.prop), and Angular (CUSTOM_ELEMENTS_SCHEMA) all support binding object properties to custom elements.

Should I use shadow DOM?

For a data grid, usually no - build with shadow: 'none' so your app's CSS and --sg-* theme tokens apply. Use shadow DOM only when you need strict style isolation.

How do I pass columns and data to the <sv-grid> element?

As JavaScript properties: el.columns = [...]; el.data = [...]. HTML attributes only carry strings, so arrays and objects must be set on the element instance.

See also