diff --git a/package.json b/package.json
index e6458829d9250f47ab4ce9016b304672b737469d..0e03879a3a9b71cdfabf434aee6eff9a475872a9 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
     "lodash.pick": "4.4.0",
     "minilog": "3.1.0",
     "mkdirp": "^0.5.1",
+    "platform": "^1.3.4",
     "postcss-import": "^11.0.0",
     "postcss-loader": "^2.0.5",
     "postcss-simple-vars": "^4.0.0",
diff --git a/src/components/browser-modal/browser-modal.css b/src/components/browser-modal/browser-modal.css
new file mode 100644
index 0000000000000000000000000000000000000000..52773e08f1b90e4bc5a453b730ddfb04225b5af6
--- /dev/null
+++ b/src/components/browser-modal/browser-modal.css
@@ -0,0 +1,70 @@
+@import "../../css/colors.css";
+@import "../../css/units.css";
+@import "../../css/typography.css";
+
+.modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 1000;
+    background-color: hsla(215, 100%, 65%, .9);
+}
+
+.modal-content {
+    margin: 100px auto;
+    outline: none;
+    border: .25rem solid hsla(0, 100%, 100%, .25);
+    padding: 0;
+    border-radius: $space;
+    user-select: none;
+    width: 500px;
+
+    color: $text-primary;
+    overflow: hidden;
+}
+
+.illustration {
+    width: 100%;
+    height: 208px;
+    background-color: $motion-primary;
+    background-image: url('./unsupported.png');
+    background-size: cover;
+}
+
+.body {
+    background: $ui-pane-gray;
+    padding: 1.5rem 2.25rem;
+    text-align: center;
+}
+
+/* Confirmation buttons at the bottom of the modal */
+.button-row {
+    margin: 1.5rem 0;
+    font-weight: bolder;
+    text-align: right;
+    display: flex;
+    justify-content: center;
+}
+
+.button-row button {
+    border: 1px solid $motion-primary;
+    border-radius: 0.25rem;
+    padding: 0.5rem 2rem;
+    background: $motion-primary;
+    color: white;
+    font-weight: bold;
+    font-size: 0.875rem;
+}
+
+.faq-link-text {
+    margin: 2rem 0 .5rem 0;
+    font-size: .875rem;
+    color: $text-primary;
+}
+
+.faq-link {
+    color: $motion-primary;
+    text-decoration: none;
+}
diff --git a/src/components/browser-modal/browser-modal.jsx b/src/components/browser-modal/browser-modal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c410f7f62d26e359a300e2bb80072e8ed9bcf248
--- /dev/null
+++ b/src/components/browser-modal/browser-modal.jsx
@@ -0,0 +1,84 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactModal from 'react-modal';
+import Box from '../box/box.jsx';
+import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
+
+import styles from './browser-modal.css';
+
+const messages = defineMessages({
+    label: {
+        id: 'gui.unsupportedBrowser.label',
+        defaultMessage: 'Internet Explorer is not supported',
+        description: ''
+    }
+});
+
+const BrowserModal = ({intl, ...props}) => (
+    <ReactModal
+        isOpen
+        className={styles.modalContent}
+        contentLabel={intl.formatMessage({...messages.label})}
+        overlayClassName={styles.modalOverlay}
+        onRequestClose={props.onBack}
+    >
+        <Box className={styles.illustration} />
+
+        <Box className={styles.body}>
+            <h2>
+                <FormattedMessage {...messages.label} />
+            </h2>
+            <p>
+                { /* eslint-disable max-len */ }
+                <FormattedMessage
+                    defaultMessage="We're very sorry, but Scratch 3.0 does not support Internet Explorer. We recommend trying a newer browser such as Google Chrome, Mozilla Firefox, or Microsoft Edge."
+                    description="Unsupported browser description"
+                    id="gui.unsupportedBrowser.description"
+                />
+                { /* eslint-enable max-len */ }
+            </p>
+
+            <Box className={styles.buttonRow}>
+                <button
+                    className={styles.backButton}
+                    onClick={props.onBack}
+                >
+                    <FormattedMessage
+                        defaultMessage="Back"
+                        description="Label for button go back when browser is unsupported"
+                        id="gui.unsupportedBrowser.back"
+                    />
+                </button>
+
+            </Box>
+            <div className={styles.faqLinkText}>
+                <FormattedMessage
+                    defaultMessage="To learn more, go to the {previewFaqLink}."
+                    description="Scratch 3.0 FAQ description"
+                    id="gui.unsupportedBrowser.previewfaq"
+                    values={{
+                        previewFaqLink: (
+                            <a
+                                className={styles.faqLink}
+                                href="//scratch.mit.edu/preview-faq"
+                            >
+                                <FormattedMessage
+                                    defaultMessage="preview FAQ"
+                                    description="link to Scratch 3.0 FAQ page"
+                                    id="gui.unsupportedBrowser.previewfaqlink"
+                                />
+                            </a>
+                        )
+                    }}
+                />
+            </div>
+        </Box>
+    </ReactModal>
+);
+
+BrowserModal.propTypes = {
+    intl: intlShape.isRequired,
+    onBack: PropTypes.func.isRequired
+};
+
+export default injectIntl(BrowserModal);
diff --git a/src/components/browser-modal/unsupported.png b/src/components/browser-modal/unsupported.png
new file mode 100644
index 0000000000000000000000000000000000000000..d8fbab2f10ad965933527e6af8ecdf56cb3e3933
Binary files /dev/null and b/src/components/browser-modal/unsupported.png differ
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 639c8cbd30c12432512ba5886bc92f1931b02cba..e5164ff5734cb7fc4c12d76269f674c4f17222ad 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -13,6 +13,7 @@ import SoundTab from '../../containers/sound-tab.jsx';
 import StageHeader from '../../containers/stage-header.jsx';
 import Stage from '../../containers/stage.jsx';
 import {FormattedMessage} from 'react-intl';
+import PreviewModal from '../../containers/preview-modal.jsx';
 
 import Box from '../box/box.jsx';
 import IconButton from '../icon-button/icon-button.jsx';
@@ -36,6 +37,7 @@ const GUIComponent = props => {
         children,
         enableExtensions,
         vm,
+        previewInfoVisible,
         onExtensionButtonClick,
         onTabSelect,
         tabIndex,
@@ -63,6 +65,9 @@ const GUIComponent = props => {
             className={styles.pageWrapper}
             {...componentProps}
         >
+            {previewInfoVisible ? (
+                <PreviewModal />
+            ) : null}
             <MenuBar />
             <Box className={styles.bodyWrapper}>
                 <Box className={styles.flexWrapper}>
@@ -141,6 +146,7 @@ GUIComponent.propTypes = {
     enableExtensions: PropTypes.bool,
     onExtensionButtonClick: PropTypes.func,
     onTabSelect: PropTypes.func,
+    previewInfoVisible: PropTypes.bool,
     tabIndex: PropTypes.number,
     vm: PropTypes.instanceOf(VM).isRequired
 };
diff --git a/src/components/preview-modal/happy-cat.svg b/src/components/preview-modal/happy-cat.svg
new file mode 100644
index 0000000000000000000000000000000000000000..9a04aa18da28b7a787ad4217c81beeb806182578
Binary files /dev/null and b/src/components/preview-modal/happy-cat.svg differ
diff --git a/src/components/preview-modal/info.png b/src/components/preview-modal/info.png
new file mode 100644
index 0000000000000000000000000000000000000000..5b17f14bf158ad4766b047ec19a9ccb2c1d63bb6
Binary files /dev/null and b/src/components/preview-modal/info.png differ
diff --git a/src/components/preview-modal/preview-modal.css b/src/components/preview-modal/preview-modal.css
new file mode 100644
index 0000000000000000000000000000000000000000..b630741ec5c6fa58d627482a057b8780085979d5
--- /dev/null
+++ b/src/components/preview-modal/preview-modal.css
@@ -0,0 +1,88 @@
+@import "../../css/colors.css";
+@import "../../css/units.css";
+@import "../../css/typography.css";
+
+.modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 1000;
+    background-color: hsla(215, 100%, 65%, .9);
+}
+
+.modal-content {
+    margin: 100px auto;
+    outline: none;
+    border: .25rem solid hsla(0, 100%, 100%, .25);
+    padding: 0;
+    border-radius: $space;
+    user-select: none;
+    width: 500px;
+
+    color: $text-primary;
+    overflow: hidden;
+}
+
+.illustration {
+    width: 100%;
+    height: 208px;
+    background-color: $motion-primary;
+    background-image: url('./info.png');
+    background-size: cover;
+}
+
+.body {
+    background: $ui-pane-gray;
+    padding: 1.5rem 2.25rem;
+    text-align: center;
+}
+
+/* Confirmation buttons at the bottom of the modal */
+.button-row {
+    margin: 1.5rem 0;
+    font-weight: bolder;
+    text-align: right;
+    display: flex;
+    justify-content: center;
+}
+
+.button-row button {
+    border: 1px solid $motion-primary;
+    border-radius: 0.25rem;
+    padding: 0.5rem 2rem;
+    background: white;
+    font-weight: bold;
+    font-size: .875rem;
+}
+
+.button-row button.ok-button {
+    background: $motion-primary;
+    color: white;
+}
+
+.button-row button.no-button {
+    color: $motion-primary;
+}
+.button-row button + button {
+    margin-left: 0.5rem;
+}
+
+.cat-icon {
+    margin-left: .125rem;
+    width: 1.5rem;
+    height: 1.5rem;
+    vertical-align: middle;
+}
+
+.faq-link-text {
+    margin: 2rem 0 .5rem 0;
+    font-size: .875rem;
+    color: $text-primary;
+}
+
+.faq-link {
+    color: $motion-primary;
+    text-decoration: none;
+}
diff --git a/src/components/preview-modal/preview-modal.jsx b/src/components/preview-modal/preview-modal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0c1651a2a232c6f78f89df6bdbfc29319250b9fb
--- /dev/null
+++ b/src/components/preview-modal/preview-modal.jsx
@@ -0,0 +1,106 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactModal from 'react-modal';
+import Box from '../box/box.jsx';
+import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
+
+import styles from './preview-modal.css';
+import catIcon from './happy-cat.svg';
+
+const messages = defineMessages({
+    label: {
+        id: 'gui.previewInfo.label',
+        defaultMessage: 'Try Scratch 3.0',
+        description: 'Scratch 3.0 modal label - for accessibility'
+    }
+});
+
+const PreviewModal = ({intl, ...props}) => (
+    <ReactModal
+        isOpen
+        className={styles.modalContent}
+        contentLabel={intl.formatMessage({...messages.label})}
+        overlayClassName={styles.modalOverlay}
+        onRequestClose={props.onTryIt}
+    >
+        <Box className={styles.illustration} />
+
+        <Box className={styles.body}>
+            <h2>
+                <FormattedMessage
+                    defaultMessage="Welcome to the Scratch 3.0 Preview"
+                    description="Header for Preview Info Modal"
+                    id="gui.previewInfo.welcome"
+                />
+            </h2>
+            <p>
+                <FormattedMessage
+                    defaultMessage="We're working on the next generation of Scratch. We're excited for you to try it!"
+                    description="Invitation to try 3.0 preview"
+                    id="gui.previewInfo.invitation"
+                />
+            </p>
+
+            <Box className={styles.buttonRow}>
+                <button
+                    className={styles.noButton}
+                    onClick={props.onCancel}
+                >
+                    <FormattedMessage
+                        defaultMessage="Not Now"
+                        description="Label for button to back out of trying Scratch 3.0 preview"
+                        id="gui.previewInfo.notnow"
+                    />
+                </button>
+                <button
+                    className={styles.okButton}
+                    title="tryit"
+                    onClick={props.onTryIt}
+                >
+                    <FormattedMessage
+                        defaultMessage="Try It! {caticon}"
+                        description="Label for button to try Scratch 3.0 preview"
+                        id="gui.previewModal.tryit"
+                        values={{
+                            caticon: (
+                                <img
+                                    className={styles.catIcon}
+                                    src={catIcon}
+                                />
+                            )
+                        }}
+                    />
+                </button>
+            </Box>
+            <Box className={styles.faqLinkText}>
+                <FormattedMessage
+                    defaultMessage="To learn more, go to the {previewFaqLink}."
+                    description="Invitation to try 3.0 preview"
+                    id="gui.previewInfo.previewfaq"
+                    values={{
+                        previewFaqLink: (
+                            <a
+                                className={styles.faqLink}
+                                href="//scratch.mit.edu/preview-faq"
+                            >
+                                <FormattedMessage
+                                    defaultMessage="Preview FAQ"
+                                    description="link to Scratch 3.0 preview FAQ page"
+                                    id="gui.previewInfo.previewfaqlink"
+                                />
+                            </a>
+                        )
+                    }}
+                />
+            </Box>
+        </Box>
+    </ReactModal>
+);
+
+PreviewModal.propTypes = {
+    intl: intlShape.isRequired,
+    onCancel: PropTypes.func.isRequired,
+    onTryIt: PropTypes.func.isRequired
+};
+
+export default injectIntl(PreviewModal);
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index 9de0e3616691b9a931ebf3d10a4ac7bebf35feb6..1ed5c4165e4c4aa82ee5cd424cced84e63a8b923 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -60,13 +60,16 @@ class GUI extends React.Component {
 
 GUI.propTypes = {
     ...GUIComponent.propTypes,
+    previewInfoVisible: PropTypes.bool,
     projectData: PropTypes.string,
     vm: PropTypes.instanceOf(VM)
 };
 
 GUI.defaultProps = GUIComponent.defaultProps;
 
-const mapStateToProps = () => ({});
+const mapStateToProps = state => ({
+    previewInfoVisible: state.modals.previewInfo
+});
 
 const mapDispatchToProps = dispatch => ({
     onExtensionButtonClick: () => dispatch(openExtensionLibrary())
diff --git a/src/containers/preview-modal.jsx b/src/containers/preview-modal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..db913e9ea709c170c1c94c609e9ce9c93e5f12ff
--- /dev/null
+++ b/src/containers/preview-modal.jsx
@@ -0,0 +1,68 @@
+import bindAll from 'lodash.bindall';
+import PropTypes from 'prop-types';
+import React from 'react';
+import {connect} from 'react-redux';
+import platform from 'platform';
+
+import PreviewModalComponent from '../components/preview-modal/preview-modal.jsx';
+import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx';
+
+import {
+    closePreviewInfo
+} from '../reducers/modals';
+
+class PreviewModal extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, [
+            'handleTryIt',
+            'handleCancel'
+        ]);
+
+        this.state = {
+            previewing: false
+        };
+    }
+    handleTryIt () {
+        this.setState({previewing: true});
+        this.props.onTryIt();
+    }
+    handleCancel () {
+        window.history.back();
+    }
+    supportedBrowser () {
+        if (platform.name === 'IE') {
+            return false;
+        }
+        return true;
+    }
+    render () {
+        return (this.supportedBrowser() ?
+            <PreviewModalComponent
+                previewing={this.state.previewing}
+                onCancel={this.handleCancel}
+                onTryIt={this.handleTryIt}
+            /> :
+            <BrowserModalComponent
+                onBack={this.handleCancel}
+            />
+        );
+    }
+}
+
+PreviewModal.propTypes = {
+    onTryIt: PropTypes.func
+};
+
+const mapStateToProps = () => ({});
+
+const mapDispatchToProps = dispatch => ({
+    onTryIt: () => {
+        dispatch(closePreviewInfo());
+    }
+});
+
+export default connect(
+    mapStateToProps,
+    mapDispatchToProps
+)(PreviewModal);
diff --git a/src/css/typography.css b/src/css/typography.css
new file mode 100644
index 0000000000000000000000000000000000000000..348e1df5a4aa0b6a9adac4b5f92a50e14ff9146b
--- /dev/null
+++ b/src/css/typography.css
@@ -0,0 +1,13 @@
+body {
+    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+h2 {
+    font-size: 1.5rem;
+    font-weight: bold;
+}
+
+p {
+    font-size: 1rem;
+    line-height: 1.5em;
+}
diff --git a/src/reducers/modals.js b/src/reducers/modals.js
index 55a55e4fea6a31e78334f6ee62aa45ec32d296a8..24e7269ecef206efd40118369a0c97c4f91b0fee 100644
--- a/src/reducers/modals.js
+++ b/src/reducers/modals.js
@@ -7,11 +7,14 @@ const MODAL_SOUND_LIBRARY = 'soundLibrary';
 const MODAL_SPRITE_LIBRARY = 'spriteLibrary';
 const MODAL_SOUND_RECORDER = 'soundRecorder';
 const MODAL_EXTENSION_LIBRARY = 'extensionLibrary';
+const MODAL_PREVIEW_INFO = 'previewInfo';
+
 
 const initialState = {
     [MODAL_BACKDROP_LIBRARY]: false,
     [MODAL_COSTUME_LIBRARY]: false,
     [MODAL_EXTENSION_LIBRARY]: false,
+    [MODAL_PREVIEW_INFO]: true,
     [MODAL_SOUND_LIBRARY]: false,
     [MODAL_SPRITE_LIBRARY]: false,
     [MODAL_SOUND_RECORDER]: false
@@ -62,6 +65,9 @@ const openSoundRecorder = function () {
 const openExtensionLibrary = function () {
     return openModal(MODAL_EXTENSION_LIBRARY);
 };
+const openPreviewInfo = function () {
+    return openModal(MODAL_PREVIEW_INFO);
+};
 const closeBackdropLibrary = function () {
     return closeModal(MODAL_BACKDROP_LIBRARY);
 };
@@ -71,6 +77,9 @@ const closeCostumeLibrary = function () {
 const closeExtensionLibrary = function () {
     return closeModal(MODAL_EXTENSION_LIBRARY);
 };
+const closePreviewInfo = function () {
+    return closeModal(MODAL_PREVIEW_INFO);
+};
 const closeSpriteLibrary = function () {
     return closeModal(MODAL_SPRITE_LIBRARY);
 };
@@ -85,12 +94,14 @@ export {
     openExtensionLibrary,
     openBackdropLibrary,
     openCostumeLibrary,
+    openPreviewInfo,
     openSoundLibrary,
     openSpriteLibrary,
     openSoundRecorder,
     closeBackdropLibrary,
     closeCostumeLibrary,
     closeExtensionLibrary,
+    closePreviewInfo,
     closeSpriteLibrary,
     closeSoundLibrary,
     closeSoundRecorder
diff --git a/test/integration/test.js b/test/integration/test.js
index aa616e5dcf9f4688241ece0d47418466e0268ec6..4653018e9780dd119a1330ecc5aca86d12e4d24e 100644
--- a/test/integration/test.js
+++ b/test/integration/test.js
@@ -37,6 +37,7 @@ describe('costumes, sounds and variables', () => {
 
     test('Blocks report when clicked in the toolbox', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('Blocks');
         await clickText('Operators', blocksTabScope);
         await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
@@ -48,6 +49,7 @@ describe('costumes, sounds and variables', () => {
 
     test('Switching sprites updates the block menus', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('Sound', blocksTabScope);
         await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
         // "Meow" sound block should be visible
@@ -64,6 +66,7 @@ describe('costumes, sounds and variables', () => {
 
     test('Adding a costume', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('Costumes');
         await clickText('Add Costume');
         const el = await findByXpath("//input[@placeholder='what are you looking for?']");
@@ -76,6 +79,7 @@ describe('costumes, sounds and variables', () => {
 
     test('Adding a backdrop', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('Add Backdrop');
         const el = await findByXpath("//input[@placeholder='what are you looking for?']");
         await el.sendKeys('blue');
@@ -86,6 +90,7 @@ describe('costumes, sounds and variables', () => {
 
     test('Adding a sound', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('Sounds');
 
         // Delete the sound
@@ -142,6 +147,7 @@ describe('costumes, sounds and variables', () => {
             .setSize(1920, 1080);
         const projectId = '96708228';
         await loadUri(`${uri}#${projectId}`);
+        await clickXpath('//button[@title="tryit"]');
         await new Promise(resolve => setTimeout(resolve, 2000));
         await clickXpath('//img[@title="Full Screen Control"]');
         await clickXpath('//img[@title="Go"]');
@@ -158,6 +164,7 @@ describe('costumes, sounds and variables', () => {
 
     test('Creating variables', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('Blocks');
         await clickText('Variables', blocksTabScope);
         await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
@@ -187,6 +194,7 @@ describe('costumes, sounds and variables', () => {
 
     test('Importing extensions', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('Blocks');
         await clickText('Extensions');
         await clickText('Pen', modalScope); // Modal closes
@@ -211,6 +219,7 @@ describe('costumes, sounds and variables', () => {
     test('Deleting only sprite does not crash', async () => {
         const spriteTileContext = '*[starts-with(@class,"react-contextmenu-wrapper")]';
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
         await rightClickText('Sprite1', spriteTileContext);
         await clickText('delete', spriteTileContext);
@@ -224,6 +233,7 @@ describe('costumes, sounds and variables', () => {
 
     test('Custom procedures', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('My Blocks');
         await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
         await clickText('Make a Block');
@@ -244,6 +254,7 @@ describe('costumes, sounds and variables', () => {
     // "Coming Soon"
     test.skip('Localization', async () => {
         await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
         await clickText('Blocks');
         await clickText('Extensions');
         await clickText('Pen', modalScope); // Modal closes