Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started

This guide takes you from an empty Bevy app to a running superui interface authored in .tsx. It follows the counter example step by step — the smallest complete superui app.

Prerequisites: a Bevy app

superui is a Bevy plugin, so you need a working Bevy application first. If you have not yet setup Bevy app firstly check Bevy’s own Getting Started guide and come back once cargo run opens a window.

From here on we assume you have a binary crate that builds and runs a Bevy app.

Add the dependencies

superui is on crates.io. Add it to your existing Bevy project (your bevy dependency is already in place from the prerequisite step):

[dependencies]
superui = "0.4"

[build-dependencies]
supersolid = "0.4"

Pick the superui version that matches your Bevy version:

superuibevy
0.40.20
0.30.19
0.20.18
0.10.17

Pre-transpile your .tsx

Add a build.rs next to your Cargo.toml. It calls the supersolid build dependency to transpile your .tsx to JS:

fn main() {
    supersolid::build::transpile_dir("assets/ui/counter");
}

The path is the UI directory you create in Author the UI. Only native hot reload (--features hmr, set up below) transpiles .tsx live; every other build — including a plain cargo run and any web build — loads this generated JS, so without the build script the UI renders nothing.

The output lands in assets/ui/counter/.superui/build/. Keep it out of version control:

**/.superui/

Build modes and hot reload are covered in Project Structure & Build.

Set up editor support

For autocomplete, hover docs, and type-checking in your .tsx files you will need type definitions setup.

This can be installed automatically with cargo-superui, which you need to install first:

cargo install cargo-superui # this only needs to be done once per machine

Then run the install command in your project:

cargo superui install

This drops the supersolid type declarations and a tsconfig.json into your project (in a gitignored superui_modules/ folder) so your editor understands the authoring API. It’s optional — the build doesn’t need it — but it makes writing components far nicer. Details in Project Structure & Build.

Author the UI

A superui interface is a small set of web-like files in a directory under your Bevy assets/ folder. The path is up to you — the examples group them under assets/ui/<name>/, so we’ll create three files under assets/ui/counter/.

index.html is the entry point — it links the stylesheet and the component module, exactly like a web page:

<html>
  <head>
    <link rel="stylesheet" href="style.css">
    <script type="module" src="app.tsx"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

app.tsx is your component. A component is a plain function that returns markup; state lives in a signal (createSignal), and reading that signal inside the markup makes the UI update when it changes:

import { createSignal, render } from "supersolid";

function Counter() {
  const [count, setCount] = createSignal(0);
  return (
    <button class="counter" onClick={() => setCount(count() + 1)}>
      clicked {count()} times
    </button>
  );
}

render(() => <Counter />, document.getElementById("root"));

style.css styles it with familiar CSS (see the CSS reference for what’s supported):

#root {
  width: 100%;
  height: 100%;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.counter {
  font-size: 14px;
  color: #04120f;
  background-image: linear-gradient(135deg, #4ff0e0, #2bd0c0);
  border-width: 0;
  padding: 12px 20px;
  border-radius: 7px;
}

Splitting a UI across files. Besides from "supersolid", your app.tsx can import other local modules: relative .tsx/.ts/.js files (import {MyComponent} from "./my_component") and .json data (inlined as a const binding — see Importing JSON). superui resolves and bundles the whole import graph, so splitting one UI’s components across sibling files — one module per screen, shared widgets in their own file — works and hot-reloads. See Components & JSX for the supported import forms and their limits.

Mount it

Add SuperUiPlugin, then spawn a SuperUiRoot pointing at your UI directory. SuperUiRoot::from_asset_dir loads <dir>/index.html and everything it references, and bundles a full-viewport root node for you:

use bevy::prelude::*;
use superui::prelude::{SuperUiPlugin, SuperUiRoot};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(SuperUiPlugin)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands, assets: Res<AssetServer>) {
    commands.spawn(Camera2d);
    commands.spawn(SuperUiRoot::from_asset_dir("ui/counter", &assets));
}

That’s the whole integration: one plugin and one spawn. The path is relative to your assets/ directory, so "ui/counter" resolves to assets/ui/counter/index.html.

Run it

cargo run

A window opens with a button that counts your clicks. To edit the UI and see changes without restarting, turn on hot reload:

cargo run --features hmr

Now editing app.tsx or style.css updates the running window in place — and the counter keeps its value across the reload. Enabling this feature is covered next.

Where to go next

PROJECTSUPERUI · BEVY UI
DRAWN BYSTROWK
SCALENOT TO SCALE
REVR1 · 0.1 EARLY BUILD
LICENCEMIT / APACHE-2.0