vanilla · no framework

Embed a VSM widget in plain JavaScript

No Vue, no React, no build step, no dependencies. Two ways — pick one. ← back

click the button…

VSM is framework-agnostic. The editor happens to be built with Vue, but the thing you ship is pure DOM + a ~2 KB core. Vue/React bindings are optional conveniences, never required.

Option A — one self-contained file (simplest)

In the editor, click Export JS module → you get widget.js with the SVG, the state machine, and the runtime all inlined (≈16 KB, 0 deps). Then:

  1. Put widget.js next to your page.
  2. Add a container and the snippet below.
<!-- index.html -->
<div id="btn"></div>

<script type="module">
  import mount from './widget.js';

  const m = mount('#btn', { persist: { key: 'btn' } });

  m.on('onPress', () => console.log('pressed!'));   // events from the animation
  document.querySelector('#btn01-path')
          .addEventListener('click', () =>
            m.fire(m.value === 'default' ? 'press' : 'release'));
</script>

Option B — keep files separate

Export SVG + .vsm.json and the small runtime (src/). Use the mount() helper with URLs:

<div id="btn"></div>

<script type="module">
  import { mount } from './src/index.mjs';

  const m = await mount('#btn', {
    svgUrl:  './button.svg',
    specUrl: './button.vsm.json',
    persist: { key: 'btn' },
  });
  m.on('onPress', () => {/* your app reacts */});
</script>
That's the whole API: m.fire('trigger') to drive it · m.on('event', fn) for events back · m.subscribe(s => s.value) for state · m.value = current state. Works with any framework, or none.

Using a framework? There's a Vue example too — same core, one thin wrapper.