diff --git a/src/components/drag-layer/drag-layer.css b/src/components/drag-layer/drag-layer.css
new file mode 100644
index 0000000000000000000000000000000000000000..711b5eea813d6a2d80b97d75c98c40fbb68e8457
--- /dev/null
+++ b/src/components/drag-layer/drag-layer.css
@@ -0,0 +1,28 @@
+@import "../../css/units.css";
+@import "../../css/colors.css";
+
+.drag-layer {
+    position: fixed;
+    pointer-events: none;
+    z-index: 1000; /* Above everything */
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%
+}
+
+.image-wrapper {
+    /* Absolute allows wrapper to snuggly fit image */
+    position: absolute;
+}
+
+.image {
+    max-width: 80px;
+
+    /* Center the dragging image on the given position */
+    margin-left: -50%;
+    margin-top: -50%;
+
+    /* Use the same drop shadow as stage dragging */
+    filter: drop-shadow(5px 5px 5px $ui-black-transparent);
+}
diff --git a/src/components/drag-layer/drag-layer.jsx b/src/components/drag-layer/drag-layer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..7df2bd7cf828f822b7e9b22fecc8378fc187143b
--- /dev/null
+++ b/src/components/drag-layer/drag-layer.jsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styles from './drag-layer.css';
+
+/* eslint no-confusing-arrow: ["error", {"allowParens": true}] */
+const DragLayer = ({dragging, img, currentOffset}) => (dragging ? (
+    <div className={styles.dragLayer}>
+        <div
+            className={styles.imageWrapper}
+            style={{
+                transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`
+            }}
+        >
+            <img
+                className={styles.image}
+                src={img}
+            />
+        </div>
+    </div>
+) : null);
+
+DragLayer.propTypes = {
+    currentOffset: PropTypes.shape({
+        x: PropTypes.number.isRequired,
+        y: PropTypes.number.isRequired
+    }),
+    dragging: PropTypes.bool.isRequired,
+    img: PropTypes.string
+};
+
+export default DragLayer;
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index ad02b2feda7d185e3cd468347363b93a77e58ac7..98a4e83d12856d1d1ffc7568b6287a84f030c925 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -21,6 +21,7 @@ import ImportModal from '../../containers/import-modal.jsx';
 import WebGlModal from '../../containers/webgl-modal.jsx';
 import TipsLibrary from '../../containers/tips-library.jsx';
 import Cards from '../../containers/cards.jsx';
+import DragLayer from '../../containers/drag-layer.jsx';
 
 import styles from './gui.css';
 import addExtensionIcon from './icon--extensions.svg';
@@ -215,6 +216,7 @@ const GUIComponent = props => {
                     </Box>
                 </Box>
             </Box>
+            <DragLayer />
         </Box>
     );
 };
diff --git a/src/components/sprite-selector-item/sprite-selector-item.jsx b/src/components/sprite-selector-item/sprite-selector-item.jsx
index e947b380e25e3470bbb2a00c99d562033fe35995..62364ec53cab6605f552eeb06112ceb3fa58bb1d 100644
--- a/src/components/sprite-selector-item/sprite-selector-item.jsx
+++ b/src/components/sprite-selector-item/sprite-selector-item.jsx
@@ -20,8 +20,11 @@ const SpriteSelectorItem = props => (
             }),
             onClick: props.onClick,
             onMouseEnter: props.onMouseEnter,
-            onMouseLeave: props.onMouseLeave
+            onMouseLeave: props.onMouseLeave,
+            onMouseDown: props.onMouseDown,
+            onTouchStart: props.onMouseDown
         }}
+        disable={props.dragging}
         id={`${props.name}-${contextMenuId}`}
     >
         {(props.selected && props.onDeleteButtonClick) ? (
@@ -77,11 +80,13 @@ SpriteSelectorItem.propTypes = {
     className: PropTypes.string,
     costumeURL: PropTypes.string,
     details: PropTypes.string,
+    dragging: PropTypes.bool,
     name: PropTypes.string.isRequired,
     number: PropTypes.number,
     onClick: PropTypes.func,
     onDeleteButtonClick: PropTypes.func,
     onDuplicateButtonClick: PropTypes.func,
+    onMouseDown: PropTypes.func,
     onMouseEnter: PropTypes.func,
     onMouseLeave: PropTypes.func,
     selected: PropTypes.bool.isRequired
diff --git a/src/containers/drag-layer.jsx b/src/containers/drag-layer.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f52008f35f0cf144ca1c9bc1c404b062edf06674
--- /dev/null
+++ b/src/containers/drag-layer.jsx
@@ -0,0 +1,10 @@
+import {connect} from 'react-redux';
+import DragLayer from '../components/drag-layer/drag-layer.jsx';
+
+const mapStateToProps = state => ({
+    dragging: state.scratchGui.assetDrag.dragging,
+    currentOffset: state.scratchGui.assetDrag.currentOffset,
+    img: state.scratchGui.assetDrag.img
+});
+
+export default connect(mapStateToProps)(DragLayer);
diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx
index 7d3fc2f21c45677102ef9feb3b4322c48b45dfe6..1a6c77b9d970831cda6f8b5c307de99fbf21d7f3 100644
--- a/src/containers/sprite-selector-item.jsx
+++ b/src/containers/sprite-selector-item.jsx
@@ -4,9 +4,13 @@ import React from 'react';
 import {connect} from 'react-redux';
 
 import {setHoveredSprite} from '../reducers/hovered-target';
+import {updateAssetDrag} from '../reducers/asset-drag';
+import {getEventXY} from '../lib/touch-utils';
 
 import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx';
 
+const dragThreshold = 3; // Same as the block drag threshold
+
 class SpriteSelectorItem extends React.Component {
     constructor (props) {
         super(props);
@@ -15,9 +19,44 @@ class SpriteSelectorItem extends React.Component {
             'handleDelete',
             'handleDuplicate',
             'handleMouseEnter',
-            'handleMouseLeave'
+            'handleMouseLeave',
+            'handleMouseDown',
+            'handleMouseMove',
+            'handleMouseUp'
         ]);
     }
+    handleMouseUp () {
+        this.initialOffset = null;
+        window.removeEventListener('mouseup', this.handleMouseUp);
+        window.removeEventListener('mousemove', this.handleMouseMove);
+        window.removeEventListener('touchend', this.handleMouseUp);
+        window.removeEventListener('touchmove', this.handleMouseMove);
+        this.props.onDrag({
+            img: null,
+            currentOffset: null,
+            dragging: false
+        });
+    }
+    handleMouseMove (e) {
+        const currentOffset = getEventXY(e);
+        const dx = currentOffset.x - this.initialOffset.x;
+        const dy = currentOffset.y - this.initialOffset.y;
+        if (Math.sqrt((dx * dx) + (dy * dy)) > dragThreshold) {
+            this.props.onDrag({
+                img: this.props.costumeURL,
+                currentOffset: currentOffset,
+                dragging: true
+            });
+        }
+        e.preventDefault();
+    }
+    handleMouseDown (e) {
+        this.initialOffset = getEventXY(e);
+        window.addEventListener('mouseup', this.handleMouseUp);
+        window.addEventListener('mousemove', this.handleMouseMove);
+        window.addEventListener('touchend', this.handleMouseUp);
+        window.addEventListener('touchmove', this.handleMouseMove);
+    }
     handleClick (e) {
         e.preventDefault();
         this.props.onClick(this.props.id);
@@ -57,6 +96,7 @@ class SpriteSelectorItem extends React.Component {
                 onClick={this.handleClick}
                 onDeleteButtonClick={onDeleteButtonClick ? this.handleDelete : null}
                 onDuplicateButtonClick={onDuplicateButtonClick ? this.handleDuplicate : null}
+                onMouseDown={this.handleMouseDown}
                 onMouseEnter={this.handleMouseEnter}
                 onMouseLeave={this.handleMouseLeave}
                 {...props}
@@ -73,6 +113,7 @@ SpriteSelectorItem.propTypes = {
     name: PropTypes.string,
     onClick: PropTypes.func,
     onDeleteButtonClick: PropTypes.func,
+    onDrag: PropTypes.func.isRequired,
     onDuplicateButtonClick: PropTypes.func,
     receivedBlocks: PropTypes.bool.isRequired,
     selected: PropTypes.bool
@@ -80,13 +121,15 @@ SpriteSelectorItem.propTypes = {
 
 const mapStateToProps = (state, {assetId, costumeURL, id}) => ({
     costumeURL: costumeURL || (assetId && state.scratchGui.vm.runtime.storage.get(assetId).encodeDataURI()),
+    dragging: state.scratchGui.assetDrag.dragging,
     receivedBlocks: state.scratchGui.hoveredTarget.receivedBlocks &&
             state.scratchGui.hoveredTarget.sprite === id
 });
 const mapDispatchToProps = dispatch => ({
     dispatchSetHoveredSprite: spriteId => {
         dispatch(setHoveredSprite(spriteId));
-    }
+    },
+    onDrag: data => dispatch(updateAssetDrag(data))
 });
 
 export default connect(
diff --git a/src/reducers/asset-drag.js b/src/reducers/asset-drag.js
new file mode 100644
index 0000000000000000000000000000000000000000..b8081581280e0ac0ff07c511d01933f47f23d054
--- /dev/null
+++ b/src/reducers/asset-drag.js
@@ -0,0 +1,31 @@
+const DRAG_UPDATE = 'scratch-gui/asset-drag/DRAG_UPDATE';
+
+const initialState = {
+    dragging: false,
+    currentOffset: null,
+    img: null
+};
+
+const reducer = function (state, action) {
+    if (typeof state === 'undefined') state = initialState;
+
+    switch (action.type) {
+    case DRAG_UPDATE:
+        return Object.assign({}, state, action.state);
+    default:
+        return state;
+    }
+};
+
+const updateAssetDrag = function (state) {
+    return {
+        type: DRAG_UPDATE,
+        state: state
+    };
+};
+
+export {
+    reducer as default,
+    initialState as assetDragInitialState,
+    updateAssetDrag
+};
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index e28b757163b50b0c10c8265222e6bc4f877a14d8..829f0b0446171e56ca2c3157ea316c5611727d2a 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -1,4 +1,5 @@
 import {applyMiddleware, compose, combineReducers} from 'redux';
+import assetDragReducer, {assetDragInitialState} from './asset-drag';
 import cardsReducer, {cardsInitialState} from './cards';
 import colorPickerReducer, {colorPickerInitialState} from './color-picker';
 import customProceduresReducer, {customProceduresInitialState} from './custom-procedures';
@@ -19,6 +20,7 @@ import throttle from 'redux-throttle';
 const guiMiddleware = compose(applyMiddleware(throttle(300, {leading: true, trailing: true})));
 
 const guiInitialState = {
+    assetDrag: assetDragInitialState,
     blockDrag: blockDragInitialState,
     cards: cardsInitialState,
     colorPicker: colorPickerInitialState,
@@ -57,6 +59,7 @@ const initFullScreen = function (currentState) {
     );
 };
 const guiReducer = combineReducers({
+    assetDrag: assetDragReducer,
     blockDrag: blockDragReducer,
     cards: cardsReducer,
     colorPicker: colorPickerReducer,
diff --git a/test/unit/containers/sprite-selector-item.test.jsx b/test/unit/containers/sprite-selector-item.test.jsx
index 23bda4e143de8f2ed55a7aac834cf182ffe5bdbd..85032603d4b3a1712db3fb431a96d109dc40d7d7 100644
--- a/test/unit/containers/sprite-selector-item.test.jsx
+++ b/test/unit/containers/sprite-selector-item.test.jsx
@@ -37,8 +37,9 @@ describe('SpriteSelectorItem Container', () => {
 
     beforeEach(() => {
         store = mockStore({scratchGui: {
-            hoveredTarget: {receivedBlocks: false, sprite: null}}
-        });
+            hoveredTarget: {receivedBlocks: false, sprite: null},
+            assetDrag: {dragging: false}
+        }});
         className = 'ponies';
         costumeURL = 'https://scratch.mit.edu/foo/bar/pony';
         id = 1337;