diff --git a/src/components/mic-indicator/mic-indicator.css b/src/components/mic-indicator/mic-indicator.css
new file mode 100644
index 0000000000000000000000000000000000000000..9fb3970297b19fbc9c4db523bc9bbe42674ed682
--- /dev/null
+++ b/src/components/mic-indicator/mic-indicator.css
@@ -0,0 +1,10 @@
+@keyframes popIn {
+    from {transform: scale(0.5)}
+    to {transform: scale(1)}
+}
+
+.mic-img {
+    margin: 10px;
+    transform-origin: center;
+    animation: popIn 0.1s ease-in-out;
+}
diff --git a/src/components/mic-indicator/mic-indicator.jsx b/src/components/mic-indicator/mic-indicator.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fe0eb602b531e5c33de0cd2a696ab5a5dc907962
--- /dev/null
+++ b/src/components/mic-indicator/mic-indicator.jsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styles from './mic-indicator.css';
+import micIcon from './mic-indicator.svg';
+import {stageSizeToTransform} from '../../lib/screen-utils';
+
+const MicIndicatorComponent = props => (
+    <div
+        className={props.className}
+        style={stageSizeToTransform(props.stageSize)}
+    >
+        <img
+            className={styles.micImg}
+            src={micIcon}
+        />
+    </div>
+);
+
+MicIndicatorComponent.propTypes = {
+    className: PropTypes.string,
+    stageSize: PropTypes.shape({
+        width: PropTypes.number,
+        height: PropTypes.number,
+        widthDefault: PropTypes.number,
+        heightDefault: PropTypes.number
+    }).isRequired
+};
+
+export default MicIndicatorComponent;
diff --git a/src/components/mic-indicator/mic-indicator.svg b/src/components/mic-indicator/mic-indicator.svg
new file mode 100644
index 0000000000000000000000000000000000000000..78726389d13b811a8b7f31bbfa32a98df118a932
Binary files /dev/null and b/src/components/mic-indicator/mic-indicator.svg differ
diff --git a/src/components/monitor-list/monitor-list.jsx b/src/components/monitor-list/monitor-list.jsx
index 5cb11424113808e07f9c8573a9b2626ec3a838d5..f7b02080fdf71aeadbf673bf53a9fa23990d6760 100644
--- a/src/components/monitor-list/monitor-list.jsx
+++ b/src/components/monitor-list/monitor-list.jsx
@@ -4,20 +4,10 @@ import Box from '../box/box.jsx';
 import Monitor from '../../containers/monitor.jsx';
 import PropTypes from 'prop-types';
 import {OrderedMap} from 'immutable';
+import {stageSizeToTransform} from '../../lib/screen-utils';
 
 import styles from './monitor-list.css';
 
-const stageSizeToTransform = ({width, height, widthDefault, heightDefault}) => {
-    const scaleX = width / widthDefault;
-    const scaleY = height / heightDefault;
-    if (scaleX === 1 && scaleY === 1) {
-        // Do not set a transform if the scale is 1 because
-        // it messes up `position: fixed` elements like the context menu.
-        return;
-    }
-    return {transform: `scale(${scaleX},${scaleY})`};
-};
-
 const MonitorList = props => (
     <Box
         // Use static `monitor-overlay` class for bounds of draggables
diff --git a/src/components/question/question.css b/src/components/question/question.css
index 4550df75cdbe1b3ad51b2e5c45e54f47d1de7627..a0f8116ed1a1d9c85957d1167ee792f1ee8515f4 100644
--- a/src/components/question/question.css
+++ b/src/components/question/question.css
@@ -1,13 +1,6 @@
 @import "../../css/units.css";
 @import "../../css/colors.css";
 
-.question-wrapper {
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    width: 100%;
-}
-
 .question-container {
     margin: $space;
     border: 1px solid $ui-black-transparent;
diff --git a/src/components/question/question.jsx b/src/components/question/question.jsx
index c57fb085d37de1f42a75f3a709c05d0f313f76ac..373f7cb919cd86fdbab96665457fdec29cb5fb4e 100644
--- a/src/components/question/question.jsx
+++ b/src/components/question/question.jsx
@@ -7,13 +7,14 @@ import enterIcon from './icon--enter.svg';
 const QuestionComponent = props => {
     const {
         answer,
+        className,
         question,
         onChange,
         onClick,
         onKeyPress
     } = props;
     return (
-        <div className={styles.questionWrapper}>
+        <div className={className}>
             <div className={styles.questionContainer}>
                 {question ? (
                     <div className={styles.questionLabel}>{question}</div>
@@ -43,6 +44,7 @@ const QuestionComponent = props => {
 
 QuestionComponent.propTypes = {
     answer: PropTypes.string,
+    className: PropTypes.string,
     onChange: PropTypes.func.isRequired,
     onClick: PropTypes.func.isRequired,
     onKeyPress: PropTypes.func.isRequired,
diff --git a/src/components/stage/stage.css b/src/components/stage/stage.css
index 7ce4d55d9f6a953541564e6e12b51fc1fed9be78..98a0c42a715d8aace5fc8294dfc5d9f895226dd2 100644
--- a/src/components/stage/stage.css
+++ b/src/components/stage/stage.css
@@ -74,10 +74,6 @@
     border: none;
 }
 
-.question-wrapper {
-    position: absolute;
-}
-
 /* adjust monitors when stage is standard size:
 shift them down and right to compensate for the stage's border */
 .stage-wrapper .monitor-wrapper {
@@ -93,7 +89,7 @@ to adjust for the border using a different method */
     padding-bottom: calc($stage-full-screen-stage-padding + $stage-full-screen-border-width);
 }
 
-.monitor-wrapper, .color-picker-wrapper, .queston-wrapper {
+.monitor-wrapper, .color-picker-wrapper {
     position: absolute;
     top: 0;
     left: 0;
@@ -110,3 +106,24 @@ to adjust for the border using a different method */
     z-index: $z-index-dragging-sprite;
     filter: drop-shadow(5px 5px 5px $ui-black-transparent);
  }
+
+.stage-bottom-wrapper {
+    position: absolute;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    top: 0;
+    overflow: hidden;
+    pointer-events: none;
+}
+
+.mic-indicator {
+    transform-origin: bottom right;
+    z-index: $z-index-stage-indicator;
+    pointer-events: none;
+    align-self: flex-end;
+}
+
+.question-wrapper {
+    pointer-events: auto;
+}
diff --git a/src/components/stage/stage.jsx b/src/components/stage/stage.jsx
index d8c0d07749bec8eb360820988b5e360318513e71..9e1f3454dd1686be6e745dd5fb8035ec97b16b41 100644
--- a/src/components/stage/stage.jsx
+++ b/src/components/stage/stage.jsx
@@ -7,6 +7,7 @@ import DOMElementRenderer from '../../containers/dom-element-renderer.jsx';
 import Loupe from '../loupe/loupe.jsx';
 import MonitorList from '../../containers/monitor-list.jsx';
 import Question from '../../containers/question.jsx';
+import MicIndicator from '../mic-indicator/mic-indicator.jsx';
 import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants.js';
 import {getStageDimensions} from '../../lib/screen-utils.js';
 import styles from './stage.css';
@@ -18,6 +19,7 @@ const StageComponent = props => {
         isColorPicking,
         isFullScreen,
         colorInfo,
+        micIndicator,
         question,
         stageSize,
         useEditorDragStyle,
@@ -66,13 +68,22 @@ const StageComponent = props => {
                         <Loupe colorInfo={colorInfo} />
                     </Box>
                 ) : null}
-                {question === null ? null : (
-                    <div
-                        className={classNames(
-                            styles.stageOverlayContent,
-                            styles.stageOverlayContentBorderOverride
-                        )}
-                    >
+                <div
+                    className={styles.stageBottomWrapper}
+                    style={{
+                        width: stageDimensions.width,
+                        height: stageDimensions.height,
+                        left: '50%',
+                        marginLeft: stageDimensions.width * -0.5
+                    }}
+                >
+                    {micIndicator ? (
+                        <MicIndicator
+                            className={styles.micIndicator}
+                            stageSize={stageDimensions}
+                        />
+                    ) : null}
+                    {question === null ? null : (
                         <div
                             className={styles.questionWrapper}
                             style={{width: stageDimensions.width}}
@@ -82,8 +93,8 @@ const StageComponent = props => {
                                 onQuestionAnswered={onQuestionAnswered}
                             />
                         </div>
-                    </div>
-                )}
+                    )}
+                </div>
                 <canvas
                     className={styles.draggingSprite}
                     height={0}
diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx
index 0aa3f2a4b64ef883e29cf0b47ff0a4f1d8d93cd3..dc2b87043956be682e4dcb68dc4e6a9657f9bcf3 100644
--- a/src/containers/stage.jsx
+++ b/src/containers/stage.jsx
@@ -75,7 +75,8 @@ class Stage extends React.Component {
             this.props.isColorPicking !== nextProps.isColorPicking ||
             this.state.colorInfo !== nextState.colorInfo ||
             this.props.isFullScreen !== nextProps.isFullScreen ||
-            this.state.question !== nextState.question;
+            this.state.question !== nextState.question ||
+            this.props.micIndicator !== nextProps.micIndicator;
     }
     componentDidUpdate (prevProps) {
         if (this.props.isColorPicking && !prevProps.isColorPicking) {
@@ -402,6 +403,7 @@ Stage.defaultProps = {
 const mapStateToProps = state => ({
     isColorPicking: state.scratchGui.colorPicker.active,
     isFullScreen: state.scratchGui.mode.isFullScreen,
+    micIndicator: state.scratchGui.micIndicator,
     // Do not use editor drag style in fullscreen or player mode.
     useEditorDragStyle: !(state.scratchGui.mode.isFullScreen || state.scratchGui.mode.isPlayerOnly)
 });
diff --git a/src/css/z-index.css b/src/css/z-index.css
index 87cc2cd01a70bc8cc35a88f6019b6744040a26e3..06e2015658b563f88c84a901a35f36cad91c8fcb 100644
--- a/src/css/z-index.css
+++ b/src/css/z-index.css
@@ -8,6 +8,7 @@ $z-index-extension-button: 50; /* Force extension button above the ScratchBlocks
 $z-index-menu-bar: 50; /* blocklyToolboxDiv is 40 */
 
 $z-index-monitor: 100;
+$z-index-stage-indicator: 110;
 $z-index-add-button: 120;
 $z-index-tooltip: 130; /* tooltips should go over add buttons if they overlap */
 
diff --git a/src/lib/screen-utils.js b/src/lib/screen-utils.js
index afef90d84c845119d881c11eef4e27c4f3a0b2ae..d612017841291b990e3fb1fa6af539f99afee1f5 100644
--- a/src/lib/screen-utils.js
+++ b/src/lib/screen-utils.js
@@ -72,7 +72,29 @@ const getStageDimensions = (stageSize, isFullScreen) => {
     return stageDimensions;
 };
 
+/**
+ * Take a pair of sizes for the stage (a target height and width and a default height and width),
+ * calculate the ratio between them, and return a CSS transform to scale to that ratio.
+ * @param {object} sizeInfo An object containing dimensions of the target and default stage sizes.
+ * @param {number} sizeInfo.width The target width
+ * @param {number} sizeInfo.height The target height
+ * @param {number} sizeInfo.widthDefault The default width
+ * @param {number} sizeInfo.heightDefault The default height
+ * @returns {object} the CSS transform
+ */
+const stageSizeToTransform = ({width, height, widthDefault, heightDefault}) => {
+    const scaleX = width / widthDefault;
+    const scaleY = height / heightDefault;
+    if (scaleX === 1 && scaleY === 1) {
+        // Do not set a transform if the scale is 1 because
+        // it messes up `position: fixed` elements like the context menu.
+        return;
+    }
+    return {transform: `scale(${scaleX},${scaleY})`};
+};
+
 export {
     getStageDimensions,
-    resolveStageSize
+    resolveStageSize,
+    stageSizeToTransform
 };
diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx
index 68b2753ab4c153c83f76ed79d3246b126b4fe969..6b8d1777792c8e68790a6f5b5d0859e21952088c 100644
--- a/src/lib/vm-listener-hoc.jsx
+++ b/src/lib/vm-listener-hoc.jsx
@@ -10,6 +10,7 @@ import {updateBlockDrag} from '../reducers/block-drag';
 import {updateMonitors} from '../reducers/monitors';
 import {setRunningState, setTurboState} from '../reducers/vm-status';
 import {showAlert} from '../reducers/alerts';
+import {updateMicIndicator} from '../reducers/mic-indicator';
 
 /*
  * Higher Order Component to manage events emitted by the VM
@@ -38,6 +39,8 @@ const vmListenerHOC = function (WrappedComponent) {
             this.props.vm.on('PROJECT_RUN_START', this.props.onProjectRunStart);
             this.props.vm.on('PROJECT_RUN_STOP', this.props.onProjectRunStop);
             this.props.vm.on('PERIPHERAL_ERROR', this.props.onShowAlert);
+            this.props.vm.on('MIC_LISTENING', this.props.onMicListeningUpdate);
+
         }
         componentDidMount () {
             if (this.props.attachKeyboardEvents) {
@@ -89,6 +92,7 @@ const vmListenerHOC = function (WrappedComponent) {
                 onBlockDragUpdate,
                 onKeyDown,
                 onKeyUp,
+                onMicListeningUpdate,
                 onMonitorsUpdate,
                 onTargetsUpdate,
                 onProjectRunStart,
@@ -107,6 +111,7 @@ const vmListenerHOC = function (WrappedComponent) {
         onBlockDragUpdate: PropTypes.func.isRequired,
         onKeyDown: PropTypes.func,
         onKeyUp: PropTypes.func,
+        onMicListeningUpdate: PropTypes.func.isRequired,
         onMonitorsUpdate: PropTypes.func.isRequired,
         onProjectRunStart: PropTypes.func.isRequired,
         onProjectRunStop: PropTypes.func.isRequired,
@@ -141,6 +146,9 @@ const vmListenerHOC = function (WrappedComponent) {
         onTurboModeOff: () => dispatch(setTurboState(false)),
         onShowAlert: data => {
             dispatch(showAlert(data));
+        },
+        onMicListeningUpdate: listening => {
+            dispatch(updateMicIndicator(listening));
         }
     });
     return connect(
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index 4d6cea9c0ea151d29bf92945ae10d61e59c8dbf8..bce112a1c327c6e6a2761cbbeb6e63f29dbf87da 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -8,6 +8,7 @@ import blockDragReducer, {blockDragInitialState} from './block-drag';
 import editorTabReducer, {editorTabInitialState} from './editor-tab';
 import hoveredTargetReducer, {hoveredTargetInitialState} from './hovered-target';
 import menuReducer, {menuInitialState} from './menus';
+import micIndicatorReducer, {micIndicatorInitialState} from './mic-indicator';
 import modalReducer, {modalsInitialState} from './modals';
 import modeReducer, {modeInitialState} from './mode';
 import monitorReducer, {monitorsInitialState} from './monitors';
@@ -38,6 +39,7 @@ const guiInitialState = {
     hoveredTarget: hoveredTargetInitialState,
     stageSize: stageSizeInitialState,
     menus: menuInitialState,
+    micIndicator: micIndicatorInitialState,
     modals: modalsInitialState,
     monitors: monitorsInitialState,
     monitorLayout: monitorLayoutInitialState,
@@ -104,6 +106,7 @@ const guiReducer = combineReducers({
     hoveredTarget: hoveredTargetReducer,
     stageSize: stageSizeReducer,
     menus: menuReducer,
+    micIndicator: micIndicatorReducer,
     modals: modalReducer,
     monitors: monitorReducer,
     monitorLayout: monitorLayoutReducer,
diff --git a/src/reducers/mic-indicator.js b/src/reducers/mic-indicator.js
new file mode 100644
index 0000000000000000000000000000000000000000..2548abbb41e785c086036b7e18e48b18f9e3542f
--- /dev/null
+++ b/src/reducers/mic-indicator.js
@@ -0,0 +1,26 @@
+const UPDATE = 'scratch-gui/mic-indicator/UPDATE';
+
+const initialState = false;
+
+const reducer = function (state, action) {
+    if (typeof state === 'undefined') state = initialState;
+    switch (action.type) {
+    case UPDATE:
+        return action.visible;
+    default:
+        return state;
+    }
+};
+
+const updateMicIndicator = function (visible) {
+    return {
+        type: UPDATE,
+        visible: visible
+    };
+};
+
+export {
+    reducer as default,
+    initialState as micIndicatorInitialState,
+    updateMicIndicator
+};