diff --git a/__tests__/src/components/WindowViewer.test.js b/__tests__/src/components/WindowViewer.test.js
index 8845af7c71683260d335103248879f8234dd2c31..2bed4303aa1be9c591fa3df8d8a0b33db9605b0d 100644
--- a/__tests__/src/components/WindowViewer.test.js
+++ b/__tests__/src/components/WindowViewer.test.js
@@ -26,4 +26,7 @@ describe('WindowViewer', () => {
     // do not effectively find elements (even though they are there)
     expect(wrapper.render().find('.openseadragon-canvas').length).toBe(1);
   });
+  it('has navigation controls', () => {
+    expect(wrapper.find('.mirador-osd-navigation').length).toBe(1);
+  });
 });
diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..3709ab33b168ac179a9c375ab803db1fa3d09016
--- /dev/null
+++ b/src/components/OpenSeadragonViewer.js
@@ -0,0 +1,89 @@
+import React, { Component, Fragment } from 'react';
+import PropTypes from 'prop-types';
+import OpenSeadragon from 'openseadragon';
+import miradorWithPlugins from '../lib/miradorWithPlugins';
+import ns from '../config/css-ns';
+
+/**
+ * Represents a OpenSeadragonViewer in the mirador workspace. Responsible for mounting
+ * and rendering OSD.
+ */
+class OpenSeadragonViewer extends Component {
+  /**
+   * @param {Object} props
+   */
+  constructor(props) {
+    super(props);
+
+    this.viewer = null;
+    this.ref = React.createRef();
+  }
+
+  /**
+   * React lifecycle event
+   */
+  componentDidMount() {
+    const { tileSources } = this.props;
+    if (!this.ref.current) {
+      return;
+    }
+    this.viewer = new OpenSeadragon({
+      id: this.ref.current.id,
+      preserveViewport: true,
+      blendTime: 0.1,
+      alwaysBlend: false,
+      showNavigationControl: false,
+    });
+    tileSources.forEach(tileSource => this.addTileSource(tileSource));
+  }
+
+  /**
+   */
+  componentDidUpdate() {
+    const { tileSources } = this.props;
+    tileSources.forEach(tileSource => this.addTileSource(tileSource));
+  }
+
+  /**
+   */
+  componentWillUnmount() {
+    this.viewer.removeAllHandlers();
+  }
+
+  /**
+   */
+  addTileSource(tileSource) {
+    this.viewer.addTiledImage({
+      tileSource,
+      success: (event) => {
+      },
+    });
+  }
+
+  /**
+   * Renders things
+   */
+  render() {
+    const { window } = this.props;
+    return (
+      <Fragment>
+        <div
+          className={ns('osd-container')}
+          id={`${window.id}-osd`}
+          ref={this.ref}
+        />
+      </Fragment>
+    );
+  }
+}
+
+OpenSeadragonViewer.defaultProps = {
+  tileSources: [],
+};
+
+OpenSeadragonViewer.propTypes = {
+  tileSources: PropTypes.arrayOf(PropTypes.object),
+  window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+};
+
+export default miradorWithPlugins(OpenSeadragonViewer);
diff --git a/src/components/ViewerNavigation.js b/src/components/ViewerNavigation.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6d989f143db068813cd8de574dcf5242ede5b6a
--- /dev/null
+++ b/src/components/ViewerNavigation.js
@@ -0,0 +1,91 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import miradorWithPlugins from '../lib/miradorWithPlugins';
+import { actions } from '../store';
+import ns from '../config/css-ns';
+
+/**
+ */
+class ViewerNavigation extends Component {
+  /**
+   */
+  constructor(props) {
+    super(props);
+
+    this.nextCanvas = this.nextCanvas.bind(this);
+    this.previousCanvas = this.previousCanvas.bind(this);
+  }
+
+  /**
+   */
+  nextCanvas() {
+    const { window, nextCanvas } = this.props;
+    if (this.hasNextCanvas()) nextCanvas(window.id);
+  }
+
+  /**
+   */
+  hasNextCanvas() {
+    const { window, canvases } = this.props;
+    return window.canvasIndex < canvases.length - 1;
+  }
+
+  /**
+   */
+  previousCanvas() {
+    const { window, previousCanvas } = this.props;
+    if (this.hasPreviousCanvas()) previousCanvas(window.id);
+  }
+
+  /**
+   */
+  hasPreviousCanvas() {
+    const { window } = this.props;
+    return window.canvasIndex > 0;
+  }
+
+  /**
+   * Renders things
+   */
+  render() {
+    return (
+      <div className={ns('osd-navigation')}>
+        <button
+          type="button"
+          disabled={!this.hasPreviousCanvas()}
+          onClick={this.previousCanvas}
+        >
+        &#8249;
+        </button>
+        <button
+          type="button"
+          disabled={!this.hasNextCanvas()}
+          onClick={this.nextCanvas}
+        >
+        &#8250;
+        </button>
+      </div>
+    );
+  }
+}
+
+ViewerNavigation.propTypes = {
+  canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
+  nextCanvas: PropTypes.func.isRequired,
+  previousCanvas: PropTypes.func.isRequired,
+  window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+};
+
+
+/**
+ * mapDispatchToProps - used to hook up connect to action creators
+ * @memberof ManifestForm
+ * @private
+ */
+const mapDispatchToProps = dispatch => ({
+  nextCanvas: windowId => dispatch(actions.nextCanvas(windowId)),
+  previousCanvas: windowId => dispatch(actions.previousCanvas(windowId)),
+});
+
+export default connect(null, mapDispatchToProps)(miradorWithPlugins(ViewerNavigation));
diff --git a/src/components/WindowViewer.js b/src/components/WindowViewer.js
index 0a58b454344197e3835b234cc2d48a6dd81cb2b7..f0624e5f25f26e4333c8d8dabe98984909eb93d7 100644
--- a/src/components/WindowViewer.js
+++ b/src/components/WindowViewer.js
@@ -1,13 +1,13 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
 import PropTypes from 'prop-types';
-import OpenSeaDragon from 'openseadragon';
 import fetch from 'node-fetch';
 import miradorWithPlugins from '../lib/miradorWithPlugins';
-import ns from '../config/css-ns';
+import OpenSeadragonViewer from './OpenSeadragonViewer';
+import ViewerNavigation from './ViewerNavigation';
 
 /**
  * Represents a WindowViewer in the mirador workspace. Responsible for mounting
- * and rendering OSD.
+ * OSD and Navigation
  */
 class WindowViewer extends Component {
   /**
@@ -16,47 +16,43 @@ class WindowViewer extends Component {
   constructor(props) {
     super(props);
 
-    this.ref = React.createRef();
-    this.viewer = null;
+    const { manifest } = this.props;
+    this.canvases = manifest.manifestation.getSequences()[0].getCanvases();
+    this.state = {
+      tileSources: [],
+    };
   }
 
   /**
-   * React lifecycle event
+   * componentDidMount - React lifecycle method
+   * Request the initial canvas on mount
    */
   componentDidMount() {
-    const { manifest } = this.props;
-    if (!this.ref.current) {
-      return false;
+    this.requestAndUpdateTileSources();
+  }
+
+  /**
+   * componentDidUpdate - React lifecycle method
+   * Request a new canvas if it is needed
+   */
+  componentDidUpdate(prevProps) {
+    const { window } = this.props;
+    if (prevProps.window.canvasIndex !== window.canvasIndex) {
+      this.requestAndUpdateTileSources();
     }
-    this.viewer = OpenSeaDragon({
-      id: this.ref.current.id,
-      showNavigationControl: false,
-    });
-    const that = this;
-    fetch(`${manifest.manifestation.getSequences()[0].getCanvases()[0].getImages()[0].getResource().getServices()[0].id}/info.json`)
+  }
+
+  /**
+   */
+  requestAndUpdateTileSources() {
+    const { window } = this.props;
+    fetch(`${this.canvases[window.canvasIndex].getImages()[0].getResource().getServices()[0].id}/info.json`)
       .then(response => response.json())
       .then((json) => {
-        that.viewer.addTiledImage({
-          tileSource: json,
-          success: (event) => {
-            const tiledImage = event.item;
-
-            /**
-             * A callback for the tile after its drawn
-             * @param  {[type]} e event object
-             */
-            const tileDrawnHandler = (e) => {
-              if (e.tiledImage === tiledImage) {
-                that.viewer.removeHandler('tile-drawn', tileDrawnHandler);
-                that.ref.current.style.display = 'block';
-              }
-            };
-            that.viewer.addHandler('tile-drawn', tileDrawnHandler);
-          },
+        this.setState({
+          tileSources: [json],
         });
-      })
-      .catch(error => console.log(error));
-    return false;
+      });
   }
 
   /**
@@ -64,13 +60,12 @@ class WindowViewer extends Component {
    */
   render() {
     const { window } = this.props;
+    const { tileSources } = this.state;
     return (
-      <div
-        className={ns('osd-container')}
-        style={{ display: 'none' }}
-        id={`${window.id}-osd`}
-        ref={this.ref}
-      />
+      <Fragment>
+        <OpenSeadragonViewer tileSources={tileSources} window={window} />
+        <ViewerNavigation window={window} canvases={this.canvases} />
+      </Fragment>
     );
   }
 }
diff --git a/src/styles/index.scss b/src/styles/index.scss
index 62ab9468ed4d4187cc3a451b3eb16094b3a4c239..f37eb5dc2f4a4d823401ae7b2cf93931f407bbd7 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -53,4 +53,12 @@ body {
     top: 0;
     width: 100%;
   }
+
+  &-osd-navigation {
+    bottom: 10px;
+    left: 0;
+    position: absolute;
+    right: 0;
+    text-align: center;
+  }
 }