Leva, but for vanilla JavaScript (leva-vanilla)

I’ve been using Leva a lot for debug UIs, but kept hitting the same limitation: it’s tied to React.

So I built leva-vanilla — a lightweight control panel with a Leva-like experience, but for plain JavaScript (and any framework).

What it does

  • No React, no dependencies

  • Works with Three.js, WebGL, or any JS app

  • Familiar API (dat.gui / lil-gui)

  • Nested folders, sliders, selects, colors, etc.

Why I made it

I wanted something as clean as Leva, but usable in:

  • vanilla JS projects

  • quick prototypes

  • non-React environments

Repository

https://github.com/Alkebsi/leva-vanilla

Live Demo

https://alkebsi.github.io/leva-vanilla


I’m especially looking for feedback on:

  • API design (does it feel intuitive?)

  • Missing controls you’d actually need

  • Any friction in real usage

Even quick impressions would help a lot.

4 Likes

Cool, I created a demo using it with the classic Dat.GUI Fizzy Text example.

3 Likes

One of the features I use often with lil-gui is to change values with the wheel of the mouse - i.e. I click inside the numerical textbox, and with the wheel scrolling and I change the value in small steps (depending on the .step()) and whith shift+scroll I change the value in large steps. I use this when the range is big and I need to make just small change, that is smaller than one pixel of the horizontal slider.

A Kitchen sink demo would be nice.

2 Likes

That’s awesome! Really cool to see it used so quickly. Your demo is really good.

Would you be okay if I linked your demo in the README?

Great point! I hadn’t implemented scroll-based input, but I can see how useful it is for fine control, especially on large ranges.

I’ll work on adding it and update you here once it’s ready. Really appreciate the feedback!

1 Like

Thats fine

1 Like

Just added scroll-based input. Thanks again for the suggestion.

I went with:

  • scroll to adjust values (even number inputs, not just sliders)

  • shift → 10× faster

  • alt/option → 0.1× slower

It also allows fine control beyond the defined .step() for precision, while still respecting min/max.

Curious if this feels natural in practice, or if you’d expect it to strictly follow .step() like lil-gui.

The live demo is already updated: leva-vanilla

2 Likes

Scrolling feels OK from my point of view. The options for larger and smaller steps are useful. There is a light inconsistency - changing values with up/down keyboard arrows does not change the step size ( ↑↓ for one step, Shift-↑↓ for 10×step and Alt-↑↓ for 0.1×steps).

My next question is about customization. Usually I use lil-gui, but decorate and resize its appearance like this. The current leva-vanilla listbox options are somewhat too dark – see “Medium”:

And also, leva-vanilla could be shortened to levanilla (not to be confused with Le Vanilla)

2 Likes

Thanks for the detailed feedback, really helpful.

Good catch on the keyboard inconsistency, I’ll align it with the scroll behavior.

And yeah, the dropdown issue is coming from the native <select>. I’m planning to replace it with a custom one instead of relying on browser styles.

I’ll work on these and update you here once ready.

I’ve aligned keyboard input with scroll (shift/alt now behave the same), and replaced the native <select> with a custom dropdown to avoid browser styling issues.

I’m also working on a theming system similar to what Leva provides, so styling can be customized more easily.

Next step is improving keyboard navigation for the dropdown.

Would love to hear how this feels if you get a chance to try it again. Thanks a lot for your continuous feedback! Much appreciated

1 Like

Heads up: I’ll be introducing a breaking API change after v0.0.6.

leva-vanilla started as a lightweight lil-gui-style tool, but that direction limits what it can become. I’m reworking the API toward a schema-based, reactive model inspired by Leva.

The goal is better DX, not more features.

Instead of this:

const gui = new GUI()
gui.add(params, 'speed', 0, 10)

You’ll be able to do:

const controls = leva({
  positionX: { value: 1, min: 0, max: 10 },
  color: '#ff0000'
})

leva.effect(() => {
  element.style.background = controls.color
  mesh.position.x = controls.positionX
})

No manual wiring, no .onChange, just state.

This is a breaking change, but it’s happening early (pre v0.1.0) so the project can grow on a stronger foundation.

I also plan to build a React wrapper so it aligns closely with Leva’s useControls.

If you’ve tried the current API or have opinions about this direction, I’d really appreciate your feedback before I lock it in.

I’m especially interested in input from people who have worked on similar systems (reactivity, control panels, or creative coding tools), including contributors in the Three.js / Poimandres ecosystem.

More details coming soon.

Finally, after 2 weeks, v0.1.0 is ready.

A small reactive system with a the beautiful Leva GUI, purely built for vanilla JavaScript.

import { leva } from './core/leva';

const box = document.createElement('div');

const controls = leva({
  width: { value: 10, min: 10, max: 160, step: 10 },
  height: { value: 10, min: 10, max: 160, step: 10 },
  color: '#ff0000ff',
});

controls.effect(() => {
  box.style.width = `${controls.width}px`;
  box.style.height = `${controls.height}px`;
  box.style.backgroundColor = controls.color;
});

setTimeout(() => {
  controls.width = 60;
  controls.height = 60;
  controls.color = '#777';
}, 1000);

document.body.append(box);
  • Reactive controls
  • Optional GUI (gui: false)
  • Nested folders
  • Type-safe schema inference
  • Lightweight reactive core

You can learn more about it or contribute from the GitHub repository below. Any ideas or tips are much appreciated.

Repo: GitHub - Alkebsi/leva-vanilla: Leva GUI, but for Vanilla JS · GitHub
Live Demo: leva-vanilla

LeVanilla.

1 Like