Skip to content

Ignite Element

Framework-agnostic custom elements with typed state and events.

Ignite Element gives you a thin, typed layer around your favorite state library so you can ship web components without bringing a framework runtime.

Bundle size Lean deps Tree-shakeable

Terminal window
pnpm add ignite-element xstate
# or npm/yarn equivalents

Add JSX runtime settings for Ignite JSX (the default renderer):

tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "ignite-element/jsx"
}
}
import { igniteCore } from 'ignite-element/xstate';
import machine from './counter-machine';
const component = igniteCore({
source: machine,
});
component('my-counter', ({ state, send }) => {
const { count } = state;
const increment = () => send({ type: 'INCREMENT' });
const decrement = () => send({ type: 'DECREMENT' });
return (
<div class="counter">
<span class="counter-value">{count}</span>
<div class="counter-buttons">
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
</div>
);
});

counter-machine.ts:

import { assign, setup } from 'xstate';
const machine = setup({
types: {
context: {} as { count: number },
events: {} as { type: 'INCREMENT' } | { type: 'DECREMENT' },
},
}).createMachine({
id: 'counter-machine',
context: { count: 0 },
initial: 'active',
states: {
active: {
on: {
INCREMENT: {
actions: assign({
count: ({ context }) => context.count + 1,
}),
},
DECREMENT: {
actions: assign({
count: ({ context }) => context.count - 1,
}),
},
},
},
},
});
export default machine;

Add the element anywhere:

<my-counter></my-counter>

Want to go deeper with XState itself? See the XState docs ↗.

Spin up the counter example in your browser:

  • Works with XState, Redux Toolkit, or MobX—bring your adapter, keep your patterns.
  • Typed commands, states, and events without a React/Solid runtime; you only install the state library and renderer you pick.
  • Renderer choice: Ignite JSX (default, backed by lit-html); choose per project via config.
  • Built-in lifecycle handling for shared vs. isolated adapters.
  • Ship design-safe components: global styles via ignite.config.ts, per-component CSS as needed.
  • Lit / Stencil: Ignite is renderer-agnostic with pluggable adapters; keep your state library instead of adopting a bespoke reactive model.
  • Framework wrappers: No React/Solid runtime required—ship native custom elements with typed events and commands.
  • State-library lock-in: Swap XState/Redux/MobX without rewriting renderers; shared lifecycle handling stays consistent.
  • Tiny runtime surface—Ignite JSX uses a minimal custom JSX renderer (no framework runtime).
  • Works with tree shaking; keep only the adapters/renderers you use.
  • Bundle size check: Bundlephobia for the current npm release.
  • Shared adapters are reference-counted to avoid duplicate subscriptions.