diff --git a/src/apps/rednatco/color-picker.tsx b/src/apps/rednatco/color-picker.tsx
index d57b2037f97a020859800ad609f46e9f7a94d08b..77547251c349ec2d0f13979f958ff268eeda7698 100644
--- a/src/apps/rednatco/color-picker.tsx
+++ b/src/apps/rednatco/color-picker.tsx
@@ -11,7 +11,6 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import { Colors } from './colors';
 import { PushButton, SpinBox } from './controls';
-import { parseInt as parseIntMS } from '../../mol-io/reader/common/text/number-parser';
 import './assets/imgs/triangle-down.svg';
 import './assets/imgs/triangle-up.svg';
 
@@ -43,10 +42,6 @@ function isSatValVal(v: number) {
     return v >= MIN_SATVAL && v <= MAX_SATVAL;
 }
 
-function stoi(s: string) {
-    return parseIntMS(s, 0, s.length);
-}
-
 interface State {
     h: number;
     s: number;
@@ -545,18 +540,13 @@ export class ColorPicker extends React.Component<ColorPicker.Props, State> {
                         min={MIN_RGB}
                         max={MAX_RGB}
                         step={1}
-                        value={this.state.rIn === '' ? null : Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).r)}
+                        value={Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).r)}
                         onChange={rIn => {
-                            if (rIn === '')
-                                this.setState({ ...this.state, rIn });
-                            else {
-                                const r = stoi(rIn);
-                                if (!isRgbVal(r))
-                                    return;
-
-                                const { g, b } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
-                                this.updateColorRgb({ r, g, b });
-                            }
+                            if (!isRgbVal(rIn))
+                                return;
+
+                            const { g, b } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
+                            this.updateColorRgb({ r: rIn, g, b });
                         }}
                         pathPrefix={this.props.pathPrefix}
                     />
@@ -565,18 +555,13 @@ export class ColorPicker extends React.Component<ColorPicker.Props, State> {
                         min={MIN_RGB}
                         max={MAX_RGB}
                         step={1}
-                        value={this.state.gIn === '' ? null : Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).g)}
+                        value={Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).g)}
                         onChange={gIn => {
-                            if (gIn === '')
-                                this.setState({ ...this.state, gIn });
-                            else {
-                                const g = stoi(gIn);
-                                if (!isRgbVal(g))
-                                    return;
-
-                                const { r, b } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
-                                this.updateColorRgb({ r, g, b });
-                            }
+                            if (!isRgbVal(gIn))
+                                return;
+
+                            const { r, b } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
+                            this.updateColorRgb({ r, g: gIn, b });
                         }}
                         pathPrefix={this.props.pathPrefix}
                     />
@@ -585,18 +570,13 @@ export class ColorPicker extends React.Component<ColorPicker.Props, State> {
                         min={MIN_RGB}
                         max={MAX_RGB}
                         step={1}
-                        value={this.state.bIn === '' ? null : Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).b)}
+                        value={Math.round(Colors.hsv2rgb(this.state.h, this.state.s, this.state.v).b)}
                         onChange={bIn => {
-                            if (bIn === '')
-                                this.setState({ ...this.state, bIn });
-                            else {
-                                const b = stoi(bIn);
-                                if (!isRgbVal(b))
-                                    return;
-
-                                const { r, g } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
-                                this.updateColorRgb({ r, g, b });
-                            }
+                            if (!isRgbVal(bIn))
+                                return;
+
+                            const { r, g } = Colors.hsv2rgb(this.state.h, this.state.s, this.state.v);
+                            this.updateColorRgb({ r, g, b: bIn });
                         }}
                         pathPrefix={this.props.pathPrefix}
                     />
@@ -614,17 +594,12 @@ export class ColorPicker extends React.Component<ColorPicker.Props, State> {
                         min={MIN_HUE}
                         max={MAX_HUE}
                         step={1}
-                        value={this.state.hIn === '' ? null : Math.round(this.state.h)}
+                        value={Math.round(this.state.h)}
                         onChange={hIn => {
-                            if (hIn === '')
-                                this.setState({ ...this.state, hIn });
-                            else {
-                                const h = stoi(hIn);
-                                if (!isHueVal(h))
-                                    return;
-
-                                this.updateColorHsv({ h, s: this.state.s, v: this.state.v });
-                            }
+                            if (!isHueVal(hIn))
+                                return;
+
+                            this.updateColorHsv({ h: hIn, s: this.state.s, v: this.state.v });
                         }}
                         pathPrefix={this.props.pathPrefix}
                     />
@@ -633,17 +608,12 @@ export class ColorPicker extends React.Component<ColorPicker.Props, State> {
                         min={MIN_SATVAL}
                         max={MAX_SATVAL}
                         step={1}
-                        value={this.state.sIn === '' ? null : Math.round(this.state.s * 100)}
+                        value={Math.round(this.state.s * 100)}
                         onChange={sIn => {
-                            if (sIn === '')
-                                this.setState({ ...this.state, sIn });
-                            else {
-                                const s = stoi(sIn);
-                                if (!isSatValVal(s))
-                                    return;
-
-                                this.updateColorHsv({ h: this.state.h, s: s / 100, v: this.state.v });
-                            }
+                            if (!isSatValVal(sIn))
+                                return;
+
+                            this.updateColorHsv({ h: this.state.h, s: sIn / 100, v: this.state.v });
                         }}
                         pathPrefix={this.props.pathPrefix}
                     />
@@ -652,17 +622,12 @@ export class ColorPicker extends React.Component<ColorPicker.Props, State> {
                         min={MIN_SATVAL}
                         max={MAX_SATVAL}
                         step={1}
-                        value={this.state.vIn === '' ? null : Math.round(this.state.v * 100)}
+                        value={Math.round(this.state.v * 100)}
                         onChange={vIn => {
-                            if (vIn === '')
-                                this.setState({ ...this.state, vIn });
-                            else {
-                                const v = stoi(vIn);
-                                if (!isSatValVal(v))
-                                    return;
-
-                                this.updateColorHsv({ h: this.state.h, s: this.state.s, v: v / 100 });
-                            }
+                            if (!isSatValVal(vIn))
+                                return;
+
+                            this.updateColorHsv({ h: this.state.h, s: this.state.s, v: vIn / 100 });
                         }}
                         pathPrefix={this.props.pathPrefix}
                     />
diff --git a/src/apps/rednatco/controls.tsx b/src/apps/rednatco/controls.tsx
index 2c3c53896964c687c7ec6e10969c31fb2351dbda..d625c8cf67462cac105b06823058bf5867284410 100644
--- a/src/apps/rednatco/controls.tsx
+++ b/src/apps/rednatco/controls.tsx
@@ -1,4 +1,20 @@
 import React from 'react';
+import { 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) {
+    for (let idx = 0; idx < s.length; idx++) {
+        const cc = s.charCodeAt(idx);
+        if (!((cc >= Zero && cc <= Nine) || cc === Minus || cc === Period))
+            return false;
+    }
+
+    return true;
+}
 
 export class CollapsibleVertical extends React.Component<CollapsibleVertical.Props, { collapsed: boolean }> {
     constructor(props: CollapsibleVertical.Props) {
@@ -67,8 +83,9 @@ export class RangeSlider extends React.Component<RangeSlider.Props> {
                 max={this.props.max}
                 step={this.props.step}
                 onChange={evt => {
-                    const n = parseFloat(evt.currentTarget.value);
-                    this.props.onChange(isNaN(n) ? null : n);
+                    const n = stof(evt.currentTarget.value);
+                    if (n !== undefined)
+                        this.props.onChange(n);
                 }}
             />
         );
@@ -84,7 +101,20 @@ export namespace RangeSlider {
     }
 }
 
-export class SpinBox extends React.Component<SpinBox.Props> {
+interface SpinBoxState {
+    displayedValue: string
+}
+export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
+    constructor(props: SpinBox.Props) {
+        super(props);
+
+        this.state = {
+            displayedValue: this.props.formatter
+                ? this.props.formatter(this.props.value.toString())
+                : this.props.value.toString(),
+        };
+    }
+
     private clsDisabled() {
         return this.props.classNameDisabled ?? 'rmsp-spinbox-input-disabled';
     }
@@ -94,19 +124,46 @@ export class SpinBox extends React.Component<SpinBox.Props> {
     }
 
     private decrease() {
-        if (this.props.value === null)
+        const n = stof(this.state.displayedValue);
+        if (n === undefined)
             return;
-        const nv = this.props.value - this.props.step;
+        const nv = n - this.props.step;
         if (nv >= this.props.min)
-            this.props.onChange(nv.toString());
+            this.props.onChange(n);
     }
 
     private increase() {
-        if (this.props.value === null)
+        const n = stof(this.state.displayedValue);
+        if (n === undefined)
             return;
-        const nv = this.props.value + this.props.step;
+        const nv = n + this.props.step;
         if (nv >= this.props.min)
-            this.props.onChange(nv.toString());
+            this.props.onChange(n);
+    }
+
+    private handleChange(value: string) {
+        const n = stof(value);
+
+        if (
+            n !== undefined &&
+            n !== this.props.value &&
+            (this.props.min <= n && n <= this.props.max) &&
+            value[value.length - 1] !== '.'
+        ) {
+            this.props.onChange(n);
+        } else {
+            if (maybeNumeric(value))
+                this.setState({ ...this.state, displayedValue: value });
+        }
+    }
+
+    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 });
+        }
     }
 
     render() {
@@ -115,27 +172,21 @@ export class SpinBox extends React.Component<SpinBox.Props> {
                 <input
                     type='text'
                     className={this.props.disabled ? this.clsDisabled() : this.clsEnabled()}
-                    value={this.props.formatter ? this.props.formatter(this.props.value) : this.props.value?.toString() ?? ''}
-                    onChange={evt =>{
-                        const v = evt.currentTarget.value;
-                        const n = parseFloat(v);
-                        if (!isNaN(n) && (n < this.props.min || n > this.props.max))
-                            return;
-
-                        this.props.onChange(evt.currentTarget.value);
-                    }}
+                    value={this.state.displayedValue}
+                    onChange={evt => this.handleChange(evt.currentTarget.value)}
                     onWheel={evt => {
                         evt.stopPropagation();
-                        if (this.props.value === null)
+                        const n = stof(this.state.displayedValue);
+                        if (n === undefined)
                             return;
                         if (evt.deltaY < 0) {
-                            const nv = this.props.value + this.props.step;
+                            const nv = n + this.props.step;
                             if (nv <= this.props.max)
-                                this.props.onChange(nv.toString());
+                                this.props.onChange(nv);
                         } else if (evt.deltaY > 0) {
-                            const nv = this.props.value - this.props.step;
+                            const nv = n - this.props.step;
                             if (nv >= this.props.min)
-                                this.props.onChange(nv.toString());
+                                this.props.onChange(nv);
                         }
                     }}
                 />
@@ -155,15 +206,15 @@ export class SpinBox extends React.Component<SpinBox.Props> {
 }
 export namespace SpinBox {
     export interface Formatter {
-        (v: number|null): string;
+        (v: string): string;
     }
 
     export interface OnChange {
-        (newValue: string): void;
+        (newValue: number): void;
     }
 
     export interface Props {
-        value: number|null;
+        value: number;
         onChange: OnChange;
         min: number;
         max: number;
diff --git a/src/apps/rednatco/density-map-controls.tsx b/src/apps/rednatco/density-map-controls.tsx
index b922006a04cc4f67f5bbbd9dae4cfe27e4d7315e..293e719c196667cd6607041627f39e7d8ff83ced 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 } from './util';
+import { isoToFixed, numDecimals, stof } from './util';
 
 export class DensityMapControls extends React.Component<DensityMapControls.Props> {
     render() {
@@ -49,7 +49,7 @@ export class DensityMapControls extends React.Component<DensityMapControls.Props
                                     max={this.props.isoMax}
                                     step={this.props.isoStep}
                                     value={isoToFixed(this.props.iso, this.props.isoStep)}
-                                    onChange={(v) => this.props.changeIso(parseFloat(v))}
+                                    onChange={(n) => this.props.changeIso(n)}
                                     pathPrefix=''
                                 />
                                 <div />
@@ -76,9 +76,15 @@ export class DensityMapControls extends React.Component<DensityMapControls.Props
                                     min={0}
                                     max={1}
                                     step={0.1}
-                                    value={parseFloat((1.0 - this.props.alpha).toFixed(1))}
-                                    onChange={(v) => this.props.changeAlpha(1.0 - parseFloat(v))}
+                                    value={(1.0 - this.props.alpha)}
+                                    onChange={(n) => this.props.changeAlpha(1.0 - n)}
                                     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/index.tsx b/src/apps/rednatco/index.tsx
index f6dda49d199c4f7006086bb396b767ac926d6b84..68a2625cf759d7a7481f3834ae999d2406f87af8 100644
--- a/src/apps/rednatco/index.tsx
+++ b/src/apps/rednatco/index.tsx
@@ -138,7 +138,10 @@ export class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
                 : this.classColorToConformers(changes.cls, Color(changes.color)))
         };
 
-        const display = { ...this.state.display, classColors, conformerColors };
+        const display = { ...this.state.display };
+        display.structures.classColors = classColors;
+        display.structures.conformerColors = conformerColors;
+
         this.viewer!.changeNtCColors(display);
         this.setState({ ...this.state, display });
     }
@@ -150,7 +153,9 @@ export class ReDNATCOMsp extends React.Component<ReDNATCOMsp.Props, State> {
         else
             conformerColors[changes.conformer] = Color(changes.color);
 
-        const display = { ...this.state.display, conformerColors };
+        const display = { ...this.state.display };
+        display.structures.conformerColors = conformerColors;
+
         this.viewer!.changeNtCColors(display);
         this.setState({ ...this.state, display });
     }
diff --git a/src/apps/rednatco/util.ts b/src/apps/rednatco/util.ts
index 165709e39b5f8bb96f1018ca2146b8c5f0ef4215..c943be480fc90fd766fae94bcfd8d4ff16d0d2e5 100644
--- a/src/apps/rednatco/util.ts
+++ b/src/apps/rednatco/util.ts
@@ -1,4 +1,5 @@
 import { Color } from '../../mol-util/color';
+import { parseInt as parseIntMS, parseFloat as parseFloatMS } from '../../mol-io/reader/common/text/number-parser';
 
 export function isoBounds(min: number, max: number): { min: number, max: number, step: number } {
     let diff = max - min;
@@ -31,10 +32,33 @@ export function luminance(color: Color) {
     return Math.sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
 }
 
+export function numDecimals(s: string) {
+    const idx = s.lastIndexOf('.');
+    return idx >= 0 ? s.length - idx - 1 : 0;
+}
+
 export function prettyIso(iso: number, step: number) {
     return Math.floor((iso - step) / step) * step + step;
 }
 
+export function stof(s: string) {
+    if (s.length === 0)
+        return void 0;
+    if (s === '-')
+        return void 0;
+    const n = parseFloatMS(s, 0, s.length);
+    return isNaN(n) ? undefined : n;
+}
+
+export function stoi(s: string) {
+    if (s.length === 0)
+        return void 0;
+    if (s === '-')
+        return void 0;
+    const n = parseIntMS(s, 0, s.length);
+    return isNaN(n) ? undefined : n;
+}
+
 export function toggleArray<T>(array: T[], elem: T) {
     if (array.includes(elem))
         return array.filter((x) => x !== elem);