Skip to content

Adding VTK.wasm to a Project

There are two ways to get VTK.wasm into a web project:

  • HTML Script Tag — load a prebuilt bundle from a CDN, no build step required. Best for quick prototypes, demos, and embedding into existing pages.
  • Bundler — install the @kitware/vtk-wasm package and import it. Best for application development with a tool like Vite.

Either way you end up calling loadAsync; only how it reaches the page differs.

HTML Script Tag

Use VTK.wasm directly in an HTML file using a <script> tag without a build step.

The following examples rely on loading the vtk.umd.js bundle from a CDN. To focus on the initialization part, we've externalized the JS/WASM scene code since that part does not change.

Load WASM as a module

In this example we pre-load the WASM module, so we don't need to provide any URL when loading it.

html
<html>
  <head>
    <script
      src="/path/to/vtkWebAssembly.mjs"
      type="module"
    ></script>
    <script src="https://unpkg.com/@kitware/vtk-wasm/vtk-umd.js"></script>
    <script src="example.js"></script>
  </head>
  <body>
    <canvas id="vtk-wasm-window"></canvas>
    <script>
      vtkwasm.loadAsync().then((runtime) =>
        buildWASMScene(runtime.createStandaloneSession().vtk),
      );
    </script>
  </body>
</html>
js
async function buildWASMScene(vtk, titleText = "Sample VTK.wasm scene") {

  function createSharedTextProperty() {
    const textProperty = vtk.vtkTextProperty({fontSize: 22});
    return textProperty;
  }

  async function createLookupTable(scalarRange) {
    const lut = vtk.vtkColorTransferFunction();
    await lut.setColorSpaceToHSV();
    const colorSeries = vtk.vtkColorSeries({ colorScheme: 16 });
    const numColors = await colorSeries.getNumberOfColors();
    const scalarDiff = (scalarRange[1] - scalarRange[0]) / numColors;
    for (let i = 0; i < numColors; i++) {
      const color = await colorSeries.getColor(i);
      const t = scalarRange[0] + i * scalarDiff;
      lut.addRGBPoint(
        t,
        color[0] / 255,
        color[1] / 255,
        color[2] / 255,
      );
    }
    await lut.build();
    return lut;
  }

  async function createTitleTextActor(titleText, textProperty) {
    const textActor = vtk.vtkTextActor({ input: titleText, textProperty });
    const position = await textActor.getPositionCoordinate();
    await position.setCoordinateSystemToNormalizedViewport();
    return textActor;
  }

  // Create a VTK source. Output has a point data array named "Scalars" whose range is [0, PI].
  const shapes = vtk.vtkPartitionedDataSetCollectionSource({ numberOfShapes: 2 });
  const lut = await createLookupTable([0.0, Math.PI]);

  const mapper = vtk.vtkCompositePolyDataMapper({ lookupTable: lut });
  await mapper.setInputConnection(await shapes.getOutputPort());
  const actor = vtk.vtkActor({ mapper, scale: [0.1, 0.1, 0.1] });
  actor.property.edgeVisibility = true;
  actor.property.edgeColor = [0.2, 0.2, 0.2];

  const textProperty = createSharedTextProperty();

  // Create an actor that displays the title.
  const titleTextActor = await createTitleTextActor(titleText, textProperty);

  // Setup rendering part
  const renderer = vtk.vtkRenderer({ background: [0.384314, 0.364706, 0.352941] });
  await renderer.addActor(actor);
  await renderer.addActor(titleTextActor);
  await renderer.resetCamera();

  // Create a RenderWindow and bind it to a canvas in the DOM
  const canvasSelector = "#vtk-wasm-window";
  const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
  await renderWindow.addRenderer(renderer);
  const interactor = vtk.vtkRenderWindowInteractor({
    canvasSelector,
    renderWindow,
  });
  await interactor.interactorStyle.setCurrentStyleToTrackballCamera();

  // Create camera widget
  const cameraOrientation = vtk.vtkCameraOrientationWidget({ interactor, parentRenderer: renderer });
  cameraOrientation.enabled = true;

  // Display the scalar bar at the bottom with a horizontal orientation
  const scalarBarActor = vtk.vtkScalarBarActor({ 
    lookupTable: lut,
    title: "Scalars",
    titleTextProperty: textProperty,
    labelTextProperty: textProperty,
    annotationTextProperty: textProperty,
    unconstrainedFontSize: true,
  });
  const scalarBar = vtk.vtkScalarBarWidget({ scalarBarActor, interactor, defaultRenderer: renderer });
  const scalarBarRepresentation = await scalarBar.getRepresentation();
  await scalarBarRepresentation.setOrientation(0); // 1: vertical, 0: horizontal
  const lowerLeftPosition = await scalarBarRepresentation.getPositionCoordinate();
  await lowerLeftPosition.setValue([0.1, 0.05]);
  scalarBar.enabled = true;

  // Trigger render and start interactor
  await interactor.start();
}

Defer WASM loading

Since we didn't pre-load the WASM module here, we provide the URL where the WASM bundle can be found.

html
<html>
  <head>
    <script src="https://unpkg.com/@kitware/vtk-wasm/vtk-umd.js"></script>
    <script src="example.js"></script>
  </head>
  <body>
    <canvas id="vtk-wasm-window" tabindex="-1" onclick="focus()"></canvas>
    <script>
      vtkwasm.loadAsync({ url: "https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-wasm32-emscripten/9.5.20251215/vtk-9.5.20251215-wasm32-emscripten.tar.gz" })
      .then((runtime) => {
        const session = runtime.createStandaloneSession();
        buildWASMScene(session.vtk, "This scene passes the VTK.wasm bundle from GitLab registry to loadAsync()");
      });
    </script>
  </body>
</html>
js
async function buildWASMScene(vtk, titleText = "Sample VTK.wasm scene") {

  function createSharedTextProperty() {
    const textProperty = vtk.vtkTextProperty({fontSize: 22});
    return textProperty;
  }

  async function createLookupTable(scalarRange) {
    const lut = vtk.vtkColorTransferFunction();
    await lut.setColorSpaceToHSV();
    const colorSeries = vtk.vtkColorSeries({ colorScheme: 16 });
    const numColors = await colorSeries.getNumberOfColors();
    const scalarDiff = (scalarRange[1] - scalarRange[0]) / numColors;
    for (let i = 0; i < numColors; i++) {
      const color = await colorSeries.getColor(i);
      const t = scalarRange[0] + i * scalarDiff;
      lut.addRGBPoint(
        t,
        color[0] / 255,
        color[1] / 255,
        color[2] / 255,
      );
    }
    await lut.build();
    return lut;
  }

  async function createTitleTextActor(titleText, textProperty) {
    const textActor = vtk.vtkTextActor({ input: titleText, textProperty });
    const position = await textActor.getPositionCoordinate();
    await position.setCoordinateSystemToNormalizedViewport();
    return textActor;
  }

  // Create a VTK source. Output has a point data array named "Scalars" whose range is [0, PI].
  const shapes = vtk.vtkPartitionedDataSetCollectionSource({ numberOfShapes: 2 });
  const lut = await createLookupTable([0.0, Math.PI]);

  const mapper = vtk.vtkCompositePolyDataMapper({ lookupTable: lut });
  await mapper.setInputConnection(await shapes.getOutputPort());
  const actor = vtk.vtkActor({ mapper, scale: [0.1, 0.1, 0.1] });
  actor.property.edgeVisibility = true;
  actor.property.edgeColor = [0.2, 0.2, 0.2];

  const textProperty = createSharedTextProperty();

  // Create an actor that displays the title.
  const titleTextActor = await createTitleTextActor(titleText, textProperty);

  // Setup rendering part
  const renderer = vtk.vtkRenderer({ background: [0.384314, 0.364706, 0.352941] });
  await renderer.addActor(actor);
  await renderer.addActor(titleTextActor);
  await renderer.resetCamera();

  // Create a RenderWindow and bind it to a canvas in the DOM
  const canvasSelector = "#vtk-wasm-window";
  const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
  await renderWindow.addRenderer(renderer);
  const interactor = vtk.vtkRenderWindowInteractor({
    canvasSelector,
    renderWindow,
  });
  await interactor.interactorStyle.setCurrentStyleToTrackballCamera();

  // Create camera widget
  const cameraOrientation = vtk.vtkCameraOrientationWidget({ interactor, parentRenderer: renderer });
  cameraOrientation.enabled = true;

  // Display the scalar bar at the bottom with a horizontal orientation
  const scalarBarActor = vtk.vtkScalarBarActor({ 
    lookupTable: lut,
    title: "Scalars",
    titleTextProperty: textProperty,
    labelTextProperty: textProperty,
    annotationTextProperty: textProperty,
    unconstrainedFontSize: true,
  });
  const scalarBar = vtk.vtkScalarBarWidget({ scalarBarActor, interactor, defaultRenderer: renderer });
  const scalarBarRepresentation = await scalarBar.getRepresentation();
  await scalarBarRepresentation.setOrientation(0); // 1: vertical, 0: horizontal
  const lowerLeftPosition = await scalarBarRepresentation.getPositionCoordinate();
  await lowerLeftPosition.setValue([0.1, 0.05]);
  scalarBar.enabled = true;

  // Trigger render and start interactor
  await interactor.start();
}

Full Screen Viewer

Defer WASM loading with annotation

Here we tag the script to autoload WASM directly from the VTK repository's package registry; the VTK namespace is then reached by awaiting vtkwasm.ready. You can customize the wasm architecture and version by changing the data-url.

html
<html>
  <head>
    <script
      src="https://unpkg.com/@kitware/vtk-wasm/vtk-umd.js"
      id="vtk-wasm"
      data-url="https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-wasm32-emscripten/9.6.20260228/vtk-9.6.20260228-wasm32-emscripten.tar.gz"
    ></script>
    <script src="example.js"></script>
  </head>
  <body>
    <canvas id="vtk-wasm-window" tabindex="-1" onclick="focus()"></canvas>
    <script>
      vtkwasm.ready.then((vtk) => {
        buildWASMScene(vtk,"This scene points the data-url in script tag to the VTK.wasm bundle from GitLab registry");
      });
    </script>
  </body>
</html>
js
async function buildWASMScene(vtk, titleText = "Sample VTK.wasm scene") {

  function createSharedTextProperty() {
    const textProperty = vtk.vtkTextProperty({fontSize: 22});
    return textProperty;
  }

  async function createLookupTable(scalarRange) {
    const lut = vtk.vtkColorTransferFunction();
    await lut.setColorSpaceToHSV();
    const colorSeries = vtk.vtkColorSeries({ colorScheme: 16 });
    const numColors = await colorSeries.getNumberOfColors();
    const scalarDiff = (scalarRange[1] - scalarRange[0]) / numColors;
    for (let i = 0; i < numColors; i++) {
      const color = await colorSeries.getColor(i);
      const t = scalarRange[0] + i * scalarDiff;
      lut.addRGBPoint(
        t,
        color[0] / 255,
        color[1] / 255,
        color[2] / 255,
      );
    }
    await lut.build();
    return lut;
  }

  async function createTitleTextActor(titleText, textProperty) {
    const textActor = vtk.vtkTextActor({ input: titleText, textProperty });
    const position = await textActor.getPositionCoordinate();
    await position.setCoordinateSystemToNormalizedViewport();
    return textActor;
  }

  // Create a VTK source. Output has a point data array named "Scalars" whose range is [0, PI].
  const shapes = vtk.vtkPartitionedDataSetCollectionSource({ numberOfShapes: 2 });
  const lut = await createLookupTable([0.0, Math.PI]);

  const mapper = vtk.vtkCompositePolyDataMapper({ lookupTable: lut });
  await mapper.setInputConnection(await shapes.getOutputPort());
  const actor = vtk.vtkActor({ mapper, scale: [0.1, 0.1, 0.1] });
  actor.property.edgeVisibility = true;
  actor.property.edgeColor = [0.2, 0.2, 0.2];

  const textProperty = createSharedTextProperty();

  // Create an actor that displays the title.
  const titleTextActor = await createTitleTextActor(titleText, textProperty);

  // Setup rendering part
  const renderer = vtk.vtkRenderer({ background: [0.384314, 0.364706, 0.352941] });
  await renderer.addActor(actor);
  await renderer.addActor(titleTextActor);
  await renderer.resetCamera();

  // Create a RenderWindow and bind it to a canvas in the DOM
  const canvasSelector = "#vtk-wasm-window";
  const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
  await renderWindow.addRenderer(renderer);
  const interactor = vtk.vtkRenderWindowInteractor({
    canvasSelector,
    renderWindow,
  });
  await interactor.interactorStyle.setCurrentStyleToTrackballCamera();

  // Create camera widget
  const cameraOrientation = vtk.vtkCameraOrientationWidget({ interactor, parentRenderer: renderer });
  cameraOrientation.enabled = true;

  // Display the scalar bar at the bottom with a horizontal orientation
  const scalarBarActor = vtk.vtkScalarBarActor({ 
    lookupTable: lut,
    title: "Scalars",
    titleTextProperty: textProperty,
    labelTextProperty: textProperty,
    annotationTextProperty: textProperty,
    unconstrainedFontSize: true,
  });
  const scalarBar = vtk.vtkScalarBarWidget({ scalarBarActor, interactor, defaultRenderer: renderer });
  const scalarBarRepresentation = await scalarBar.getRepresentation();
  await scalarBarRepresentation.setOrientation(0); // 1: vertical, 0: horizontal
  const lowerLeftPosition = await scalarBarRepresentation.getPositionCoordinate();
  await lowerLeftPosition.setValue([0.1, 0.05]);
  scalarBar.enabled = true;

  // Trigger render and start interactor
  await interactor.start();
}

Full Screen Viewer

The data-config attribute on the annotation <script> accepts the same settings as the options object passed to loadAsync(...) — for example, add data-config='{"rendering": "webgpu"}' to switch the rendering backend. See Loading VTK.wasm for what each option does, or the loadAsync reference for the exact option types.

Bundler

Modern web development relies on a package manager to bring in project dependencies. This section covers how published releases are used within a JavaScript project.

Project setup

In this simple example we use Vite with Vanilla JavaScript. The full code is available for reference here. Use a concrete version, or "latest", for the @kitware/vtk-wasm package. Here, the example uses a relative path to the vtk-wasm project root so the in-repo documentation stays relevant.

json
{
  "name": "modern-app",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "vite": "^6.3.5"
  },
  "dependencies": {
    "@kitware/vtk-wasm": "file:../../.."
  }
}
html
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Standalone VTK.wasm example</title>
</head>

<body>
    <div id="app">
        <canvas tabindex="-1" onclick="focus()"></canvas>
    </div>
    <script type="module" src="/src/main.js"></script>
</body>

</html>
js
import "./style.css";
import { loadAsync } from "@kitware/vtk-wasm";
import { buildWASMScene } from "./example";

const runtime = await loadAsync({
  url: "https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-wasm32-emscripten/9.5.20251215/vtk-9.5.20251215-wasm32-emscripten.tar.gz",
});
const session = runtime.createStandaloneSession();
buildWASMScene(session.vtk, "#app > canvas", "This scene passes the VTK.wasm bundle from GitLab registry to loadAsync()");
js
export async function buildWASMScene(vtk, canvasSelector = "#vtk-wasm-window", titleText = "Sample VTK.wasm scene") {

  function createSharedTextProperty() {
    const textProperty = vtk.vtkTextProperty({ fontSize: 22 });
    return textProperty;
  }

  async function createLookupTable(scalarRange) {
    const lut = vtk.vtkColorTransferFunction();
    await lut.setColorSpaceToHSV();
    const colorSeries = vtk.vtkColorSeries({ colorScheme: 16 });
    const numColors = await colorSeries.getNumberOfColors();
    const scalarDiff = (scalarRange[1] - scalarRange[0]) / numColors;
    for (let i = 0; i < numColors; i++) {
      const color = await colorSeries.getColor(i);
      const t = scalarRange[0] + i * scalarDiff;
      lut.addRGBPoint(
        t,
        color[0] / 255,
        color[1] / 255,
        color[2] / 255,
      );
    }
    await lut.build();
    return lut;
  }

  async function createTitleTextActor(titleText, textProperty) {
    const textActor = vtk.vtkTextActor({ input: titleText, textProperty });
    const position = await textActor.getPositionCoordinate();
    await position.setCoordinateSystemToNormalizedViewport();
    return textActor;
  }

  // Create a VTK source. Output has a point data array named "Scalars" whose range is [0, PI].
  const shapes = vtk.vtkPartitionedDataSetCollectionSource({ numberOfShapes: 2 });
  const lut = await createLookupTable([0.0, Math.PI]);

  const mapper = vtk.vtkCompositePolyDataMapper({ lookupTable: lut });
  await mapper.setInputConnection(await shapes.getOutputPort());
  const actor = vtk.vtkActor({ mapper, scale: [0.1, 0.1, 0.1] });
  actor.property.edgeVisibility = true;
  actor.property.edgeColor = [0.2, 0.2, 0.2];

  const textProperty = createSharedTextProperty();

  // Create an actor that displays the title.
  const titleTextActor = await createTitleTextActor(titleText, textProperty);

  // Setup rendering part
  const renderer = vtk.vtkRenderer({ background: [0.384314, 0.364706, 0.352941] });
  await renderer.addActor(actor);
  await renderer.addActor(titleTextActor);
  await renderer.resetCamera();

  // Create a RenderWindow and bind it to a canvas in the DOM
  const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
  await renderWindow.addRenderer(renderer);
  const interactor = vtk.vtkRenderWindowInteractor({
    canvasSelector,
    renderWindow,
  });
  await interactor.interactorStyle.setCurrentStyleToTrackballCamera();

  // Create camera widget
  const cameraOrientation = vtk.vtkCameraOrientationWidget({ interactor, parentRenderer: renderer });
  cameraOrientation.enabled = true;

  // Display the scalar bar at the bottom with a horizontal orientation
  const scalarBarActor = vtk.vtkScalarBarActor({
    lookupTable: lut,
    title: "Scalars",
    titleTextProperty: textProperty,
    labelTextProperty: textProperty,
    annotationTextProperty: textProperty,
    unconstrainedFontSize: true,
  });
  const scalarBar = vtk.vtkScalarBarWidget({ scalarBarActor, interactor, defaultRenderer: renderer });
  const scalarBarRepresentation = await scalarBar.getRepresentation();
  await scalarBarRepresentation.setOrientation(0); // 1: vertical, 0: horizontal
  const lowerLeftPosition = await scalarBarRepresentation.getPositionCoordinate();
  await lowerLeftPosition.setValue([0.1, 0.05]);
  scalarBar.enabled = true;

  // Trigger render and start interactor
  await interactor.start();
}
css
:root {
  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  margin: 0;
  height: 100vh;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
}
bash
npm install
npm run build

Here, the VTK.wasm bundle is downloaded in the browser directly from the GitLab package registry. See the src/main.js file for the relevant code.

Result

Full Screen Viewer