diff --git a/__tests__/src/components/WindowSideBarCanvasPanel.test.js b/__tests__/src/components/WindowSideBarCanvasPanel.test.js
index e01de7a023d63ddbb5747e67434e9a7e6450650e..cbd5a3ad1011b5d659eb4b57714c588229498ea1 100644
--- a/__tests__/src/components/WindowSideBarCanvasPanel.test.js
+++ b/__tests__/src/components/WindowSideBarCanvasPanel.test.js
@@ -3,6 +3,8 @@ import { shallow } from 'enzyme';
 import List from '@material-ui/core/List';
 import ListItem from '@material-ui/core/ListItem';
 import Typography from '@material-ui/core/Typography';
+import ExpansionPanel from '@material-ui/core/ExpansionPanel';
+import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
 import manifesto from 'manifesto.js';
 import { WindowSideBarCanvasPanel } from '../../../src/components/WindowSideBarCanvasPanel';
 import { CanvasThumbnail } from '../../../src/components/CanvasThumbnail';
@@ -65,6 +67,57 @@ describe('WindowSideBarCanvasPanel', () => {
     expect(wrapper.find(CanvasThumbnail).length).toBe(0);
   });
 
+  it('renders elements for the TOC view', () => {
+    const wrapper = createWrapper({
+      structures: [{
+        id: 'top',
+        getLabel: () => [{ value: 'top' }],
+        getCanvasIds: () => [],
+        getRanges: () => [
+          {
+            id: '1', getLabel: () => [{ value: 'empty' }], getRanges: () => [], getCanvasIds: () => [],
+          },
+          {
+            id: '2',
+            getLabel: () => [{ value: 'one-with-ranges' }],
+            getRanges: () => [
+              {
+                id: '2.1', getLabel: () => [{ value: 'subrange' }], getRanges: () => [], getCanvasIds: () => [],
+              },
+            ],
+            getCanvasIds: () => [],
+          },
+          {
+            id: '3',
+            getLabel: () => [{ value: 'one-with-canvases' }],
+            getRanges: () => [],
+            getCanvasIds: () => [
+              'http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json',
+            ],
+          },
+        ],
+      }],
+    });
+    wrapper.setState({ variant: 'toc' });
+
+    expect(wrapper.find(ExpansionPanel).length).toBe(5);
+
+    expect(wrapper
+      .find(ExpansionPanel)
+      .at(1)
+      .find(ExpansionPanelSummary)
+      .at(0)
+      .render()
+      .text()).toEqual('empty');
+
+    expect(wrapper.find(ExpansionPanel).at(2).find(List).find(ExpansionPanel).length).toEqual(1);
+
+    expect(wrapper
+      .find(ExpansionPanel).at(4).find(List)
+      .find(Typography)
+      .render()
+      .text()).toEqual('Test 19 Canvas: 1');
+  });
 
   it('should set the correct labels', () => {
     const wrapper = createWrapper();
diff --git a/locales/de/translation.json b/locales/de/translation.json
index 19413ee6acfeedec1225c39b990b3aa69b601d4d..372d6a2e29840b4d345fa05d5b224efede72eb8a 100644
--- a/locales/de/translation.json
+++ b/locales/de/translation.json
@@ -55,6 +55,7 @@
     "thumbnailNavigation": "Miniaturansicht",
     "thumbnails": "Miniaturansicht",
     "thumbnailList": "Miniaturansicht",
+    "tocList": "Inhaltsverzeichnis",
     "toggleWindowSideBar": "Seitenleiste umschalten",
     "tryAgain": "Wiederholen",
     "untitled": "[Unbenannt]",
diff --git a/locales/en/translation.json b/locales/en/translation.json
index 6191ecc6b3f5a5745a6722d7e7c7da4e0a66b84f..d9bf52bf2979083549f6d92a9906517bc9f2b4d9 100644
--- a/locales/en/translation.json
+++ b/locales/en/translation.json
@@ -59,6 +59,7 @@
     "thumbnailNavigation": "Thumbnail carousel",
     "thumbnails": "Thumbnails",
     "thumbnailList": "Thumbnail List",
+    "tocList": "Table of Contents",
     "toggleWindowSideBar": "Toggle window sidebar",
     "tryAgain": "Try again",
     "untitled": "[Untitled]",
diff --git a/src/components/WindowSideBarCanvasPanel.js b/src/components/WindowSideBarCanvasPanel.js
index 556255b1dedec39becf1339b4e91b67d0db5a2c9..8b9e5ff09107bd78fe6aec0cab7daabe9eda077d 100644
--- a/src/components/WindowSideBarCanvasPanel.js
+++ b/src/components/WindowSideBarCanvasPanel.js
@@ -8,6 +8,9 @@ import FilledInput from '@material-ui/core/FilledInput';
 import MenuItem from '@material-ui/core/MenuItem';
 import FormControl from '@material-ui/core/FormControl';
 import Select from '@material-ui/core/Select';
+import ExpansionPanel from '@material-ui/core/ExpansionPanel';
+import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
+import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
 import { CanvasThumbnail } from './CanvasThumbnail';
 import ManifestoCanvas from '../lib/ManifestoCanvas';
 import CompanionWindow from '../containers/CompanionWindow';
@@ -78,18 +81,118 @@ export class WindowSideBarCanvasPanel extends Component {
     );
   }
 
+  /** */
+  renderToc(structures, defaultExpanded = false) {
+    const {
+      canvases, classes, setCanvas, windowId,
+    } = this.props;
+
+    return (
+      structures.map(canvasOrRange => (
+        <ExpansionPanel
+          defaultExpanded={defaultExpanded}
+          key={canvasOrRange.id}
+          elevation={0}
+          square
+        >
+          <ExpansionPanelSummary style={{ backgroundColor: '#eee' }}>
+            {canvasOrRange.getLabel().map(label => label.value)[0]}
+          </ExpansionPanelSummary>
+          <ExpansionPanelDetails style={{ flexDirection: 'column', paddingRight: 0, paddingLeft: 8 }}>
+            {
+              canvasOrRange.getRanges().length > 0
+                && (
+                  <List>
+                    {
+                      this.renderToc(canvasOrRange.getRanges())
+                    }
+                  </List>
+                )
+            }
+            <List>
+              {
+                canvasOrRange.getCanvasIds().map((canvasId) => {
+                  const canvas = canvases.find(e => e.id === canvasId);
+                  if (!canvas) return <></>;
+
+                  const onClick = () => { setCanvas(windowId, canvas.index); }; // eslint-disable-line require-jsdoc, max-len
+
+                  return (
+                    <ListItem
+                      key={canvas.id}
+                      alignItems="flex-start"
+                      onClick={onClick}
+                      button
+                      component="li"
+                      disableGutters
+                    >
+                      <Typography
+                        className={classNames(classes.label)}
+                        variant="body2"
+                      >
+                        {canvas.getLabel().map(label => label.value)[0]}
+                      </Typography>
+                    </ListItem>
+                  );
+                })
+              }
+            </List>
+          </ExpansionPanelDetails>
+        </ExpansionPanel>
+      ))
+    );
+  }
+
+  /** */
+  renderList() {
+    const {
+      canvases, structures, setCanvas, windowId,
+    } = this.props;
+
+    const { variant } = this.state;
+
+    const canvasesIdAndLabel = getIdAndLabelOfCanvases(canvases);
+
+    switch (variant) {
+      case 'toc':
+        return this.renderToc(structures, true);
+      default:
+        return (
+          <List>
+            {
+              canvasesIdAndLabel.map((canvas, canvasIndex) => {
+                const onClick = () => { setCanvas(windowId, canvasIndex); }; // eslint-disable-line require-jsdoc, max-len
+
+                return (
+                  <ListItem
+                    key={canvas.id}
+                    alignItems="flex-start"
+                    onClick={onClick}
+                    button
+                    component="li"
+                  >
+                    {variant === 'compact' && this.renderCompact(canvas, canvases[canvasIndex])}
+                    {variant === 'thumbnail' && this.renderThumbnail(canvas, canvases[canvasIndex])}
+                  </ListItem>
+                );
+              })
+            }
+          </List>
+        );
+    }
+  }
+
   /**
    * render
    */
   render() {
     const {
-      canvases, setCanvas, t, windowId, id,
+      t, windowId, id, structures,
     } = this.props;
 
     const { variant } = this.state;
 
 
-    const canvasesIdAndLabel = getIdAndLabelOfCanvases(canvases);
     return (
       <CompanionWindow
         title={t('canvasIndex')}
@@ -107,30 +210,12 @@ export class WindowSideBarCanvasPanel extends Component {
             >
               <MenuItem value="compact">{ t('compactList') }</MenuItem>
               <MenuItem value="thumbnail">{ t('thumbnailList') }</MenuItem>
+              { structures.length > 0 && <MenuItem value="toc">{ t('tocList') }</MenuItem> }
             </Select>
           </FormControl>
           )}
       >
-        <List>
-          {
-            canvasesIdAndLabel.map((canvas, canvasIndex) => {
-              const onClick = () => { setCanvas(windowId, canvasIndex); }; // eslint-disable-line require-jsdoc, max-len
-
-              return (
-                <ListItem
-                  key={canvas.id}
-                  alignItems="flex-start"
-                  onClick={onClick}
-                  button
-                  component="li"
-                >
-                  {variant === 'compact' && this.renderCompact(canvas, canvases[canvasIndex])}
-                  {variant === 'thumbnail' && this.renderThumbnail(canvas, canvases[canvasIndex])}
-                </ListItem>
-              );
-            })
-          }
-        </List>
+        { this.renderList() }
       </CompanionWindow>
     );
   }
@@ -138,6 +223,7 @@ export class WindowSideBarCanvasPanel extends Component {
 
 WindowSideBarCanvasPanel.propTypes = {
   canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
+  structures: PropTypes.array, // eslint-disable-line react/forbid-prop-types
   classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
   config: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
   setCanvas: PropTypes.func.isRequired,
@@ -145,3 +231,7 @@ WindowSideBarCanvasPanel.propTypes = {
   windowId: PropTypes.string.isRequired,
   id: PropTypes.string.isRequired,
 };
+
+WindowSideBarCanvasPanel.defaultProps = {
+  structures: [],
+};
diff --git a/src/containers/WindowSideBarCanvasPanel.js b/src/containers/WindowSideBarCanvasPanel.js
index b45b5267f05d94e5a1b2177a1904b1db04b938ff..b92299b0e8eb5866bfd1664c2fdba11be93e9ad2 100644
--- a/src/containers/WindowSideBarCanvasPanel.js
+++ b/src/containers/WindowSideBarCanvasPanel.js
@@ -6,6 +6,7 @@ import * as actions from '../state/actions';
 import { WindowSideBarCanvasPanel } from '../components/WindowSideBarCanvasPanel';
 import {
   getManifestCanvases,
+  getManifestStructures,
   getWindowManifest,
 } from '../state/selectors';
 
@@ -15,10 +16,12 @@ import {
 const mapStateToProps = (state, { windowId }) => {
   const manifest = getWindowManifest(state, windowId);
   const canvases = getManifestCanvases(manifest);
+  const structures = getManifestStructures(manifest);
   const { config } = state;
   return {
     canvases,
     config,
+    structures,
   };
 };
 
diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js
index f2940ef1181b82bf26602e6f08275a257bd05432..8635217c76724ac243fb591749a615b885c78c26 100644
--- a/src/state/selectors/index.js
+++ b/src/state/selectors/index.js
@@ -70,6 +70,21 @@ export function getManifestCanvases(manifest) {
   return manifest.manifestation.getSequences()[0].getCanvases();
 }
 
+/**
+ * Return the structures of a manifest or an empty Array
+ */
+export function getManifestStructures(manifest) {
+  if (!manifest.manifestation) {
+    return [];
+  }
+
+  if (!manifest.manifestation.getTopRanges) {
+    return [];
+  }
+
+  return manifest.manifestation.getTopRanges();
+}
+
 /**
 * Return ids and labels of canvases
 * @ param {Array} canvases