Getting started
source → states → view
Connect your state source to Web Components, React, or headless consumers.
Your source owns state and behavior.
Use states to derive values for the view and commands to send actions to the source.
Install v3 beta
Section titled “Install v3 beta”For ignite-element@latest (v2.2.2), see the stable v2 documentation.
Use an existing TypeScript/JSX web project, or start a new one below.
Set up a new project
In an empty directory, with Node 22.12+ and pnpm installed, run:
pnpm initpnpm add -D vite typescriptVite builds and serves the example locally.
Ignite also works with other TypeScript/JSX web tooling.
Install Ignite and XState:
pnpm add ignite-element@beta xstateBuild a component
Section titled “Build a component”This light switch is a Web Component.
Try it: each flip changes the bulb and increments the count.
Download the complete example, or read the three steps below.
The download contains the same component, styles, and HTML shown below.
Unzip it, then run pnpm install and pnpm dev from its directory.
1. Create the component
Section titled “1. Create the component”Create src/light-switch.tsx.
The first line selects Ignite JSX for this file.
/** @jsxImportSource ignite-element/jsx */import { igniteCore } from "ignite-element/xstate";import { assign, createMachine } from "xstate";
const toggleMachine = createMachine( { context: { count: 0 }, initial: "off", states: { off: { on: { FLIP: { target: "on", actions: "countFlip" } } }, on: { on: { FLIP: { target: "off", actions: "countFlip" } } }, }, }, { actions: { countFlip: assign({ count: ({ context }) => context.count + 1 }), }, },);
export const core = igniteCore({ source: toggleMachine, states: (snapshot) => ({ isOn: snapshot.matches("on"), label: snapshot.matches("on") ? "On" : "Off", count: snapshot.context.count, }), commands: ({ source }) => ({ toggle: () => source.send({ type: "FLIP" }), }),});
core("ignite-light-switch", (ctx) => ( <section class="light-switch" data-state={ctx.label}> <link rel="stylesheet" href={new URL("./light-switch.css", import.meta.url).href} /> <svg class="bulb" viewBox="0 0 64 80" aria-hidden="true"> <path d="M22 56C22 46 10 44 10 28a22 22 0 0 1 44 0c0 16-12 18-12 28Z" /> <path d="M23 64h18M26 72h12" /> </svg> <p class="state">{ctx.label}</p> <p class="count">Toggled: {ctx.count}</p> <button type="button" role="switch" aria-label="Light" aria-checked={String(ctx.isOn)} onClick={() => ctx.toggle()} > <span class="track" aria-hidden="true"> <span class="thumb" /> </span> Flip </button> </section>));toggleMachine owns the on/off state and flip count.
The states callback exposes isOn, label, and count.
The commands callback exposes toggle().
The view reads these values and calls the command through ctx.
Each <ignite-light-switch> gets its own actor, so its state is independent of other light switches on the page.
2. Add the styles
Section titled “2. Add the styles”Create src/light-switch.css alongside the component.
Its stylesheet loads inside the element’s shadow root, keeping the bulb and switch styles local.
View the stylesheet
:host { display: block; font-family: system-ui, sans-serif; color: inherit;}
.light-switch { display: grid; justify-items: center; gap: 0.75rem; padding: 2rem 1rem;}
.bulb { width: 5rem; height: 6.25rem; fill: none; stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round;}
[data-state="On"] .bulb { fill: #facc15; filter: drop-shadow(0 0 1rem #facc1566);}
.state,.count { margin: 0;}
.state { font-size: 1.5rem; font-weight: 700;}
.count { font-variant-numeric: tabular-nums;}
button { display: flex; align-items: center; gap: 0.75rem; min-height: 2.75rem; margin-top: 0.5rem; padding: 0.5rem 0.75rem; border: 2px solid currentColor; border-radius: 0.5rem; color: inherit; background: transparent; font: inherit; cursor: pointer;}
button:hover { background: color-mix(in srgb, currentColor 10%, transparent);}
button:focus-visible { outline: 3px solid currentColor; outline-offset: 4px;}
.track { display: block; width: 2.5rem; padding: 0.2rem; border: 2px solid currentColor; border-radius: 2rem;}
.thumb { display: block; width: 1rem; height: 1rem; border-radius: 50%; background: currentColor;}
[aria-checked="true"] .thumb { transform: translateX(1.5rem);}
@media (prefers-reduced-motion: no-preference) { .thumb { transition: transform 150ms ease; }}3. Run the example
Section titled “3. Run the example”In an existing app, import src/light-switch.tsx from your entry module and add <ignite-light-switch></ignite-light-switch> to the page.
Run with Vite
If you used the Vite setup above, create index.html in the project root:
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Ignite light switch</title> </head> <body> <ignite-light-switch></ignite-light-switch> <script type="module" src="/src/light-switch.tsx"></script> </body></html>Run this command from the project root, then open the local URL it prints:
pnpm exec viteIgnite manages each element’s private actor.
The core stays available for the page’s lifetime.
See Ownership & cleanup for application teardown.
Configure JSX for the project (optional)
For a project using only Ignite JSX, add this tsconfig.json and remove the first-line pragma from src/light-switch.tsx.
v3 uses native ES modules.
{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "jsx": "react-jsx", "jsxImportSource": "ignite-element/jsx", "strict": true, "noEmit": true }, "include": ["src"]}Where next
Section titled “Where next”- Sources: choose an XState, Redux, or MobX source.
- Views: render with Ignite JSX or React.
- Examples: run a complete application.
- API reference: look up exact contracts.
See Compatibility for supported runtimes and current limitations.