diff --git a/package.json b/package.json
index f4489ad0e0244fc417040f4ccd7f2bc3eeaef8c9..3429ec678e7a3c0e3f8ab6b843dcb33def970d82 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
     "html-webpack-plugin": "2.28.0",
     "immutable": "3.8.1",
     "lodash.bindall": "4.4.0",
+    "lodash.debounce": "4.0.8",
     "lodash.defaultsdeep": "4.6.0",
     "lodash.isequal": "4.5.0",
     "lodash.omit": "4.5.0",
diff --git a/src/components/close-button/close-button.css b/src/components/close-button/close-button.css
index ce9cc99ac96ba61f8b30bcdcd815de1dbfe63ded..66b79d8a6fd84948e29d846277eb2ed3577155c0 100644
--- a/src/components/close-button/close-button.css
+++ b/src/components/close-button/close-button.css
@@ -6,40 +6,46 @@
     align-items: center;
     justify-content: center;
 
-    color: gray;
-    background-color: $blue;
-
+    overflow: hidden;  /* Mask the icon animation */
+    background-color: rgba(0, 0, 0, 0.1);
     border-radius: 50%;
-    border-color: #dbdbdb;
-    border-style: solid;
-
+    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-    cursor: pointer;
     user-select: none;
-    transition: all 0.15s ease-out; /* @todo: standardize with var */
+    cursor: pointer;
+    transition: all 0.15s ease-out;
+}
+
+.close-button.large:hover {
+    transform: scale(1.1, 1.1);
+    box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.15);
 }
 
 .small {
     width: 1rem;
     height: 1rem;
-    border-width: 1px;
+    color: #FFF;
+    background-color: $blue;
 }
 
 .large {
-    width: 2.75rem;
-    height: 2.75rem;
-    border-width: 2px;
+    width: 1.75rem;
+    height: 1.75rem;
 }
 
-.close-button:hover {
-    transform: scale(1.1, 1.1);
-}
-
-/*  Same icon as Sprite Selector Add button, but rotated.
-    @todo: reuse?
-*/
 .close-icon {
+    position: relative;
+    margin: 0.25rem;
+    user-select: none;
     transform-origin: 50%;
     transform: rotate(45deg);
+}
+
+.small .close-icon {
     width: 40%;
 }
+
+.large .close-icon {
+    width: 0.75rem;
+    height: 0.75rem;
+}
diff --git a/src/components/close-button/close-button.jsx b/src/components/close-button/close-button.jsx
index f7c7fc141f28a93bd2833c67aaea1651e6e6d652..c2d78c79620b207c36e934ad16edf8106482cafe 100644
--- a/src/components/close-button/close-button.jsx
+++ b/src/components/close-button/close-button.jsx
@@ -11,8 +11,8 @@ const CloseButton = props => (
             styles.closeButton,
             props.className,
             {
-                [styles.large]: props.size === CloseButton.SIZE_LARGE,
-                [styles.small]: props.size === CloseButton.SIZE_SMALL
+                [styles.small]: props.size === CloseButton.SIZE_SMALL,
+                [styles.large]: props.size === CloseButton.SIZE_LARGE
             }
         )}
         onClick={props.onClick}
@@ -24,13 +24,13 @@ const CloseButton = props => (
     </div>
 );
 
-CloseButton.SIZE_LARGE = 'large';
 CloseButton.SIZE_SMALL = 'small';
+CloseButton.SIZE_LARGE = 'large';
 
 CloseButton.propTypes = {
     className: PropTypes.string,
     onClick: PropTypes.func.isRequired,
-    size: PropTypes.oneOf([CloseButton.SIZE_LARGE, CloseButton.SIZE_SMALL])
+    size: PropTypes.oneOf([CloseButton.SIZE_SMALL, CloseButton.SIZE_LARGE])
 };
 
 CloseButton.defaultProps = {
diff --git a/src/components/filter/filter.css b/src/components/filter/filter.css
new file mode 100644
index 0000000000000000000000000000000000000000..fa8e072e10bc8cea39150526b1d0184f46b9fa75
--- /dev/null
+++ b/src/components/filter/filter.css
@@ -0,0 +1,103 @@
+@import "../../css/units.css";
+@import "../../css/colors.css";
+
+.filter {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+     /*
+        Should we use width 100% instead, for cases when component's
+        parent is not a flexbox container?
+    */
+    flex-grow: 1;
+
+    padding: 0.3rem 0.5rem;
+    background: rgba(0, 0, 0, 0.10);
+    border: 1px solid rgba(255, 255, 255, 0.10);
+    border-radius: 10rem;
+    user-select: none;
+}
+
+.filter:hover {
+    background: rgba(0, 0, 0, 0.15);
+}
+
+.filter-icon {
+    height: 0.9rem;
+    width: 0.9rem;
+    margin: 0 0.5rem 0 0.75rem;
+}
+
+/*
+    Hidden state
+*/
+.x-icon-wrapper {
+    opacity: 0;
+
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    overflow: hidden;  /* Mask the icon animation */
+    height: 1.25rem;
+    width: 1.25rem;
+    margin: 0 0.25rem 0 0.5rem; /* @todo: move to parent to make component*/
+
+    border-radius: 50%;
+    pointer-events: none;
+    cursor: default;
+    transition: opacity 0.05s linear;
+}
+
+/*
+    Shown state
+*/
+.filter.is-active .x-icon-wrapper {
+    pointer-events: auto;
+    cursor: pointer;
+    opacity: 1;
+    transition: opacity 0.05s linear;
+}
+
+.filter.is-active .x-icon-wrapper:hover {
+    transform: scale(1.2, 1.2);
+}
+
+/*
+    Hidden state
+*/
+.x-icon {
+    position: relative;
+    margin: 0.25rem;
+    user-select: none;
+    transform: translateX(0.5rem);
+    transition: transform 0.085s cubic-bezier(0.78, 1, 1, 1);
+}
+
+/*
+    Shown state
+*/
+.filter.is-active .x-icon-wrapper .x-icon {
+    transform: translateX(0);
+}
+
+.filter-input {
+    flex-grow: 1;
+    line-height: 1.25rem;
+    background-color: transparent;
+    -webkit-appearance: none;
+    outline: none;
+    border: 0;
+    color: white;
+    font-size: 0.75rem;
+    letter-spacing: 0.15px;
+    cursor: text;
+}
+
+.filter-input::placeholder {
+    opacity: 0.75;
+    padding: 0 0 0 0.25rem;
+    color: white;
+    font-size: 0.75rem;
+    letter-spacing: 0.15px;
+}
diff --git a/src/components/filter/filter.jsx b/src/components/filter/filter.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..6e140eb80b3c4bd6bd0f8bad350f447cb7865b36
--- /dev/null
+++ b/src/components/filter/filter.jsx
@@ -0,0 +1,57 @@
+const classNames = require('classnames');
+const PropTypes = require('prop-types');
+const React = require('react');
+
+const filterIcon = require('./icon--filter.svg');
+const xIcon = require('./icon--x.svg');
+const styles = require('./filter.css');
+
+const FilterComponent = props => {
+    const {
+        onChange,
+        onClear,
+        placeholderText,
+        filterQuery
+    } = props;
+    return (
+        <div
+            className={classNames({
+                [styles.filter]: true,
+                [styles.isActive]: filterQuery.length > 0
+            })}
+        >
+            <img
+                className={styles.filterIcon}
+                src={filterIcon}
+            />
+            <input
+                autoFocus
+                className={styles.filterInput}
+                placeholder={placeholderText}
+                type="text"
+                value={filterQuery}
+                onChange={onChange}
+            />
+            <div
+                className={styles.xIconWrapper}
+                onClick={onClear}
+            >
+                <img
+                    className={styles.xIcon}
+                    src={xIcon}
+                />
+            </div>
+        </div>
+    );
+};
+
+FilterComponent.propTypes = {
+    filterQuery: PropTypes.string,
+    onChange: PropTypes.func,
+    onClear: PropTypes.func,
+    placeholderText: PropTypes.string
+};
+FilterComponent.defaultProps = {
+    placeholderText: 'what are you looking for?'
+};
+module.exports = FilterComponent;
diff --git a/src/components/filter/icon--filter.svg b/src/components/filter/icon--filter.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2a0c75fcb9ffc6aa604b66b0ce6320994e2b086a
Binary files /dev/null and b/src/components/filter/icon--filter.svg differ
diff --git a/src/components/filter/icon--x.svg b/src/components/filter/icon--x.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e78a2a8e24a8fe526004cd1ec4e1a2622dec76d2
Binary files /dev/null and b/src/components/filter/icon--x.svg differ
diff --git a/src/components/library-item/library-item.css b/src/components/library-item/library-item.css
index e25c478d2af2147ee88ebccb472fa3d00fff69dc..12253a55202602010b71b2a98014d1ecbbe787df 100644
--- a/src/components/library-item/library-item.css
+++ b/src/components/library-item/library-item.css
@@ -6,8 +6,9 @@
     align-items: center;
     justify-content: center;
     flex-basis: 160px;
+    max-width: 160px;
     height: 160px;
-    margin: calc($space / 2);
+    margin: $space;
     padding: 1rem 1rem 0 1rem;
     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
     color: #575e75;
@@ -23,18 +24,18 @@
 .library-item:hover {
     border-width: 2px;
     border-color: #1dacf4;
-    transition: 0.1s ease-out;
-    transform: scale(1.02, 1.02);
 }
 
-.library-item.is-selected {
-    border-width: 2px;
-    border-color: #1dacf4;
-    transition: 0.25s ease-out;
+.library-item-image-container-wrapper {
+    height: 100px;
+    width: 100%;
+    position: relative;
 }
 
 .library-item-image-container {
+    position: absolute;
     height: 100px;
+    width: 100%;
 }
 
 .library-item-image {
@@ -47,12 +48,12 @@
     margin: 0.25rem 0;
     text-align: center;
 
-    /* 
+    /*
         For truncating overflowing text gracefully
         Min-width is for a bug: https://css-tricks.com/flexbox-truncated-text
     */
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
-    min-width: 0; 
+    min-width: 0;
 }
diff --git a/src/components/library-item/library-item.jsx b/src/components/library-item/library-item.jsx
index 31e722b0e8fd6cc9bb7d76cd24bd8fdf12cfe9e5..cdb1415a12db8370c0e8e5a66f7c6d732ba5073e 100644
--- a/src/components/library-item/library-item.jsx
+++ b/src/components/library-item/library-item.jsx
@@ -1,4 +1,3 @@
-const classNames = require('classnames');
 const bindAll = require('lodash.bindall');
 const PropTypes = require('prop-types');
 const React = require('react');
@@ -9,26 +8,41 @@ const styles = require('./library-item.css');
 class LibraryItem extends React.Component {
     constructor (props) {
         super(props);
-        bindAll(this, ['handleClick']);
+        bindAll(this, [
+            'handleClick',
+            'handleMouseEnter',
+            'handleMouseLeave'
+        ]);
+    }
+    shouldComponentUpdate (nextProps) {
+        return this.props.iconURL !== nextProps.iconURL;
     }
     handleClick (e) {
         this.props.onSelect(this.props.id);
         e.preventDefault();
     }
+    handleMouseEnter () {
+        this.props.onMouseEnter(this.props.id);
+    }
+    handleMouseLeave () {
+        this.props.onMouseLeave(this.props.id);
+    }
     render () {
         return (
             <Box
-                className={classNames({
-                    [styles.libraryItem]: true,
-                    [styles.isSelected]: this.props.selected
-                })}
+                className={styles.libraryItem}
                 onClick={this.handleClick}
+                onMouseEnter={this.handleMouseEnter}
+                onMouseLeave={this.handleMouseLeave}
             >
-                <Box className={styles.libraryItemImageContainer}>
-                    <img
-                        className={styles.libraryItemImage}
-                        src={this.props.iconURL}
-                    />
+                {/* Layers of wrapping is to prevent layout thrashing on animation */}
+                <Box className={styles.libraryItemImageContainerWrapper}>
+                    <Box className={styles.libraryItemImageContainer}>
+                        <img
+                            className={styles.libraryItemImage}
+                            src={this.props.iconURL}
+                        />
+                    </Box>
                 </Box>
                 <span className={styles.libraryItemName}>{this.props.name}</span>
             </Box>
@@ -40,8 +54,9 @@ LibraryItem.propTypes = {
     iconURL: PropTypes.string.isRequired,
     id: PropTypes.number.isRequired,
     name: PropTypes.string.isRequired,
-    onSelect: PropTypes.func.isRequired,
-    selected: PropTypes.bool.isRequired
+    onMouseEnter: PropTypes.func.isRequired,
+    onMouseLeave: PropTypes.func.isRequired,
+    onSelect: PropTypes.func.isRequired
 };
 
 module.exports = LibraryItem;
diff --git a/src/components/library/library.css b/src/components/library/library.css
index b8147a1736f330d8c5c8cd3ec6ef9e63984bfc6f..73f615191d159177433d271193fb8b7b73f9f47c 100644
--- a/src/components/library/library.css
+++ b/src/components/library/library.css
@@ -1,24 +1,14 @@
+@import "../../css/units.css";
+@import "../../css/colors.css";
+
 .library-scroll-grid {
     display: flex;
+    justify-content: center;
+    align-content: flex-start;
+    background: $ui-pane-gray;
     flex-grow: 1;
-    justify-content: space-between;
     flex-wrap: wrap;
-    overflow-y: scroll;
-    height: calc(100% - 6rem); /* @todo: currently estimate, fix precision */
-
-    /* 
-        Gives sprites a bit of room so they don't get cut off when they grow on hover
-        @todo: sync as a var, with the transform defined on .library-item:hover
-    */
-    padding: 0.15rem; 
-}
-
-.modal-header {
-    width: 100%;
-    margin-bottom: 2rem;
+    overflow-y: auto;
+    height: calc(100% - $library-header-height);
     padding: 0.5rem;
-    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-    font-size: 1.5rem;
-    font-weight: normal;
-    color: #8e8f95;
 }
diff --git a/src/components/library/library.jsx b/src/components/library/library.jsx
index 16327c10c80cb075658e28484465998bf1ce7c4c..ad33743be1e025b8587db9c031df00f6b5378a5b 100644
--- a/src/components/library/library.jsx
+++ b/src/components/library/library.jsx
@@ -10,42 +10,62 @@ const styles = require('./library.css');
 class LibraryComponent extends React.Component {
     constructor (props) {
         super(props);
-        bindAll(this, ['handleSelect']);
-        this.state = {selectedItem: null};
+        bindAll(this, [
+            'handleFilterChange',
+            'handleFilterClear',
+            'handleMouseEnter',
+            'handleMouseLeave',
+            'handleSelect'
+        ]);
+        this.state = {
+            selectedItem: null,
+            filterQuery: ''
+        };
     }
     handleSelect (id) {
-        if (this.state.selectedItem === id) {
-            // Double select: select as the library's value.
-            this.props.onRequestClose();
-            this.props.onItemSelected(this.props.data[id]);
-        } else {
-            if (this.props.onItemChosen) {
-                this.props.onItemChosen(this.props.data[id]);
-            }
-        }
-        this.setState({selectedItem: id});
+        this.props.onRequestClose();
+        this.props.onItemSelected(this.getFilteredData()[id]);
+    }
+    handleMouseEnter (id) {
+        if (this.props.onItemMouseEnter) this.props.onItemMouseEnter(this.getFilteredData()[id]);
+    }
+    handleMouseLeave (id) {
+        if (this.props.onItemMouseLeave) this.props.onItemMouseLeave(this.getFilteredData()[id]);
+    }
+    handleFilterChange (event) {
+        this.setState({filterQuery: event.target.value});
+    }
+    handleFilterClear () {
+        this.setState({filterQuery: ''});
+    }
+    getFilteredData () {
+        return this.props.data.filter(dataItem =>
+            dataItem.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) !== -1);
     }
     render () {
         if (!this.props.visible) return null;
         return (
             <ModalComponent
                 contentLabel={this.props.title}
+                filterQuery={this.state.filterQuery}
                 visible={this.props.visible}
+                onFilterChange={this.handleFilterChange}
+                onFilterClear={this.handleFilterClear}
                 onRequestClose={this.props.onRequestClose}
             >
-                <h1 className={styles.modalHeader}>{this.props.title}</h1>
                 <div className={styles.libraryScrollGrid}>
-                    {this.props.data.map((dataItem, itemId) => {
+                    {this.getFilteredData().map((dataItem, index) => {
                         const scratchURL = dataItem.md5 ?
                             `https://cdn.assets.scratch.mit.edu/internalapi/asset/${dataItem.md5}/get/` :
                             dataItem.rawURL;
                         return (
                             <LibraryItem
                                 iconURL={scratchURL}
-                                id={itemId}
-                                key={`item_${itemId}`}
+                                id={index}
+                                key={`item_${index}`}
                                 name={dataItem.name}
-                                selected={this.state.selectedItem === itemId}
+                                onMouseEnter={this.handleMouseEnter}
+                                onMouseLeave={this.handleMouseLeave}
                                 onSelect={this.handleSelect}
                             />
                         );
@@ -59,6 +79,7 @@ class LibraryComponent extends React.Component {
 LibraryComponent.propTypes = {
     data: PropTypes.arrayOf(
         /* eslint-disable react/no-unused-prop-types, lines-around-comment */
+        // An item in the library
         PropTypes.shape({
             // @todo remove md5/rawURL prop from library, refactor to use storage
             md5: PropTypes.string,
@@ -67,7 +88,8 @@ LibraryComponent.propTypes = {
         })
         /* eslint-enable react/no-unused-prop-types, lines-around-comment */
     ),
-    onItemChosen: PropTypes.func,
+    onItemMouseEnter: PropTypes.func,
+    onItemMouseLeave: PropTypes.func,
     onItemSelected: PropTypes.func,
     onRequestClose: PropTypes.func,
     title: PropTypes.string.isRequired,
diff --git a/src/components/modal/modal.css b/src/components/modal/modal.css
index 25a134f5e689d5d80dae6ca88fe6a7a693dccce3..a72a4f06dbc18c67a672a166b7fddb57fbe66a0f 100644
--- a/src/components/modal/modal.css
+++ b/src/components/modal/modal.css
@@ -11,32 +11,69 @@
     background-color: rgba(0, 0, 0, .75);
 }
 
-.modal-content {
+/* @todo: extract to type: full ? */
+.full-modal-content {
     position: absolute;
     outline: none;
-    overflow-y: scroll;
+    overflow-y: auto;
     -webkit-overflow-scrolling: 'touch';
-    border: 1px solid #ccc;
-    padding: 0;
-    top: 5%;
-    right: 5%;
-    bottom: 5%;
-    left: 5%;
-    border-radius: $space;
     user-select: none;
+    height: 100%;
+    width: 100%;
+    display: flex;
 }
 
-.modal-children {
-    overflow: hidden;
-    height: 100%;
-    z-index: 0;
-    padding: 2rem;
-    background: $ui-pane-gray;
+/*
+    Modal header has 3 items:
+    |filter     title       x|
+
+    Use the same width for both side item containers,
+    so that title remains centered
+*/
+$sides: 20rem;
+
+.header {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: nowrap;
+    justify-content: flex-start;
+    height: $library-header-height;
+
+    box-sizing: border-box;
+    width: 100%;
+    background-color: $blue;
+
+    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+    font-size: 1rem;
+    font-weight: normal;
 }
 
-.close-button {
-    position: absolute;
-    top: 1rem;
-    right: 1rem;
-    z-index: 2;
+.header-item {
+    display: flex;
+    align-items: center;
+    padding: 1rem;
+    text-decoration: none;
+    color: white;
+    user-select: none;
 }
+
+.header-item-filter {
+    display: flex;
+    flex-basis: $sides;
+    justify-content: flex-start;
+}
+
+.header-item-title {
+    flex-grow: 1;
+    flex-shrink: 0;
+    justify-content: center;
+    user-select: none;
+    letter-spacing: 0.4px;
+    cursor: default;
+}
+
+.header-item-close {
+    flex-basis: $sides;
+    justify-content: flex-end;
+}
+
diff --git a/src/components/modal/modal.jsx b/src/components/modal/modal.jsx
index 3a3fc25db56a00488657e121ea6a41c076c3bfc6..2a743bd74447dae5cbf7465a906134551dfeafd0 100644
--- a/src/components/modal/modal.jsx
+++ b/src/components/modal/modal.jsx
@@ -1,9 +1,11 @@
+const classNames = require('classnames');
 const PropTypes = require('prop-types');
 const React = require('react');
 const ReactModal = require('react-modal');
 
 const Box = require('../box/box.jsx');
 const CloseButton = require('../close-button/close-button.jsx');
+const Filter = require('../filter/filter.jsx');
 
 const styles = require('./modal.css');
 
@@ -11,21 +13,44 @@ class ModalComponent extends React.Component {
     render () {
         return (
             <ReactModal
-                className={styles.modalContent}
+                className={styles.fullModalContent}
                 contentLabel={this.props.contentLabel}
                 isOpen={this.props.visible}
                 overlayClassName={styles.modalOverlay}
                 ref={m => (this.modal = m)}
-                onRequestClose={this.props.onRequestClose}
             >
-                <CloseButton
-                    className={styles.closeButton}
-                    onClick={this.props.onRequestClose}
-                />
                 <Box
-                    className={styles.modalChildren}
                     direction="column"
+                    grow={1}
                 >
+                    <div className={styles.header}>
+                        <div className={classNames(styles.headerItem, styles.headerItemFilter)}>
+                            <Filter
+                                filterQuery={this.props.filterQuery}
+                                onChange={this.props.onFilterChange}
+                                onClear={this.props.onFilterClear}
+                            />
+                        </div>
+                        <div
+                            className={classNames(
+                                styles.headerItem,
+                                styles.headerItemTitle
+                            )}
+                        >
+                            {this.props.contentLabel}
+                        </div>
+                        <div
+                            className={classNames(
+                                styles.headerItem,
+                                styles.headerItemClose
+                            )}
+                        >
+                            <CloseButton
+                                size={CloseButton.SIZE_LARGE}
+                                onClick={this.props.onRequestClose}
+                            />
+                        </div>
+                    </div>
                     {this.props.children}
                 </Box>
             </ReactModal>
@@ -36,7 +61,10 @@ class ModalComponent extends React.Component {
 ModalComponent.propTypes = {
     children: PropTypes.node,
     contentLabel: PropTypes.string.isRequired,
-    onRequestClose: PropTypes.func.isRequired,
+    filterQuery: PropTypes.string,
+    onFilterChange: PropTypes.func,
+    onFilterClear: PropTypes.func,
+    onRequestClose: PropTypes.func,
     visible: PropTypes.bool.isRequired
 };
 
diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx
index edd0fb0439a39ab0ccc080c67aa6cd068e603a3a..54b1ebe8a6cb6e3a20b78dadfc8fe7fd3a477bb7 100644
--- a/src/components/monitor/monitor.jsx
+++ b/src/components/monitor/monitor.jsx
@@ -7,7 +7,7 @@ const styles = require('./monitor.css');
 const categories = {
     data: '#FF8C1A',
     sensing: '#5CB1D6',
-    sounds: '#CF63CF',
+    sound: '#CF63CF',
     looks: '#9966FF',
     motion: '#4C97FF'
 };
diff --git a/src/containers/backdrop-library.jsx b/src/containers/backdrop-library.jsx
index b19921b4c77605d0f1f8700d1e70e7e814aea283..62e9cd473a21a9db548868af3bd1d273bcc18359 100644
--- a/src/containers/backdrop-library.jsx
+++ b/src/containers/backdrop-library.jsx
@@ -4,7 +4,7 @@ const React = require('react');
 const VM = require('scratch-vm');
 
 const backdropLibraryContent = require('../lib/libraries/backdrops.json');
-const LibaryComponent = require('../components/library/library.jsx');
+const LibraryComponent = require('../components/library/library.jsx');
 
 
 class BackdropLibrary extends React.Component {
@@ -26,7 +26,7 @@ class BackdropLibrary extends React.Component {
     }
     render () {
         return (
-            <LibaryComponent
+            <LibraryComponent
                 data={backdropLibraryContent}
                 title="Backdrop Library"
                 visible={this.props.visible}
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index 487bed48078b808835bc7209ae5696913fa2d784..a2146d3f8eca07cb5ce5c4fa30cb7eb0b4e23045 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -1,4 +1,5 @@
 const bindAll = require('lodash.bindall');
+const debounce = require('lodash.debounce');
 const defaultsDeep = require('lodash.defaultsdeep');
 const PropTypes = require('prop-types');
 const React = require('react');
@@ -30,6 +31,7 @@ class Blocks extends React.Component {
             'onScriptGlowOff',
             'onBlockGlowOn',
             'onBlockGlowOff',
+            'onTargetsUpdate',
             'onVisualReport',
             'onWorkspaceUpdate',
             'onWorkspaceMetricsChange',
@@ -40,6 +42,7 @@ class Blocks extends React.Component {
             workspaceMetrics: {},
             prompt: null
         };
+        this.onTargetsUpdate = debounce(this.onTargetsUpdate, 100);
     }
     componentDidMount () {
         const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options);
@@ -85,6 +88,7 @@ class Blocks extends React.Component {
         this.props.vm.addListener('BLOCK_GLOW_OFF', this.onBlockGlowOff);
         this.props.vm.addListener('VISUAL_REPORT', this.onVisualReport);
         this.props.vm.addListener('workspaceUpdate', this.onWorkspaceUpdate);
+        this.props.vm.addListener('targetsUpdate', this.onTargetsUpdate);
     }
     detachVM () {
         this.props.vm.removeListener('SCRIPT_GLOW_ON', this.onScriptGlowOn);
@@ -93,8 +97,25 @@ class Blocks extends React.Component {
         this.props.vm.removeListener('BLOCK_GLOW_OFF', this.onBlockGlowOff);
         this.props.vm.removeListener('VISUAL_REPORT', this.onVisualReport);
         this.props.vm.removeListener('workspaceUpdate', this.onWorkspaceUpdate);
+        this.props.vm.removeListener('targetsUpdate', this.onTargetsUpdate);
+    }
+    updateToolboxBlockValue (id, value) {
+        this.workspace
+            .getFlyout()
+            .getWorkspace()
+            .getBlockById(id)
+            .inputList[0]
+            .fieldRow[0]
+            .setValue(value);
+    }
+    onTargetsUpdate () {
+        if (this.props.vm.editingTarget) {
+            ['glide', 'move', 'set'].forEach(prefix => {
+                this.updateToolboxBlockValue(`${prefix}x`, this.props.vm.editingTarget.x.toFixed(0));
+                this.updateToolboxBlockValue(`${prefix}y`, this.props.vm.editingTarget.y.toFixed(0));
+            });
+        }
     }
-
     onWorkspaceMetricsChange () {
         const target = this.props.vm.editingTarget;
         if (target && target.id) {
diff --git a/src/containers/costume-library.jsx b/src/containers/costume-library.jsx
index 1fea39a2dda106f7941084469c81fb0886f32130..f245c098fcc179fc76fb17ba2c9590f449a379cc 100644
--- a/src/containers/costume-library.jsx
+++ b/src/containers/costume-library.jsx
@@ -4,10 +4,10 @@ const React = require('react');
 const VM = require('scratch-vm');
 
 const costumeLibraryContent = require('../lib/libraries/costumes.json');
-const LibaryComponent = require('../components/library/library.jsx');
+const LibraryComponent = require('../components/library/library.jsx');
 
 
-class CostumeLibrary extends React.Component {
+class CostumeLibrary extends React.PureComponent {
     constructor (props) {
         super(props);
         bindAll(this, [
@@ -26,7 +26,7 @@ class CostumeLibrary extends React.Component {
     }
     render () {
         return (
-            <LibaryComponent
+            <LibraryComponent
                 data={costumeLibraryContent}
                 title="Costume Library"
                 visible={this.props.visible}
diff --git a/src/containers/sound-library.jsx b/src/containers/sound-library.jsx
index d478d141a7f6d8c538a03830b7583d4d39ad990c..d82512225f5ccdadb4ce2ec548275d9f7acbfb3d 100644
--- a/src/containers/sound-library.jsx
+++ b/src/containers/sound-library.jsx
@@ -3,25 +3,30 @@ const PropTypes = require('prop-types');
 const React = require('react');
 const VM = require('scratch-vm');
 const AudioEngine = require('scratch-audio');
-const LibaryComponent = require('../components/library/library.jsx');
+const LibraryComponent = require('../components/library/library.jsx');
 
 const soundIcon = require('../components/asset-panel/icon--sound.svg');
 
 const soundLibraryContent = require('../lib/libraries/sounds.json');
 
-class SoundLibrary extends React.Component {
+class SoundLibrary extends React.PureComponent {
     constructor (props) {
         super(props);
         bindAll(this, [
             'handleItemSelected',
-            'handleItemChosen'
+            'handleItemMouseEnter',
+            'handleItemMouseLeave'
         ]);
     }
     componentDidMount () {
         this.audioEngine = new AudioEngine();
         this.player = this.audioEngine.createPlayer();
     }
-    handleItemChosen (soundItem) {
+    componentWillReceiveProps (newProps) {
+        // Stop playing sounds if the library closes without a mouseleave (e.g. by using the escape key)
+        if (this.player && !newProps.visible) this.player.stopAllSounds();
+    }
+    handleItemMouseEnter (soundItem) {
         const md5ext = soundItem._md5;
         const idParts = md5ext.split('.');
         const md5 = idParts[0];
@@ -39,6 +44,9 @@ class SoundLibrary extends React.Component {
             this.player.playSound(soundItem._md5);
         });
     }
+    handleItemMouseLeave () {
+        this.player.stopAllSounds();
+    }
     handleItemSelected (soundItem) {
         const vmSound = {
             format: soundItem.format,
@@ -64,11 +72,12 @@ class SoundLibrary extends React.Component {
         });
 
         return (
-            <LibaryComponent
+            <LibraryComponent
                 data={soundLibraryThumbnailData}
                 title="Sound Library"
                 visible={this.props.visible}
-                onItemChosen={this.handleItemChosen}
+                onItemMouseEnter={this.handleItemMouseEnter}
+                onItemMouseLeave={this.handleItemMouseLeave}
                 onItemSelected={this.handleItemSelected}
                 onRequestClose={this.props.onRequestClose}
             />
diff --git a/src/containers/sprite-library.jsx b/src/containers/sprite-library.jsx
index c08748eb2337728cd862fe051b78b0da03dfe22e..6395b421a7abb0664b334a8839105067548c9f0e 100644
--- a/src/containers/sprite-library.jsx
+++ b/src/containers/sprite-library.jsx
@@ -5,24 +5,70 @@ const VM = require('scratch-vm');
 
 const spriteLibraryContent = require('../lib/libraries/sprites.json');
 
-const LibaryComponent = require('../components/library/library.jsx');
+const LibraryComponent = require('../components/library/library.jsx');
 
-class SpriteLibrary extends React.Component {
+class SpriteLibrary extends React.PureComponent {
     constructor (props) {
         super(props);
         bindAll(this, [
-            'handleItemSelect'
+            'handleItemSelect',
+            'handleMouseEnter',
+            'handleMouseLeave',
+            'rotateCostume',
+            'startRotatingCostumes',
+            'stopRotatingCostumes'
         ]);
+        this.state = {
+            activeSprite: null,
+            costumeIndex: 0,
+            sprites: spriteLibraryContent
+        };
+    }
+    componentWillReceiveProps (newProps) {
+        if (!newProps.visible) clearInterval(this.intervalId);
     }
     handleItemSelect (item) {
         this.props.vm.addSprite2(JSON.stringify(item.json));
     }
+    handleMouseEnter (item) {
+        this.stopRotatingCostumes();
+        this.setState({activeSprite: item}, this.startRotatingCostumes);
+    }
+    handleMouseLeave () {
+        this.stopRotatingCostumes();
+    }
+    startRotatingCostumes () {
+        if (!this.state.activeSprite) return;
+        this.rotateCostume();
+        this.intervalId = setInterval(this.rotateCostume, 300);
+    }
+    stopRotatingCostumes () {
+        this.intervalId = clearInterval(this.intervalId);
+    }
+    rotateCostume () {
+        const costumes = this.state.activeSprite.json.costumes;
+        const nextCostumeIndex = (this.state.costumeIndex + 1) % costumes.length;
+        this.setState({
+            costumeIndex: nextCostumeIndex,
+            sprites: this.state.sprites.map(sprite => {
+                if (sprite.name === this.state.activeSprite.name) {
+                    return {
+                        ...sprite,
+                        md5: sprite.json.costumes[nextCostumeIndex].baseLayerMD5
+                    };
+                }
+                return sprite;
+            })
+        });
+    }
     render () {
         return (
-            <LibaryComponent
-                data={spriteLibraryContent}
+            <LibraryComponent
+                data={this.state.sprites}
                 title="Sprite Library"
                 visible={this.props.visible}
+                onItemMouseEnter={this.handleMouseEnter}
+                onItemMouseLeave={this.handleMouseLeave}
                 onItemSelected={this.handleItemSelect}
                 onRequestClose={this.props.onRequestClose}
             />
diff --git a/src/css/units.css b/src/css/units.css
index ab4393ac1afefb3d770289b67b7c1b8983e6c2b9..3f9aaba17d8319e322b48c817438ed4e8b92458e 100644
--- a/src/css/units.css
+++ b/src/css/units.css
@@ -3,5 +3,7 @@ $space: 0.5rem;
 $sprites-per-row: 5;
 
 $menu-bar-height: 3rem;
-$sprite-info-height: 5.25rem; /* TODO: SpriteInfo isn't explicitly set to this height yet */
+$sprite-info-height: 5.25rem; /* @todo: SpriteInfo isn't explicitly set to this height yet */
 $stage-menu-height: 3rem;
+
+$library-header-height: 4.375rem;
\ No newline at end of file
diff --git a/src/lib/monitor-adapter.js b/src/lib/monitor-adapter.js
new file mode 100644
index 0000000000000000000000000000000000000000..a648a7d93f127c9eedf51c590e3ba55812cfffb2
--- /dev/null
+++ b/src/lib/monitor-adapter.js
@@ -0,0 +1,26 @@
+/**
+ * Convert monitors from VM format to what the GUI needs to render.
+ * - Convert opcode to a label and a category
+ * - Add missing XY position data if needed
+ */
+const OpcodeLabels = require('./opcode-labels.js');
+const MonitorRecord = require('./monitor-record');
+
+const PADDING = 5;
+const MONITOR_HEIGHT = 23;
+
+const isUndefined = a => typeof a === 'undefined';
+
+module.exports = function ({id, opcode, params, value, x, y}) {
+    let {label, category, labelFn} = OpcodeLabels(opcode);
+
+    // Use labelFn if provided for dynamic labelling (e.g. variables)
+    if (!isUndefined(labelFn)) label = labelFn(params);
+
+    // @todo fix layout
+    const index = 0;
+    if (isUndefined(x)) x = PADDING;
+    if (isUndefined(y)) y = PADDING + (index * (PADDING + MONITOR_HEIGHT));
+
+    return MonitorRecord({id, label, category, value, x, y});
+};
diff --git a/src/lib/opcode-labels.js b/src/lib/opcode-labels.js
new file mode 100644
index 0000000000000000000000000000000000000000..bebe14743630de643343e9b88318b88fdf312b07
--- /dev/null
+++ b/src/lib/opcode-labels.js
@@ -0,0 +1,75 @@
+const opcodeMap = {
+    // Motion
+    motion_direction: {
+        category: 'motion',
+        label: 'direction'
+    },
+    motion_xposition: {
+        category: 'motion',
+        label: 'x position'
+    },
+    motion_yposition: {
+        category: 'motion',
+        label: 'y position'
+    },
+
+    // Looks
+    looks_size: {
+        category: 'looks',
+        label: 'size'
+    },
+    looks_costumeorder: {
+        category: 'looks',
+        label: 'costume #'
+    },
+    looks_backdroporder: {
+        category: 'looks',
+        label: 'backdrop #'
+    },
+    looks_backdropname: {
+        category: 'looks',
+        label: 'backdrop name'
+    },
+
+    // Data
+    data_variable: {
+        category: 'data',
+        labelFn: params => params.VARIABLE
+    },
+
+    // Sound
+    sound_volume: {
+        category: 'sound',
+        label: 'volume'
+    },
+    sound_tempo: {
+        category: 'sound',
+        label: 'tempo'
+    },
+
+    // Sensing
+    sensing_loudness: {
+        category: 'sensing',
+        label: 'loudness'
+    },
+    sensing_of: {
+        category: 'sensing',
+        labelFn: params => `${params.PROPERTY} of ${params.OBJECT}`
+    },
+    sensing_current: {
+        category: 'sensing',
+        labelFn: params => params.CURRENTMENU.toLowerCase()
+    },
+    sensing_timer: {
+        category: 'sensing',
+        label: 'timer'
+    }
+};
+
+module.exports = function (opcode) {
+    if (opcode in opcodeMap) return opcodeMap[opcode];
+    return {
+        category: 'data',
+        label: opcode
+    };
+};
diff --git a/src/reducers/monitors.js b/src/reducers/monitors.js
index e21e67a0193a5a1d40a9690a877d05cd75ca0aa4..3db22b203718efd7c9bf5790a6daef70c7816426 100644
--- a/src/reducers/monitors.js
+++ b/src/reducers/monitors.js
@@ -1,3 +1,5 @@
+const monitorAdapter = require('../lib/monitor-adapter.js');
+
 const UPDATE_MONITORS = 'scratch-gui/monitors/UPDATE_MONITORS';
 const {OrderedMap} = require('immutable');
 
@@ -7,7 +9,7 @@ const reducer = function (state, action) {
     if (typeof state === 'undefined') state = initialState;
     switch (action.type) {
     case UPDATE_MONITORS:
-        return action.monitors;
+        return action.monitors.map(monitorAdapter);
     default:
         return state;
     }