diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts
index b9bbb639ef126489123bab6d076ea25823188055..3c97dcd0acfde1ee9eb7de153f1eef3ad644084b 100644
--- a/src/mol-canvas3d/canvas3d.ts
+++ b/src/mol-canvas3d/canvas3d.ts
@@ -13,7 +13,7 @@ import * as SetUtils from 'mol-util/set'
 import Renderer, { RendererStats } from 'mol-gl/renderer'
 import { RenderObject } from 'mol-gl/render-object'
 
-import TrackballControls from './controls/trackball'
+import { TrackballControls, TrackballControlsParams } from './controls/trackball'
 import { Viewport } from './camera/util'
 import { resizeCanvas } from './util';
 import { createContext, getGLContext, WebGLContext } from 'mol-gl/webgl/context';
@@ -37,9 +37,10 @@ export const Canvas3DParams = {
     clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }),
     fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }),
     pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
+    trackball: PD.Group(TrackballControlsParams),
     debug: PD.Group(DebugHelperParams)
 }
-export type Canvas3DParams = PD.Values<typeof Canvas3DParams>
+export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
 
 export { Canvas3D }
 
@@ -67,17 +68,17 @@ interface Canvas3D {
     readonly camera: Camera
     downloadScreenshot: () => void
     getImageData: (variant: RenderVariant) => ImageData
-    setProps: (props: Partial<Canvas3DParams>) => void
+    setProps: (props: Partial<Canvas3DProps>) => void
 
     /** Returns a copy of the current Canvas3D instance props */
-    readonly props: Canvas3DParams
+    readonly props: Canvas3DProps
     readonly input: InputObserver
     readonly stats: RendererStats
     dispose: () => void
 }
 
 namespace Canvas3D {
-    export function create(canvas: HTMLCanvasElement, container: Element, props: Partial<Canvas3DParams> = {}): Canvas3D {
+    export function create(canvas: HTMLCanvasElement, container: Element, props: Partial<Canvas3DProps> = {}): Canvas3D {
         const p = { ...PD.getDefaultValues(Canvas3DParams), ...props }
 
         const reprRenderObjects = new Map<Representation.Any, Set<RenderObject>>()
@@ -107,7 +108,7 @@ namespace Canvas3D {
         const webgl = createContext(gl)
 
         const scene = Scene.create(webgl)
-        const controls = TrackballControls.create(input, camera, {})
+        const controls = TrackballControls.create(input, camera, p.trackball)
         const renderer = Renderer.create(webgl, camera, { clearColor: p.backgroundColor })
 
         let pickScale = 0.25 / webgl.pixelRatio
@@ -362,7 +363,7 @@ namespace Canvas3D {
                 }
             },
             didDraw,
-            setProps: (props: Partial<Canvas3DParams>) => {
+            setProps: (props: Partial<Canvas3DProps>) => {
                 if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
                     camera.setState({ mode: props.cameraMode })
                 }
@@ -376,9 +377,8 @@ namespace Canvas3D {
                 if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== renderer.props.pickingAlphaThreshold) {
                     renderer.setPickingAlphaThreshold(props.pickingAlphaThreshold)
                 }
-                if (props.debug) {
-                    debugHelper.setProps(props.debug)
-                }
+                if (props.trackball) controls.setProps(props.trackball)
+                if (props.debug) debugHelper.setProps(props.debug)
                 requestDraw(true)
             },
 
@@ -389,6 +389,7 @@ namespace Canvas3D {
                     clip: p.clip,
                     fog: p.fog,
                     pickingAlphaThreshold: renderer.props.pickingAlphaThreshold,
+                    trackball: { ...controls.props },
                     debug: { ...debugHelper.props }
                 }
             },
diff --git a/src/mol-canvas3d/controls/trackball.ts b/src/mol-canvas3d/controls/trackball.ts
index 9703eac2d106fba7c1e787fd46cf3ab27c625be8..269c5819cc3f8530f8f9fd631a0eb29e116276c6 100644
--- a/src/mol-canvas3d/controls/trackball.ts
+++ b/src/mol-canvas3d/controls/trackball.ts
@@ -3,9 +3,7 @@
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-/*
+ *
  * This code has been modified from https://github.com/mrdoob/three.js/,
  * copyright (c) 2010-2018 three.js authors. MIT License
  */
@@ -14,46 +12,41 @@ import { Quat, Vec2, Vec3, EPSILON } from 'mol-math/linear-algebra';
 import { cameraLookAt, Viewport } from '../camera/util';
 import InputObserver, { DragInput, WheelInput, ButtonsType, PinchInput } from 'mol-util/input/input-observer';
 import { Object3D } from 'mol-gl/object3d';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
 
-export const DefaultTrackballControlsProps = {
-    noScroll: true,
+export const TrackballControlsParams = {
+    noScroll: PD.Boolean(true, { isHidden: true }),
 
-    rotateSpeed: 3.0,
-    zoomSpeed: 4.0,
-    panSpeed: 0.8,
+    rotateSpeed: PD.Numeric(5.0, { min: 0.1, max: 10, step: 0.1 }),
+    zoomSpeed: PD.Numeric(6.0, { min: 0.1, max: 10, step: 0.1 }),
+    panSpeed: PD.Numeric(0.8, { min: 0.1, max: 5, step: 0.1 }),
 
-    staticMoving: true,
-    dynamicDampingFactor: 0.2,
+    staticMoving: PD.Boolean(true, { isHidden: true }),
+    dynamicDampingFactor: PD.Numeric(0.2, {}, { isHidden: true }),
 
-    minDistance: 0.01,
-    maxDistance: Infinity
+    minDistance: PD.Numeric(0.01, {}, { isHidden: true }),
+    maxDistance: PD.Numeric(Infinity, {}, { isHidden: true })
 }
-export type TrackballControlsProps = Partial<typeof DefaultTrackballControlsProps>
+export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
 
+export { TrackballControls }
 interface TrackballControls {
     viewport: Viewport
 
-    dynamicDampingFactor: number
-    rotateSpeed: number
-    zoomSpeed: number
-    panSpeed: number
+    readonly props: Readonly<TrackballControlsProps>
+    setProps: (props: Partial<TrackballControlsProps>) => void
 
     update: () => void
     reset: () => void
     dispose: () => void
 }
-
 namespace TrackballControls {
-    export function create (input: InputObserver, object: Object3D & { target: Vec3 }, props: TrackballControlsProps = {}): TrackballControls {
-        const p = { ...DefaultTrackballControlsProps, ...props }
+    export function create (input: InputObserver, object: Object3D & { target: Vec3 }, props: Partial<TrackballControlsProps> = {}): TrackballControls {
+        const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props }
 
         const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 }
         const target: Vec3 = object.target
 
-        let { rotateSpeed, zoomSpeed, panSpeed } = p
-        let { staticMoving, dynamicDampingFactor } = p
-        let { minDistance, maxDistance } = p
-
         let disposed = false
 
         const dragSub = input.drag.subscribe(onDrag)
@@ -131,7 +124,7 @@ namespace TrackballControls {
 
                 Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye))
 
-                angle *= rotateSpeed;
+                angle *= p.rotateSpeed;
                 Quat.setAxisAngle(rotQuat, rotAxis, angle )
 
                 Vec3.transformQuat(_eye, _eye, rotQuat)
@@ -139,8 +132,8 @@ namespace TrackballControls {
 
                 Vec3.copy(_lastAxis, rotAxis)
                 _lastAngle = angle;
-            } else if (!staticMoving && _lastAngle) {
-                _lastAngle *= Math.sqrt(1.0 - dynamicDampingFactor);
+            } else if (!p.staticMoving && _lastAngle) {
+                _lastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
                 Vec3.sub(_eye, Vec3.copy(_eye, object.position), target)
                 Quat.setAxisAngle(rotQuat, _lastAxis, _lastAngle)
 
@@ -152,15 +145,15 @@ namespace TrackballControls {
         }
 
         function zoomCamera () {
-            const factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * zoomSpeed
+            const factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * p.zoomSpeed
             if (factor !== 1.0 && factor > 0.0) {
                 Vec3.scale(_eye, _eye, factor)
             }
 
-            if (staticMoving) {
+            if (p.staticMoving) {
                 Vec2.copy(_zoomStart, _zoomEnd)
             } else {
-                _zoomStart[1] += (_zoomEnd[1] - _zoomStart[1]) * dynamicDampingFactor
+                _zoomStart[1] += (_zoomEnd[1] - _zoomStart[1]) * p.dynamicDampingFactor
             }
         }
 
@@ -172,7 +165,7 @@ namespace TrackballControls {
             Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart)
 
             if (Vec2.squaredMagnitude(panMouseChange)) {
-                Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * panSpeed)
+                Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed)
 
                 Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), object.up)
                 Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0])
@@ -183,11 +176,11 @@ namespace TrackballControls {
                 Vec3.add(object.position, object.position, panOffset)
                 Vec3.add(target, target, panOffset)
 
-                if (staticMoving) {
+                if (p.staticMoving) {
                     Vec2.copy(_panStart, _panEnd)
                 } else {
                     Vec2.sub(panMouseChange, _panEnd, _panStart)
-                    Vec2.scale(panMouseChange, panMouseChange, dynamicDampingFactor)
+                    Vec2.scale(panMouseChange, panMouseChange, p.dynamicDampingFactor)
                     Vec2.add(_panStart, _panStart, panMouseChange)
                 }
             }
@@ -195,14 +188,14 @@ namespace TrackballControls {
 
         /** Ensure the distance between object and target is within the min/max distance */
         function checkDistances() {
-            if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
-                Vec3.setMagnitude(_eye, _eye, maxDistance)
+            if (Vec3.squaredMagnitude(_eye) > p.maxDistance * p.maxDistance) {
+                Vec3.setMagnitude(_eye, _eye, p.maxDistance)
                 Vec3.add(object.position, target, _eye)
                 Vec2.copy(_zoomStart, _zoomEnd)
             }
 
-            if (Vec3.squaredMagnitude(_eye) < minDistance * minDistance) {
-                Vec3.setMagnitude(_eye, _eye, minDistance)
+            if (Vec3.squaredMagnitude(_eye) < p.minDistance * p.minDistance) {
+                Vec3.setMagnitude(_eye, _eye, p.minDistance)
                 Vec3.add(object.position, target, _eye)
                 Vec2.copy(_zoomStart, _zoomEnd)
             }
@@ -274,7 +267,7 @@ namespace TrackballControls {
             }
             _touchZoomDistanceEnd = distance
 
-            const factor = (_touchZoomDistanceStart / _touchZoomDistanceEnd) * zoomSpeed
+            const factor = (_touchZoomDistanceStart / _touchZoomDistanceEnd) * p.zoomSpeed
             _touchZoomDistanceStart = _touchZoomDistanceEnd;
             Vec3.scale(_eye, _eye, factor)
         }
@@ -294,20 +287,12 @@ namespace TrackballControls {
         return {
             viewport,
 
-            get dynamicDampingFactor() { return dynamicDampingFactor },
-            set dynamicDampingFactor(value: number ) { dynamicDampingFactor = value },
-            get rotateSpeed() { return rotateSpeed },
-            set rotateSpeed(value: number ) { rotateSpeed = value },
-            get zoomSpeed() { return zoomSpeed },
-            set zoomSpeed(value: number ) { zoomSpeed = value },
-            get panSpeed() { return panSpeed },
-            set panSpeed(value: number ) { panSpeed = value },
+            get props() { return p as Readonly<TrackballControlsProps> },
+            setProps: (props: Partial<TrackballControlsProps>) => { Object.assign(p, props) },
 
             update,
             reset,
             dispose
         }
     }
-}
-
-export default TrackballControls
\ No newline at end of file
+}
\ No newline at end of file