diff --git a/src/apps/rednatco/controls.tsx b/src/apps/rednatco/controls.tsx
index f2f8977c3de0010f184ee27ae9a228c70bf0ecb8..5c720989e87a9eb47959b79fdc2775e699001344 100644
--- a/src/apps/rednatco/controls.tsx
+++ b/src/apps/rednatco/controls.tsx
@@ -1,18 +1,29 @@
 import React from 'react';
-import { numDecimals, stof } from './util';
+import { fuzzyCmp, numDecimals, reduceDecimals, stof } from './util';
 
 const Zero = '0'.charCodeAt(0);
 const Nine = '9'.charCodeAt(0);
 const Minus = '-'.charCodeAt(0);
 const Period = '.'.charCodeAt(0);
 
-function maybeNumeric(s: string) {
+function isAllowedNumericInput(s: string, maxNumDecimals?: number) {
+    let havePeriod = false;
     for (let idx = 0; idx < s.length; idx++) {
         const cc = s.charCodeAt(idx);
-        if (!((cc >= Zero && cc <= Nine) || cc === Minus || cc === Period))
+        if (cc === Period) {
+            if (havePeriod)
+                return false;
+            else
+                havePeriod = true;
+        } else if (cc === Minus) {
+            if (idx > 0)
+                return false;
+        } else if (!(cc >= Zero && cc <= Nine))
             return false;
     }
 
+    if (maxNumDecimals !== undefined)
+        return numDecimals(s) <= maxNumDecimals;
     return true;
 }
 
@@ -109,9 +120,7 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
         super(props);
 
         this.state = {
-            displayedValue: this.props.formatter
-                ? this.props.formatter(this.props.value.toString())
-                : this.props.value.toString(),
+            displayedValue: this.formatValue(this.props.value),
         };
     }
 
@@ -129,7 +138,21 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
             return;
         const nv = n - this.props.step;
         if (nv >= this.props.min)
-            this.props.onChange(n);
+            this.notifyChange(nv);
+    }
+
+    private formatValue(n: number, padToDecimals?: number) {
+        if (this.props.maxNumDecimals !== undefined) {
+            if (padToDecimals !== undefined) {
+                const padded = n.toFixed(padToDecimals);
+                if (fuzzyCmp(n, stof(padded)!))
+                    return padded;
+            }
+
+            const fn = n.toFixed(this.props.maxNumDecimals);
+            return reduceDecimals(fn);
+        }
+        return n.toString();
     }
 
     private increase() {
@@ -137,11 +160,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
         if (n === undefined)
             return;
         const nv = n + this.props.step;
-        if (nv >= this.props.min)
-            this.props.onChange(n);
+        if (nv <= this.props.max)
+            this.notifyChange(nv);
     }
 
-    private handleChange(value: string) {
+    private maybeNotifyUpdate(value: string) {
         if (this.props.maxNumDecimals !== undefined && numDecimals(value) > this.props.maxNumDecimals)
             return;
 
@@ -149,23 +172,33 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
 
         if (
             n !== undefined &&
-            n !== this.props.value &&
-            (this.props.min <= n && n <= this.props.max) &&
-            value[value.length - 1] !== '.'
+            !fuzzyCmp(n, this.props.value) &&
+            (this.props.min <= n && n <= this.props.max)
         ) {
-            this.props.onChange(n);
-        } else {
-            if (maybeNumeric(value))
-                this.setState({ ...this.state, displayedValue: value });
+            this.notifyChange(n);
         }
     }
 
-    componentDidUpdate(prevProps: SpinBox.Props) {
-        if (this.props !== prevProps) {
-            const displayedValue = this.props.formatter
-                ? this.props.formatter(this.props.value.toString())
-                : this.props.value.toString();
-            this.setState({ ...this.state, displayedValue });
+    private notifyChange(n: number) {
+        if (this.props.maxNumDecimals !== undefined) {
+            const Factor = Math.pow(10, this.props.maxNumDecimals);
+            this.props.onChange(Math.round(n * Factor) / Factor);
+        } else
+            this.props.onChange(n);
+    }
+
+    componentDidUpdate(prevProps: SpinBox.Props, prevState: SpinBoxState) {
+        if (this.props !== prevProps && this.props.value !== prevProps.value) {
+            const padToDecimals = stof(this.state.displayedValue) !== undefined ? numDecimals(this.state.displayedValue) : void 0;
+            const displayedValue = this.formatValue(this.props.value, padToDecimals);
+            if (
+                displayedValue !== this.state.displayedValue &&
+                displayedValue !== this.state.displayedValue.substring(0, this.state.displayedValue.length - 1)
+            )
+                this.setState({ ...this.state, displayedValue });
+        } else {
+            if (this.state.displayedValue !== prevState.displayedValue)
+                this.maybeNotifyUpdate(this.state.displayedValue);
         }
     }
 
@@ -176,7 +209,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
                     type='text'
                     className={this.props.disabled ? this.clsDisabled() : this.clsEnabled()}
                     value={this.state.displayedValue}
-                    onChange={evt => this.handleChange(evt.currentTarget.value)}
+                    onChange={evt => {
+                        const v = evt.currentTarget.value;
+                        if (isAllowedNumericInput(v, this.props.maxNumDecimals))
+                            this.setState({ ...this.state, displayedValue: v });
+                    }}
                     onWheel={evt => {
                         evt.stopPropagation();
                         const n = stof(this.state.displayedValue);
@@ -185,11 +222,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
                         if (evt.deltaY < 0) {
                             const nv = n + this.props.step;
                             if (nv <= this.props.max)
-                                this.props.onChange(nv);
+                                this.notifyChange(nv);
                         } else if (evt.deltaY > 0) {
                             const nv = n - this.props.step;
                             if (nv >= this.props.min)
-                                this.props.onChange(nv);
+                                this.notifyChange(nv);
                         }
                     }}
                 />
@@ -209,7 +246,7 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
 }
 export namespace SpinBox {
     export interface Formatter {
-        (v: string): string;
+        (v: number): string;
     }
 
     export interface OnChange {
@@ -226,7 +263,6 @@ export namespace SpinBox {
         disabled?: boolean;
         className?: string;
         classNameDisabled?: string;
-        formatter?: Formatter;
         maxNumDecimals?: number;
     }
 }
diff --git a/src/apps/rednatco/density-map-controls.tsx b/src/apps/rednatco/density-map-controls.tsx
index ef6a10ba8eeb794abd2f5bd124427e2d9652ae96..750a3066e1ec638b1d54ddd9e1262325f734a8d7 100644
--- a/src/apps/rednatco/density-map-controls.tsx
+++ b/src/apps/rednatco/density-map-controls.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import { CollapsibleVertical, RangeSlider, SpinBox, ToggleButton } from './controls';
-import { isoToFixed, numDecimals, stof } from './util';
+import { isoToFixed } from './util';
 
 export class DensityMapControls extends React.Component<DensityMapControls.Props> {
     render() {
@@ -65,28 +65,22 @@ export class DensityMapControls extends React.Component<DensityMapControls.Props
                         <div className='rmsp-control-item'>
                             <RangeSlider
                                 min={0}
-                                max={1}
-                                step={0.1}
-                                value={(1.0 - this.props.alpha)}
-                                onChange={(v) => this.props.changeAlpha(1.0 - v!)}
+                                max={100}
+                                step={1}
+                                value={(1.0 - this.props.alpha) * 100}
+                                onChange={(n) => this.props.changeAlpha(1.0 - (n! / 100))}
                             />
                         </div>
                         <div className='rmsp-control-item'>
                             <div style={{ display: 'grid', gridTemplateColumns: '4em 1fr' }}>
                                 <SpinBox
                                     min={0}
-                                    max={1}
-                                    step={0.1}
-                                    maxNumDecimals={1}
-                                    value={(1.0 - this.props.alpha)}
-                                    onChange={(n) => this.props.changeAlpha(1.0 - n)}
+                                    max={100}
+                                    step={1}
+                                    maxNumDecimals={0}
+                                    value={(1.0 - this.props.alpha) * 100}
+                                    onChange={(n) => this.props.changeAlpha(1.0 - (n / 100))}
                                     pathPrefix=''
-                                    formatter={v => {
-                                        const n = stof(v);
-                                        if (n !== undefined && numDecimals(v) > 1)
-                                            return n.toFixed(1);
-                                        return v;
-                                    }}
                                 />
                             </div>
                         </div>
diff --git a/src/apps/rednatco/util.ts b/src/apps/rednatco/util.ts
index c943be480fc90fd766fae94bcfd8d4ff16d0d2e5..d16a9cff9a1d4fe024735458e4122508f259f35a 100644
--- a/src/apps/rednatco/util.ts
+++ b/src/apps/rednatco/util.ts
@@ -1,6 +1,21 @@
 import { Color } from '../../mol-util/color';
 import { parseInt as parseIntMS, parseFloat as parseFloatMS } from '../../mol-io/reader/common/text/number-parser';
 
+const Zero = '0'.charCodeAt(0);
+const Period = '.'.charCodeAt(0);
+
+export function clampDecimals(s: string, maxNumDecimals: number) {
+    const idx = s.lastIndexOf('.');
+    if (idx < 0)
+        return s;
+    return maxNumDecimals === 0 ? s.substring(0, idx) : s.substring(0, idx + maxNumDecimals + 1);
+}
+
+export function fuzzyCmp(a: number, b: number, relativeTolerance = 0.00001) {
+    const TOL = a * relativeTolerance;
+    return Math.abs(a - b) <= TOL;
+}
+
 export function isoBounds(min: number, max: number): { min: number, max: number, step: number } {
     let diff = max - min;
     if (diff <= 0.0)
@@ -41,6 +56,23 @@ export function prettyIso(iso: number, step: number) {
     return Math.floor((iso - step) / step) * step + step;
 }
 
+export function reduceDecimals(s: string) {
+    const delimIdx = s.lastIndexOf('.');
+    if (delimIdx < 0)
+        return s;
+    else if (delimIdx === s.length - 1)
+        return s.substring(0, s.length - 1);
+
+    let idx = s.length - 1;
+    for (; idx > delimIdx; idx--) {
+        if (s.charCodeAt(idx) !== Zero)
+            break;
+    }
+    const noDot = s.charCodeAt(idx) === Period ? 0 : 1
+
+    return s.substring(0, idx + noDot);
+}
+
 export function stof(s: string) {
     if (s.length === 0)
         return void 0;