Panels and state

Connect Vue state, organize controls into folders, and render several panels.

Dark mode

Each panel follows the nearest dark or light class on its ancestors, and falls back to <html>. Most color mode tools (for example Nuxt's @nuxtjs/color-mode or VueUse's useDark) set that class on <html>, so the panel follows your app with no setup.

To give one panel its own theme, wrap it in an element with the class:

<div class="dark">
  <TresLeches />
</div>

Drag the handle to compare both themes:

Existing refs

Pass a ref directly to keep the panel and the rest of your application on the same source of truth.

const intensity = ref(2)

useControls({ intensity })

watchEffect(() => {
  console.log(intensity.value)
})

You can also use a ref as the value in a configuration object:

useControls({
  intensity: {
    value: intensity,
    min: 0,
    max: 10,
    step: 0.1,
  },
})

Reactive objects

Passing a reactive object connects each top-level property to a control.

const settings = reactive({
  enabled: true,
  exposure: 1,
  background: '#111827',
})

useControls(settings)

Edits from the panel update settings directly.

Folders

Use the folder overload to put related controls in a collapsible folder.

const camera = useControls('Camera', {
  fov: { value: 45, min: 10, max: 120, step: 1 },
  near: { value: 0.1, min: 0.01, max: 10, step: 0.01 },
  far: { value: 1000, min: 10, max: 5000, step: 10 },
})

Folder names are included in the returned keys to keep them unique. In this example, the refs are CameraFov, CameraNear, and CameraFar.

Panel actions

An expanded panel has search and copy actions in its top-left corner. Search for a control key, its displayed label, or a folder name. A matching control keeps its parent folder visible and temporarily opens it; a matching folder keeps all of its controls visible.

Click the search button to expand the Search stuff input. Click it again, or press Escape, to close the input and clear the query.

The copy button's Copy values for AI tooltip describes its purpose. It writes every current panel value to the clipboard as formatted JSON, even when a search is active. Controls outside a folder are written at the root, named folders become nested objects, and each property uses its stable control key rather than its displayed label. Button and graph controls are omitted because they do not hold editable values.

const fov = ref(45)

useControls({
  exposure: { value: 1, label: 'Exposure' },
}, { uuid: 'panel-actions' })

useControls('Camera', {
  fov: { value: fov, label: 'Field of view' },
  enabled: { value: true, label: 'Enabled' },
}, { uuid: 'panel-actions' })

For these controls, the copied JSON has this shape:

{
  "exposure": 1,
  "Camera": {
    "CameraFov": 45,
    "CameraEnabled": true
  }
}

Try searching for camera or exposure, then use the copy action:

The actions appear only while the panel is expanded. Their staggered entrance follows the panel expansion and respects the operating system's reduced-motion preference.

Independent panels

The default panel id is default. Pass the same panel id to useControls and TresLeches (through the uuid option and prop) when you need more than one panel.

<script setup lang="ts">
import { TresLeches, useControls } from '@tresjs/leches'

useControls({ exposure: 1 }, { uuid: 'lighting' })
useControls('Camera', { fov: 45 }, { uuid: 'camera' })
</script>

<template>
  <TresLeches uuid="lighting" />
  <TresLeches uuid="camera" />
</template>

A panel disposes the controls registered to its panel id when it unmounts.

Panel presentation

<TresLeches /> floats by default. Set float to false to place it in normal document flow.

<TresLeches :float="false" />

The collapsed prop sets the initial collapsed state of a floating panel:

<TresLeches collapsed />

You can place custom content after generated controls with the default slot:

<TresLeches>
  <small>Changes are applied immediately.</small>
</TresLeches>