diff --git a/manual/conf.py b/manual/conf.py
index f1679b072f..79be525ea3 100644
--- a/manual/conf.py
+++ b/manual/conf.py
@@ -30,7 +30,7 @@ needs_sphinx = '1.2'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'custom', 'sphinx.ext.viewcode']
+extensions = ['sphinx.ext.autodoc', 'custom', 'sidebar_toc', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['templates']
diff --git a/manual/sidebar_toc.py b/manual/sidebar_toc.py
new file mode 100644
index 0000000000..b039dfc337
--- /dev/null
+++ b/manual/sidebar_toc.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python2
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+from docutils import nodes
+from itertools import count
+
+id_counter = count()
+ID = 'sidebar-collapsible-toc'
+
+CSS = r'''
+ID li {
+ list-style: none;
+ margin-left: 0;
+ padding-left: 0.2em;
+ text-indent: -0.7em;
+}
+
+ID li.leaf-node {
+ text-indent: 0;
+}
+
+ID li input[type=checkbox] {
+ display: none;
+}
+
+ID li > label {
+ cursor: pointer;
+}
+
+ID li > input[type=checkbox] ~ ul > li {
+ display: none;
+}
+
+ID li > input[type=checkbox]:checked ~ ul > li {
+ display: block;
+}
+
+ID li > input[type=checkbox]:checked + label:before {
+ content: "\025bf";
+}
+
+ID li > input[type=checkbox]:not(:checked) + label:before {
+ content: "\025b8";
+}
+'''.replace('ID', 'ul#' + ID)
+
+
+class checkbox(nodes.Element):
+ pass
+
+
+def visit_checkbox(self, node):
+ cid = node['ids'][0]
+ node['classes'] = []
+ self.body.append(''
+ ''.format(cid))
+
+
+def modify_li(li):
+ if li.first_child_matching_class(nodes.bullet_list) is None:
+ if not li.get('classes'):
+ li['classes'] = []
+ li['classes'].append('leaf-node')
+ else:
+ c = checkbox()
+ c['ids'] = ['collapse-checkbox-{}'.format(next(id_counter))]
+ li.insert(0, c)
+
+
+def create_toc(app, pagename):
+ toctree = app.env.get_toc_for(pagename, app.builder)
+ if toctree is not None:
+ subtree = toctree[toctree.first_child_matching_class(nodes.list_item)]
+ bl = subtree.first_child_matching_class(nodes.bullet_list)
+ if bl is None:
+ return # Empty ToC
+ subtree = subtree[bl]
+ for li in subtree.traverse(nodes.list_item):
+ modify_li(li)
+ subtree['ids'] = [ID]
+ return '' + app.builder.render_partial(
+ subtree)['fragment']
+
+
+def add_html_context(app, pagename, templatename, context, *args):
+ if 'toc' in context:
+ context['toc'] = create_toc(app, pagename) or context['toc']
+
+
+def setup(app):
+ app.add_node(checkbox, html=(visit_checkbox, lambda *x: None))
+ app.connect('html-page-context', add_html_context)
diff --git a/manual/templates/localtoc.html b/manual/templates/localtoc.html
new file mode 100644
index 0000000000..b47410ee2a
--- /dev/null
+++ b/manual/templates/localtoc.html
@@ -0,0 +1,3 @@
+{%- if display_toc %}
+ {{ toc }}
+{%- endif %}