Skip to content
🎉 Angular Three v4 is here! Read the announcement

Introduction

Tweakpane integration for Angular Three, enabling easy creation of debug UI controls for tweaking parameters in your 3D scenes.

No additional peer dependencies required beyond the core dependencies.

Terminal window
npm install angular-three-tweakpane tweakpane
# yarn add angular-three-tweakpane tweakpane
# pnpm add angular-three-tweakpane tweakpane

Make sure to already have angular-three installed

  1. Add tweakpaneAnchor directive to your ngt-canvas
  2. Add <tweakpane-pane> somewhere in your scene
  3. Use tweaks() in any component within the canvas
import { Component, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { NgtCanvas } from 'angular-three/dom';
import { tweaks, TweakpaneAnchor, TweakpanePane } from 'angular-three-tweakpane';
@Component({
template: `
<ngt-mesh [position]="[params.x(), params.y(), params.z()]">
<ngt-box-geometry />
<ngt-mesh-standard-material [color]="params.color()" />
</ngt-mesh>
<tweakpane-pane title="Controls" />
`,
imports: [TweakpanePane],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SceneGraph {
params = tweaks('Position', {
x: { value: 0, min: -5, max: 5 },
y: { value: 0, min: -5, max: 5 },
z: { value: 0, min: -5, max: 5 },
color: { value: '#ff0000', color: true },
});
}
@Component({
template: `
<ngt-canvas tweakpaneAnchor>
<app-scene-graph *canvasContent />
</ngt-canvas>
`,
imports: [NgtCanvas, TweakpaneAnchor, SceneGraph],
})
export class Experience {}

The tweaks() function creates Tweakpane controls declaratively from any component within an ngt-canvas. It provides component-level control with programmatic configuration.

// Primitives (creates new signals internally)
const controls = tweaks('Physics', {
gravity: 9.8,
debug: false,
name: 'World',
});
// Access values as signals
controls.gravity(); // Signal<number>
controls.debug(); // Signal<boolean>

Use config objects for more control over input behavior:

const controls = tweaks('Physics', {
gravity: { value: 9.8, min: 0, max: 20, step: 0.1 },
color: { value: '#ff0000', color: true },
mode: { value: 'normal', options: ['normal', 'debug', 'verbose'] },
});

Pass existing signals for two-way binding:

filteringEnabled = signal(true);
gravity = signal(9.8);
params = tweaks('Settings', {
filteringEnabled: this.filteringEnabled, // two-way binding
gravity: this.gravity,
});

When the control value changes, your original signal is updated. The returned signal is a readonly view of the same value.

Organize controls into collapsible groups using tweaks.folder():

params = tweaks('Settings', {
basic: 42,
position: tweaks.folder('Position', {
x: { value: 0, min: -5, max: 5 },
y: { value: 0, min: -5, max: 5 },
z: { value: 0, min: -5, max: 5 },
}),
material: tweaks.folder('Material', {
color: { value: '#ff0000', color: true },
metalness: { value: 0.5, min: 0, max: 1 },
roughness: { value: 0.5, min: 0, max: 1 },
}),
});
// Access nested values
this.params.position.x(); // Signal<number>
this.params.material.color(); // Signal<string>

Add clickable buttons that trigger callbacks:

params = tweaks('Actions', {
reset: { action: () => this.reset(), label: 'Reset All' },
randomize: { action: () => this.randomize() },
});

Button configs don’t produce signals in the result object.

ParameterTypeDescription
folderNamestringName of the folder to create in the pane
configTweakConfigConfiguration object defining controls
options.expandedbooleanWhether folder starts expanded (default: false)
options.injectorInjectorOptional injector for use outside injection context
Config TypeDescriptionExample
numberCreates number inputgravity: 9.8
stringCreates text inputname: 'Object'
booleanCreates checkboxdebug: false
WritableSignal<T>Two-way bindingenabled: this.mySignal
TweakNumberConfigNumber with constraints{ value: 0, min: -5, max: 5, step: 0.1 }
TweakColorConfigColor picker{ value: '#ff0000', color: true }
TweakCheckboxConfigBoolean toggle{ value: true }
TweakTextConfigText input{ value: 'hello' }
TweakListConfigDropdown select{ value: 'a', options: ['a', 'b', 'c'] }
TweakPointConfig2D/3D/4D point{ value: [0, 0, 0] as [number, number, number] }
TweakButtonConfigClickable button{ action: () => reset() }
TweakFolderConfigNested foldertweaks.folder('Name', { ... })
OptionTypeDescription
valuenumber | WritableSignal<number>Initial value or signal for two-way binding
minnumberMinimum value (enables slider)
maxnumberMaximum value (enables slider)
stepnumberStep increment
OptionTypeDescription
valuestring | WritableSignal<string>Hex color value
colortrueRequired marker to identify as color input
OptionTypeDescription
valueT | WritableSignal<T>Currently selected value
optionsT[] | Record<string, T>Array or label-value mapping
OptionTypeDescription
value[number, number, ...] | WritableSignal<...>Point tuple (2D, 3D, or 4D)
x{ min?, max?, step? }X-axis constraints
y{ min?, max?, step? }Y-axis constraints
z{ min?, max?, step? }Z-axis constraints (3D/4D)
w{ min?, max?, step? }W-axis constraints (4D)
OptionTypeDescription
action() => voidCallback when button is clicked
labelstringOptional label next to button

Creates a nested folder configuration.

tweaks.folder(name: string, config: TweakConfig, options?: { expanded?: boolean })
ParameterTypeDescription
namestringDisplay name of the folder
configTweakConfigControls within the folder
options.expandedbooleanWhether folder starts expanded