diff --git a/src/components/monitor-list/monitor-list.jsx b/src/components/monitor-list/monitor-list.jsx
index 50263220dd5bbbc164cee2c95bdfbbdf75a5c935..07b6a817be90020956891f340b73a344f5b21afd 100644
--- a/src/components/monitor-list/monitor-list.jsx
+++ b/src/components/monitor-list/monitor-list.jsx
@@ -24,6 +24,7 @@ const MonitorList = props => (
                     opcode={monitorData.opcode}
                     params={monitorData.params}
                     spriteName={monitorData.spriteName}
+                    targetId={monitorData.targetId}
                     value={monitorData.value}
                     width={monitorData.width}
                     x={monitorData.x}
diff --git a/src/components/monitor/list-monitor.jsx b/src/components/monitor/list-monitor.jsx
index c50cc5703cf806a294eb7aea39fc57c6f9a3fef7..e407260434a4546849189795b63ba92dfc648e8a 100644
--- a/src/components/monitor/list-monitor.jsx
+++ b/src/components/monitor/list-monitor.jsx
@@ -1,8 +1,73 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import styles from './monitor.css';
 
-const ListMonitor = ({categoryColor, label, width, height, value}) => (
+
+class ListMonitorRow extends React.Component {
+    constructor (props) {
+        super(props);
+        this.handleActivate = props.onActivate.bind(this, props.index);
+    }
+    render () {
+        return (
+            <div
+                className={styles.listRow}
+                key={`label-${this.props.index}`}
+            >
+                <div className={styles.listIndex}>{this.props.index + 1 /* one indexed */}</div>
+                <div
+                    className={styles.listValue}
+                    style={{background: this.props.categoryColor}}
+                    onClick={this.handleActivate}
+                >
+                    {this.props.activeIndex === this.props.index ? (
+                        <div className={styles.inputWrapper}>
+                            <input
+                                autoFocus
+                                autoComplete={false}
+                                className={classNames(styles.listInput, 'no-drag')}
+                                onBlur={this.props.onDeactivate}
+                                onChange={this.props.onInput}
+                                onFocus={this.props.onFocus}
+                                onKeyDown={this.props.onKeyPress} // key down to get ahead of blur
+                                spellCheck={false}
+                                type="text"
+                                value={this.props.activeValue} /* eslint-disable-line */
+                            />
+                            <div
+                                className={styles.removeButton}
+                                onMouseDown={this.props.onRemove} // mousedown to get ahead of blur
+                            >
+                                {'✖︎'}
+                            </div>
+                        </div>
+
+                    ) : (
+                        <div className={styles.valueInner}>{this.props.value}</div>
+                    )}
+                </div>
+            </div>
+        );
+    }
+}
+
+ListMonitorRow.propTypes = {
+    index: PropTypes.number,
+    activeIndex: PropTypes.number,
+    activeValue: PropTypes.string,
+    value: PropTypes.string,
+    onRemove: PropTypes.func,
+    onKeyPress: PropTypes.func,
+    onFocus: PropTypes.func,
+    onInput: PropTypes.func,
+    onDeactivate: PropTypes.func,
+    categoryColor: PropTypes.string,
+    onActivate: PropTypes.func,
+    onKeyPress: PropTypes.func
+};
+
+const ListMonitor = ({label, width, height, value, onResizeMouseDown, onAdd, ...rowProps}) => (
     <div
         className={styles.listMonitor}
         style={{
@@ -14,54 +79,58 @@ const ListMonitor = ({categoryColor, label, width, height, value}) => (
             {label}
         </div>
         <div className={styles.listBody}>
-            {!value || value.length === 0 ? (
+            {value.length === 0 ? (
                 <div className={styles.listEmpty}>
-                    {'(empty)' /* @todo not translating, awaiting design */}
+                    {'(empty)'}
                 </div>
             ) : value.map((v, i) => (
-                <div
-                    className={styles.listRow}
-                    key={`label-${i}`}
-                >
-                    <div className={styles.listIndex}>{i + 1 /* one indexed */}</div>
-                    <div
-                        className={styles.listValue}
-                        style={{background: categoryColor}}
-                    >
-                        <div className={styles.valueInner}>{v}</div>
-                    </div>
-                </div>
+                <ListMonitorRow
+                    {...rowProps}
+                    index={i}
+                    key={`${label}-row-${i}`}
+                    value={v}
+                />
             ))}
         </div>
         <div className={styles.listFooter}>
-            <div className={styles.footerButton}>
-                {/* @todo add button here */}
+            <div
+                className={styles.addButton}
+                onClick={onAdd}
+            >
+                {'+' /* TODO waiting on asset */}
             </div>
             <div className={styles.footerLength}>
-                <span className={styles.lengthNumber}>
-                    {value.length}
-                </span>
+                {`length ${value.length}`}
             </div>
-            <div className={styles.resizeHandle}>
-                {/* @todo resize handle */}
+            <div
+                className={classNames(styles.resizeHandle, 'no-drag')}
+                onMouseDown={onResizeMouseDown}
+            >
+                {'=' /* TODO waiting on asset */}
             </div>
         </div>
     </div>
 );
 
 ListMonitor.propTypes = {
+    activeIndex: PropTypes.number,
     categoryColor: PropTypes.string.isRequired,
     height: PropTypes.number,
     label: PropTypes.string.isRequired,
-    value: PropTypes.arrayOf(PropTypes.oneOfType([
+    onActivate: PropTypes.func,
+    value: PropTypes.oneOfType([
         PropTypes.string,
-        PropTypes.number
-    ])),
+        PropTypes.number,
+        PropTypes.arrayOf(PropTypes.oneOfType([
+            PropTypes.string,
+            PropTypes.number
+        ]))
+    ]),
     width: PropTypes.number
 };
 
 ListMonitor.defaultProps = {
-    width: 80,
+    width: 110,
     height: 200
 };
 
diff --git a/src/components/monitor/monitor.css b/src/components/monitor/monitor.css
index 783cec7068671c87e8011fb21140929b64afacd1..078d24104eb8770c61e80a5b28362eb940982eac 100644
--- a/src/components/monitor/monitor.css
+++ b/src/components/monitor/monitor.css
@@ -109,6 +109,7 @@
     border-radius: calc($space / 2);
     border: 1px solid $ui-black-transparent;
     flex-grow: 1;
+    height: 22px;
 }
 
 .list-footer {
@@ -137,3 +138,25 @@
     padding: 3px 5px;
     min-height: 22px;
 }
+
+.list-input {
+    padding: 3px 5px;
+    border: 0;
+    background: rgba(0, 0, 0, 0.1);
+    color: $ui-white;
+    outline: none;
+    font-size: 0.75rem;
+    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+.remove-button {
+    position: absolute;
+    top: 4px;
+    right: 3px;
+    cursor: pointer;
+    color: $ui-white;
+}
+
+.add-button {
+    cursor: pointer;
+}
diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx
index 95387a1ec78370be823f1bd05ca0d2b670147c39..a45fe52142a26090245023d4e558b720fd3d95cb 100644
--- a/src/components/monitor/monitor.jsx
+++ b/src/components/monitor/monitor.jsx
@@ -7,8 +7,8 @@ import {ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
 import Box from '../box/box.jsx';
 import DefaultMonitor from './default-monitor.jsx';
 import LargeMonitor from './large-monitor.jsx';
-import SliderMonitor from './slider-monitor.jsx';
-import ListMonitor from './list-monitor.jsx';
+import SliderMonitor from '../../containers/slider-monitor.jsx';
+import ListMonitor from '../../containers/list-monitor.jsx';
 
 import styles from './monitor.css';
 
@@ -29,7 +29,10 @@ const modes = {
 };
 
 const MonitorComponent = props => (
-    <ContextMenuTrigger id={`monitor-${props.label}`}>
+    <ContextMenuTrigger
+        holdToDisplay={props.mode === 'slider' ? -1 : 1000}
+        id={`monitor-${props.label}`}
+    >
         <Draggable
             bounds=".monitor-overlay" // Class for monitor container
             cancel=".no-drag" // Class used for slider input to prevent drag
@@ -41,14 +44,9 @@ const MonitorComponent = props => (
                 componentRef={props.componentRef}
                 onDoubleClick={props.mode === 'list' ? null : props.onNextMode}
             >
-                {(modes[props.mode] || modes.default)({ // Use default until other modes arrive
+                {React.createElement(modes[props.mode], {
                     categoryColor: categories[props.category],
-                    label: props.label,
-                    value: props.value,
-                    width: props.width,
-                    height: props.height,
-                    min: props.min,
-                    max: props.max
+                    ...props
                 })}
             </Box>
         </Draggable>
@@ -90,25 +88,13 @@ const monitorModes = Object.keys(modes);
 MonitorComponent.propTypes = {
     category: PropTypes.oneOf(Object.keys(categories)),
     componentRef: PropTypes.func.isRequired,
-    height: PropTypes.number,
     label: PropTypes.string.isRequired,
-    max: PropTypes.number,
-    min: PropTypes.number,
     mode: PropTypes.oneOf(monitorModes),
     onDragEnd: PropTypes.func.isRequired,
     onNextMode: PropTypes.func.isRequired,
     onSetModeToDefault: PropTypes.func.isRequired,
     onSetModeToLarge: PropTypes.func.isRequired,
-    onSetModeToSlider: PropTypes.func,
-    value: PropTypes.oneOfType([
-        PropTypes.string,
-        PropTypes.number,
-        PropTypes.arrayOf(PropTypes.oneOfType([
-            PropTypes.string,
-            PropTypes.number
-        ]))
-    ]),
-    width: PropTypes.number
+    onSetModeToSlider: PropTypes.func
 };
 
 MonitorComponent.defaultProps = {
diff --git a/src/components/monitor/slider-monitor.jsx b/src/components/monitor/slider-monitor.jsx
index a6c410898edc6325df5d3cfafd060a9935eee34c..f8187cfee6a9be6fe0811ddfb8751c00bf0eaa64 100644
--- a/src/components/monitor/slider-monitor.jsx
+++ b/src/components/monitor/slider-monitor.jsx
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import styles from './monitor.css';
 
-const SliderMonitor = ({categoryColor, label, min, max, value}) => (
+const SliderMonitor = ({categoryColor, label, min, max, value, onSliderUpdate}) => (
     <div className={styles.defaultMonitor}>
         <div className={styles.row}>
             <div className={styles.label}>
@@ -22,6 +22,7 @@ const SliderMonitor = ({categoryColor, label, min, max, value}) => (
                 min={min}
                 type="range"
                 value={value}
+                onChange={onSliderUpdate}
                 // @todo onChange callback
             />
         </div>
@@ -34,7 +35,7 @@ SliderMonitor.propTypes = {
     label: PropTypes.string.isRequired,
     max: PropTypes.number,
     min: PropTypes.number,
-    // @todo callback for change events
+    onSliderUpdate: PropTypes.func.isRequired,
     value: PropTypes.oneOfType([
         PropTypes.string,
         PropTypes.number
diff --git a/src/containers/list-monitor-row.jsx b/src/containers/list-monitor-row.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/containers/list-monitor.jsx b/src/containers/list-monitor.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3b5384c7672c81efbe9e53fc8a6814d18c1e9b1e
--- /dev/null
+++ b/src/containers/list-monitor.jsx
@@ -0,0 +1,182 @@
+import bindAll from 'lodash.bindall';
+import PropTypes from 'prop-types';
+import React from 'react';
+import VM from 'scratch-vm';
+import {connect} from 'react-redux';
+import {getEventXY} from '../lib/touch-utils';
+import {getVariableValue, setVariableValue} from '../lib/variable-utils';
+import ListMonitorComponent from '../components/monitor/list-monitor.jsx';
+
+class ListMonitor extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, [
+            'handleActivate',
+            'handleDeactivate',
+            'handleInput',
+            'handleRemove',
+            'handleKeyPress',
+            'handleFocus',
+            'handleAdd',
+            'handleResizeMouseDown'
+        ]);
+
+        this.state = {
+            activeIndex: null,
+            activeValue: null,
+            // TODO These will need to be sent back to the VM for saving
+            width: props.width || 80,
+            height: props.height || 200
+        };
+    }
+
+    handleActivate (index) {
+        this.setState({
+            activeIndex: index,
+            activeValue: this.props.value[index]
+        });
+    }
+
+    handleDeactivate () {
+        // Submit any in-progress value edits on blur
+        if (this.state.activeIndex !== null) {
+            const {vm, targetId, id: variableId} = this.props;
+            const newListValue = getVariableValue(vm, targetId, variableId);
+            newListValue[this.state.activeIndex] = this.state.activeValue;
+            setVariableValue(vm, targetId, variableId, newListValue);
+            this.setState({activeIndex: null, activeValue: null});
+        }
+    }
+
+    handleFocus (e) {
+        // Select all the text in the input when it is focused.
+        e.target.select();
+    }
+
+    handleKeyPress (e) {
+        // Special case for tab, arrow keys and enter.
+        // Tab / shift+tab navigate down / up the list.
+        // Arrow down / arrow up navigate down / up the list.
+        // Enter / shift+enter insert new blank item below / above.
+        const previouslyActiveIndex = this.state.activeIndex;
+        const {vm, targetId, id: variableId} = this.props;
+
+        let navigateDirection = 0;
+        if (e.key === 'Tab') navigateDirection = e.shiftKey ? -1 : 1;
+        else if (e.key === 'ArrowUp') navigateDirection = -1;
+        else if (e.key === 'ArrowDown') navigateDirection = 1;
+        if (navigateDirection) {
+            this.handleDeactivate(); // Submit in-progress edits
+            const newIndex = (previouslyActiveIndex + navigateDirection) % this.props.value.length;
+            this.setState({
+                activeIndex: newIndex,
+                activeValue: this.props.value[newIndex]
+            });
+            e.preventDefault(); // Stop default tab behavior, handled by this state change
+        } else if (e.key === 'Enter') {
+            this.handleDeactivate(); // Submit in-progress edits
+            const newListItemValue = ''; // Enter adds a blank item
+            const newValueOffset = e.shiftKey ? 0 : 1; // Shift-enter inserts above
+            const listValue = getVariableValue(vm, targetId, variableId);
+            const newListValue = listValue.slice(0, previouslyActiveIndex + newValueOffset)
+                .concat([newListItemValue])
+                .concat(listValue.slice(previouslyActiveIndex + newValueOffset));
+            setVariableValue(vm, targetId, variableId, newListValue);
+            const newIndex = (previouslyActiveIndex + newValueOffset) % newListValue.length;
+            this.setState({
+                activeIndex: newIndex,
+                activeValue: newListItemValue
+            });
+        }
+    }
+
+    handleInput (e) {
+        this.setState({activeValue: e.target.value});
+    }
+
+    handleRemove (e) {
+        e.preventDefault(); // Default would blur input, prevent that.
+        const {vm, targetId, id: variableId} = this.props;
+        const listValue = getVariableValue(vm, targetId, variableId);
+        const newListValue = listValue.slice(0, this.state.activeIndex)
+            .concat(listValue.slice(this.state.activeIndex + 1));
+        setVariableValue(vm, targetId, variableId, newListValue);
+        // Selecting the next active is handled when event bubbles up to activate
+    }
+
+    handleAdd () {
+        // Add button appends a blank value and switches to it
+        const {vm, targetId, id: variableId} = this.props;
+        const newListValue = getVariableValue(vm, targetId, variableId).concat(['']);
+        setVariableValue(vm, targetId, variableId, newListValue);
+        this.setState({activeIndex: newListValue.length - 1, activeValue: ''});
+    }
+
+    handleResizeMouseDown (e) {
+        this.initialPosition = getEventXY(e);
+        this.initialWidth = this.state.width;
+        this.initialHeight = this.state.height;
+
+        const onMouseMove = ev => {
+            const newPosition = getEventXY(ev);
+            const dx = newPosition.x - this.initialPosition.x;
+            const dy = newPosition.y - this.initialPosition.y;
+            this.setState({
+                width: Math.min(this.initialWidth + dx, 480),
+                height: Math.max(Math.min(this.initialHeight + dy, 360), 60)
+            });
+        };
+
+        const onMouseUp = ev => {
+            onMouseMove(ev); // Make sure width/height are up-to-date
+            // TODO send these new sizes to the VM for saving
+            window.removeEventListener('mousemove', onMouseMove);
+            window.removeEventListener('mouseup', onMouseUp);
+        };
+
+        window.addEventListener('mousemove', onMouseMove);
+        window.addEventListener('mouseup', onMouseUp);
+
+    }
+    render () {
+        const {
+            vm, // eslint-disable-line no-unused-vars
+            ...props
+        } = this.props;
+        return (
+            <ListMonitorComponent
+                {...props}
+                activeIndex={this.state.activeIndex}
+                activeValue={this.state.activeValue}
+                height={this.state.height}
+                width={this.state.width}
+                onActivate={this.handleActivate}
+                onAdd={this.handleAdd}
+                onDeactivate={this.handleDeactivate}
+                onFocus={this.handleFocus}
+                onInput={this.handleInput}
+                onKeyPress={this.handleKeyPress}
+                onRemove={this.handleRemove}
+                onResizeMouseDown={this.handleResizeMouseDown}
+            />
+        );
+    }
+}
+
+ListMonitor.propTypes = {
+    value: PropTypes.oneOfType([
+        PropTypes.number,
+        PropTypes.string
+    ]),
+    vm: PropTypes.instanceOf(VM),
+    targetId: PropTypes.string,
+    id: PropTypes.string,
+    height: PropTypes.number,
+    width: PropTypes.number,
+    x: PropTypes.number,
+    y: PropTypes.number
+};
+
+const mapStateToProps = state => ({vm: state.vm});
+
+export default connect(mapStateToProps)(ListMonitor);
diff --git a/src/containers/monitor.jsx b/src/containers/monitor.jsx
index cdb87f20eacea93fcafa446ee379ea4ba61f1ca8..c39b563026f78ced7181ba0e484221aec05c038f 100644
--- a/src/containers/monitor.jsx
+++ b/src/containers/monitor.jsx
@@ -107,12 +107,13 @@ class Monitor extends React.Component {
                 max={this.props.max}
                 min={this.props.min}
                 mode={this.state.mode}
-                width={this.props.width}
                 onDragEnd={this.handleDragEnd}
                 onNextMode={this.handleNextMode}
                 onSetModeToDefault={this.handleSetModeToDefault}
                 onSetModeToLarge={this.handleSetModeToLarge}
                 onSetModeToSlider={showSliderOption ? this.handleSetModeToSlider : null}
+                targetId={this.props.targetId}
+                width={this.props.width}
             />
         );
     }
diff --git a/src/containers/slider-monitor.jsx b/src/containers/slider-monitor.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..8df6f92a9879c7aaa9ee364424ac0dd955b3e688
--- /dev/null
+++ b/src/containers/slider-monitor.jsx
@@ -0,0 +1,59 @@
+import bindAll from 'lodash.bindall';
+import PropTypes from 'prop-types';
+import React from 'react';
+import VM from 'scratch-vm';
+import {setVariableValue} from '../lib/variable-utils';
+import {connect} from 'react-redux';
+
+import SliderMonitorComponent from '../components/monitor/slider-monitor.jsx';
+
+class SliderMonitor extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, [
+            'handleSliderUpdate'
+        ]);
+
+        this.state = {
+            value: Number(props.value)
+        };
+    }
+    componentWillReceiveProps (nextProps) {
+        if (this.state.value !== nextProps.value) {
+            this.setState({value: nextProps.value});
+        }
+    }
+    handleSliderUpdate (e) {
+        this.setState({value: Number(e.target.value)});
+        const {vm, targetId, id: variableId} = this.props;
+        setVariableValue(vm, targetId, variableId, Number(e.target.value));
+    }
+    render () {
+        const {
+            vm, // eslint-disable-line no-unused-vars
+            value, // eslint-disable-line no-unused-vars
+            ...props
+        } = this.props;
+        return (
+            <SliderMonitorComponent
+                {...props}
+                value={this.state.value}
+                onSliderUpdate={this.handleSliderUpdate}
+            />
+        );
+    }
+}
+
+SliderMonitor.propTypes = {
+    id: PropTypes.string,
+    targetId: PropTypes.string,
+    value: PropTypes.oneOfType([
+        PropTypes.number,
+        PropTypes.string
+    ]),
+    vm: PropTypes.instanceOf(VM)
+};
+
+const mapStateToProps = state => ({vm: state.vm});
+
+export default connect(mapStateToProps)(SliderMonitor);
diff --git a/src/lib/variable-utils.js b/src/lib/variable-utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..29eacd617938b9bf3fd8c521696cffc4c755a72a
--- /dev/null
+++ b/src/lib/variable-utils.js
@@ -0,0 +1,24 @@
+// Utility functions for updating variables in the VM
+// TODO these should be moved to top-level VM API
+const getVariable = (vm, targetId, variableId) => {
+    const target = targetId ?
+        vm.runtime.getTargetById(targetId) :
+        vm.runtime.getTargetForStage();
+    return target.variables[variableId];
+};
+
+const getVariableValue = (vm, targetId, variableId) => {
+    const variable = getVariable(vm, targetId, variableId);
+    // If array, return a new copy for mutating, ensuring that updates stay immutable.
+    if (variable.value instanceof Array) return variable.value.slice();
+    return variable.value;
+};
+
+const setVariableValue = (vm, targetId, variableId, value) => {
+    getVariable(vm, targetId, variableId).value = value;
+};
+
+export {
+    getVariableValue,
+    setVariableValue
+};