SlopScupper
00 crowd

bevy-react

A library for rendering bevy_ui elements inside Bevy app using React
Open repo on GitHubgithub.com/tulustul/bevy-react
Rust · ★ 25 · 1 forks · Apache-2.0 · paperwork by the Cap'mmostly ai (inferred)light human (inferred)works-on-my-machine (inferred)other
listed 9 hours ago by tulustul · last checked 1 hour ago
The owner didn't write this. This repo never submitted itself. The Cap'm found it on a truffle trawl and wrote its paperwork from what GitHub already shows. Picked by hand by the Cap'm on 2026-09-11: A Rust library for building Bevy game UIs with React components, which its owner calls a "quick, vibecoded proof of concept". 25 stars; Apache-2.0 license. The owner did not submit this. Votes count; awards don't until the owner claims it.

I'm not calling your project slop! Geeze, it's a joke... Do you own this repo?

Log in with GitHub as tulustul. There's no account to make: SlopScupper only asks GitHub who you are (read:user), never sees your code, and keeps just your id, login and avatar. Then you can:

  • Keep it, on your terms. Commit your own slopscore.md (spec) and press Refresh. Your paperwork replaces the Cap'm's, and you can submit it for Slop of the Day.
  • Take it down. One click on Remove. It stays gone; the trawl never brings it back.

Log in with GitHub

Can't log in as the owner? Request a takedown. No login needed, and a trawled listing comes down right away.

GitHub says
A library for rendering bevy_ui elements inside Bevy app using React
created
2026-06-22 · pushed 4 days ago · 210 commits · 1 contributor
languages
Rust 90%TypeScript 7%WGSL 2%JavaScript 1%
paperwork
licensereadme 42% health
dependencies
no dependency graph (no manifest, or disabled) · OSV.dev, checked 9 hours ago

Disclosures, inferred by the Cap'm

slopbucket
vibe-coded
category
other
ai_generated
mostly
human_touch
light
status
works-on-my-machine
language (detected)
javascriptrusttypescriptwgsl
license (detected)
apache-2.0

The Cap'm's log

The Cap'm wrote this paperwork, not the owner. This repo never submitted itself to SlopScore. The Cap'm picked it by hand: A Rust library for building Bevy game UIs with React components, which its owner calls a "quick, vibecoded proof of concept". It carries the Apache-2.0 license. The disclosures above are his best guess from what GitHub shows.

Is this yours? Commit a real slopscore.md and press Refresh to replace this, or remove the listing in one click. There's no account to make: you log in with GitHub.

README — the repo's own words, folded up so the grading fits on one screen

bevy-react logo

bevy-react

CI crates.io npm docs.rs bevy 0.19 license: MIT OR Apache-2.0

Build bevy_ui interfaces with React. You write components in React/TSX and they render to native Bevy UI through a React Native-style bridge - no web view, no DOM. The JS side stays purely declarative; Rust and Bevy do the heavy lifting. State and interactions flow both ways between your Bevy app and React, and edits hot-reload live while keeping component state.

You can play with a live demo here:

https://tulustul.github.io/bevy-react/

The bevy-react demos app: a React-driven left-nav over a live 3D Bevy scene, with a world-tracking "Bounces" panel anchored above a bouncing ball.

import { mount } from "bevy-react";
import { useState } from "react";

function App() {
  const [n, setN] = useState(0);
  return (
    <node style={{ padding: 20, gap: 12, flexDirection: "column" }}>
      <text>{`Count: ${n}`}</text>
      <button
        onClick={() => setN((c) => c + 1)}
        style={{ backgroundColor: "#7aa2f7" }}
      >
        <text>+</text>
      </button>
    </node>
  );
}

mount(<App />);

That's a real component - <node> and <button> render to actual bevy_ui nodes, useState works as you'd expect, and saving the file updates the running app without losing the count.

Why bevy-react

  • React, not a bespoke UI DSL. Hooks, components, conditional rendering, lists - everything you already know.
  • Native Bevy UI. No web view, no DOM. Your UI is bevy_ui entities in the same world as your game.
  • Hot reload that keeps state. Edit a component and it re-renders live with hook state and running animations intact.
  • Typed, two-way messaging. React and the ECS talk over typed channels generated straight from your Rust types.

How it works

bevy-react uses a bridge architecture, much like old versions of React Native - but the native side is Bevy and the ECS instead of iOS/Android views.

  • React runs on embedded V8. On native targets the JS runs in a V8 isolate via deno_core - no Node, no browser - on its own thread, off the game loop.
  • Web builds work too. On wasm the same bundle runs in the browser's own JS engine instead of V8; the UI is still bevy_ui, not DOM. The live demo is the web build.
  • JS only describes the UI. React renders through a custom reconciler that emits declarative UI-mutation ops; Rust applies them to bevy_ui entities. All the heavy lifting - layout, input, rendering - happens in Rust and Bevy.
  • Animations are orchestrated in Bevy, not JS. Shared values and transitions are driven on the Bevy side every frame; JS just declares the target. No per-frame JS, no bridge traffic per tick.

Project status

Currently, the project is a quick, vibecoded proof of concept demonstrating the idea. The API is very unstable and will change, the code quality is not satisfying. Do not use it in production.

Bevy compatibility

bevy bevy-react
0.19 0.1

Getting started

cargo add bevy-react

Scaffold the React UI:

npx bevy-react init ui
cd ui && npm run watch

Add the plugin to your app

use bevy_react::{ReactUiPlugin};

app.add_plugins(ReactUiPlugin::new("ui/dist/app.js")

Follow the examples/minimal example for a full working setup.

Typescript client generation

Copy the --export-bindings flag implementation from examples/minimal

After that you can run

npm run bevy:generate

or

cargo run -- --export-bindings ui/src/bevy.ts

which will generate a bevy.ts file in your ui directory. This file will include all the needed integration with your Rust code. See Talking to Bevy for details.

Rembember to regenerate the client each time you update the communication channel in Rust.

The demos app

examples/demos is a gallery that exercises most features available. It's the best reference implementation - each demo is a small, self-contained component you can read and copy when wiring up your own UI, messaging, or animations.

npm install
npm run build -w demos
cargo run --example demos

Features

Elements & styling

Host elements <node>, <button>, <text>, <image>, <editableText>, <canvas>, <svg>, <portal>, and <surface> cover layout, input, drawing, vector graphics, embedded 3D views, and UI rendered onto 3D meshes. Style them with a flexbox/grid object (colors, spacing, borders, radius, shadows, transforms).

<node
  style={{
    flexDirection: "row",
    gap: 16,
    padding: 20,
    backgroundColor: "#1e1e2e",
    borderRadius: 8,
  }}
>
  <text style={{ fontSize: 18, color: "#cdd6f4" }}>Hello</text>
</node>

Hover & press states

Overlay extra style while an element is hovered or pressed - no state wiring needed.

<button
  onClick={() => save()}
  style={{ backgroundColor: "#7aa2f7" }}
  hoverStyle={{ backgroundColor: "#89b4fa" }}
  pressStyle={{ backgroundColor: "#5a7fd6" }}
>
  <text>Save</text>
</button>

Pointer & drag

onPointerDown / onPointerMove / onPointerUp give you drag gestures, with both element-normalized (x, y) and window (clientX, clientY) coordinates.

<node
  onPointerDown={(e) => start(e.clientX, e.clientY)}
  onPointerMove={(e) => drag(e.clientX, e.clientY)}
  onPointerUp={() => drop()}
/>

Transitions

Ease changes to a style by listing which properties should animate, with timing or spring config.

<button
  onClick={() => setOn((v) => !v)}
  style={{
    backgroundColor: on ? "#a6e3a1" : "#45475a",
    borderRadius: on ? 24 : 6,
    transform: { translateX: on ? 36 : -36 },
    transition: {
      transform: { stiffness: 180, damping: 14 }, // spring
      backgroundColor: { duration: 200 }, // timing (ms)
      borderRadius: { duration: 200 }, // per corner
    },
  }}
>
  <text>{on ? "ON" : "OFF"}</text>
</button>

Layout animations

transition: { layout } eases a node to wherever layout puts it next — whatever moved it: a reorder, a sibling growing or leaving, a parent resize, a flipped flex knob. The real layout snaps; the box glides from its old rect to the new one (FLIP), children ride along, and clicks land on the visual. Nothing to measure, no transforms to write.

<node style={{ flexDirection, justifyContent, alignItems }}>
  {swatches.map((g, i) => (
    <node
      key={i}
      style={{
        width: 40,
        height: 40,
        backgroundGradient: g,
        transition: { layout: { duration: 350, easing: "easeInOut" } },
      }}
    />
  ))}
</node>

Four gradient swatches in a flex container easing to their new slots as direction, justify, and align are flipped from the controls below.

A size change eases the node's own box (its children stay crisp), but whatever is laid out around it snaps — a container that must re-flow its neighbours uses the real-layout transition: { size } instead, with layout on the children. Demos: "Flexbox", "Style transitions".

Shared elements

Give two nodes that swap in one commit the same sharedTag — a thumbnail in a grid and the hero of the detail screen that replaces it — and the incoming node starts where the outgoing one visually was (position, size, background color, opacity, transforms, filters, gradients) and eases to its own layout and style. React has no reparenting, so a "move" between parents is always an unmount plus a mount: the tag is the identity, and the commit itself is the trigger — no hooks, no measuring, no start call.

// grid
<image src={item.thumb} sharedTag={`hero-${item.id}`} style={{ width: 72, height: 72 }} />

// detail, mounted in the commit that unmounts the grid
<image
  src={item.full}
  sharedTag={`hero-${item.id}`}
  style={{
    width: 200,
    height: 200,
    transition: { sharedElement: { duration: 450, easing: "easeInOut" } },
  }}
/>

transition: { sharedElement } on the incoming node is the one timing for every seeded channel (required — a tag without it pairs but snaps). Pairs need the same tag, element type and UI root; the first mounted match seeds every incoming node with the tag. Size flies in measured pixels through real layout (children stay crisp, the parent re-flows), position by translation; the outgoing node unmounts instantly. Demo: "Shared elements".

A thumbnail grid opening into a detail screen: the tapped image flies from its round thumbnail to the large hero while the rest of the screen fades and scales.

Shared elements and layout animations compose. On the tickets board below a clicked ticket unmounts from one column and mounts in the other in the same commit: it carries sharedTag plus both transitions, so it takes off from where it sat, easing its width and color to the new column's on the way, while the siblings it leaves behind close the gap with their own layout transition.

<button
  sharedTag={`item-${id}`}
  onClick={() => move(id)}
  style={{
    backgroundColor: color[side],
    transition: {
      sharedElement: { duration: 400, easing: "easeOut" },
      layout: { duration: 400, easing: "easeOut" },
    },
  }}
/>

A two-column &quot;To do / Done&quot; tickets board: a clicked ticket flies to the other column and the remaining tickets slide up to close the gap.

Animations

For richer motion, use Reanimated-style shared values driven on the Bevy side (no per-frame JS). Create a value with useSharedValue, assign it a driver, and bind it inline in style with the { animated: … } wrapper — on any plain element, in any animatable position (opacity, colors, layout lengths, transform channels, transform3d fields, filter params).

import { useSharedValue, withRepeat, withTiming } from "bevy-react";
import { useEffect } from "react";

function Pulse() {
  const opacity = useSharedValue(1);
  useEffect(()  Read the rest on GitHub

Scan report · 2026-09-11
  • Prohibited terms or links
  • Repository eligibility
  • slopscore.md paperwork
  • Content policy
  • Risk review

0 comments

log in to comment.

report this listinglog in to report