Repository: mkdocs/mkdocs

Issue:
Allow nav links with depth > 2
The default theme doesn't necessarily need to deal with styling them, but they should at least be allowed.

Discussion:
ci5er: I never personally had a problem with the old way, but this is pretty dang sweet. Thank you.

waylan: Wow, that is a great improvement. At a glance, the page structure is immediately obvious from the config. I like it.
d0ugal: This is not supported in the MkDocs theme.

![MkDocs theme](https://dl.dropboxusercontent.com/u/14255217/Screenshots/Screenshot%202015-05-01-10.45.37.Google-chrome.png)
d0ugal: ![RTD](https://dl.dropboxusercontent.com/u/14255217/Screenshots/Screenshot%202015-05-01-12.31.48.Google-chrome.png)

Patch:
diff --git a/docs/index.md b/docs/index.md
index 047f6a4e..9f9b1367 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -105,8 +105,8 @@ We'd like our documentation site to include some navigation headers, so we'll ed
 
     site_name: MkLorum
     pages:
-    - [index.md, Home]
-    - [about.md, About]
+    - Home: index.md
+    - About: about.md
 
 Refresh the browser and you'll now see a navigation bar with `Home` and `About` headers.
 
@@ -116,9 +116,10 @@ While we're here can also change the configuration file to alter how the documen
 
     site_name: MkLorum
     pages:
-    - [index.md, Home]
-    - [about.md, About]
-```
+    - Home: index.md
+    - About: about.md
+    theme: readthedocs
+
 
 Refresh the browser again, and you'll now see the ReadTheDocs theme being used.
 
diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md
index c7dff762..abf7c48f 100644
--- a/docs/user-guide/configuration.md
+++ b/docs/user-guide/configuration.md
@@ -90,22 +90,23 @@ The setting should be a list.  Each row in the list represents information about
 
 Here's a simple example that would cause the build stage to create three pages:
 
-
     pages:
-    - ['index.md', 'Introduction']
-    - ['user-guide.md', 'User Guide']
-    - ['about.md', 'About']
+    - 'Introduction': 'index.md'
+    - 'User Guide': 'user-guide.md'
+    - 'Abut': 'about.md'
 
 Assuming the `docs_dir` setting was left with the default value of `docs`, the source files for this site's build process would be `docs/index.md`, `docs/user-guide.md` and `docs/about.md`.
 
 If you have a lot of project documentation you might choose to use headings to break up your site navigation by category.  You can do so by including an extra string in the page configuration for any pages that require a navigation heading, like so:
 
     pages:
-    - ['index.md', 'Introduction']
-    - ['user-guide/creating.md', 'User Guide', 'Creating a new Mashmallow project']
-    - ['user-guide/api.md', 'User Guide', 'Mashmallow API guide']
-    - ['user-guide/configuration.md', 'User Guide', 'Configuring Mashmallow']
-    - ['about/license.md', 'About', 'License']
+    - Introduction: 'index.md'
+    - User Guide:
+        - 'Creating a new Mashmallow project': 'user-guide/creating.md'
+        - 'Mashmallow API guide': 'user-guide/api.md'
+        - 'Configuring Mashmallow': 'user-guide/configuration.md'
+    - About:
+        - License: 'about/license.md'
 
 See also the section on [configuring pages and navigation](/user-guide/writing-your-docs/#configure-pages-and-navigation) for a more detailed breakdown.
 
diff --git a/docs/user-guide/writing-your-docs.md b/docs/user-guide/writing-your-docs.md
index 57620dd7..8ba930bf 100644
--- a/docs/user-guide/writing-your-docs.md
+++ b/docs/user-guide/writing-your-docs.md
@@ -17,19 +17,21 @@ A simple pages configuration looks like this:
 With this example we will build two pages at the top level and they will automatically have their titles inferred from the filename. To provide a custom name for these pages, they can be added after the filename.
 
     pages:
-    - ['index.md', 'Home']
-    - ['about.md', 'About MkDocs']
+    - Home: 'index.md'
+    - About: 'about.md'
 
 ### Multilevel documentation
 
 To create a second level in the navigation and group topics, the category can be provided before the page title. This is best demonstrated in a documentation project with more pages and is slighlt more complicated.
 
     pages:
-    - ['index.md', 'Home']
-    - ['user-guide/writing-your-docs.md', 'User Guide', 'Writing your docs']
-    - ['user-guide/styling-your-docs.md', 'User Guide', 'Styling your docs']
-    - ['about/license.md', 'About', 'License']
-    - ['about/release-notes.md', 'About', 'Release Notes']
+    - Home: 'index.md'
+    - User Guide:
+        - 'Writing your docs': 'user-guide/writing-your-docs.md'
+        - 'Styling your docs': 'user-guide/styling-your-docs.md'
+    - About:
+        - 'License': 'about/license.md'
+        - 'Release Notes': 'about/release-notes.md'
 
 With the above configuration we have three top level sections Home, User Guide and About. Then under User Guide we have two pages, Writing your docs and Styling your docs. Under the About section we also have two pages, License and Release Notes
 
diff --git a/mkdocs.yml b/mkdocs.yml
index 48c2a31f..ad7ce968 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -5,13 +5,17 @@ site_description: Project documentation with Markdown.
 repo_url: https://github.com/mkdocs/mkdocs/
 
 pages:
-- ['index.md', 'Home']
-- ['user-guide/writing-your-docs.md', 'User Guide', 'Writing your docs']
-- ['user-guide/styling-your-docs.md', 'User Guide', 'Styling your docs']
-- ['user-guide/configuration.md', 'User Guide', 'Configuration']
-- ['about/license.md', 'About', 'License']
-- ['about/release-notes.md', 'About', 'Release Notes']
-- ['about/contributing.md', 'About', 'Contributing']
+- Home: index.md
+- User Guide:
+    - Writing Your Docs: user-guide/writing-your-docs.md
+    - Styling Your Docs: user-guide/styling-your-docs.md
+    - Configuration: user-guide/configuration.md
+- About:
+    - License: about/license.md
+    - Release Notes: about/release-notes.md
+    - Contributing: about/contributing.md
+
+use_new_pages_structure: true
 
 markdown_extensions:
     toc:
diff --git a/mkdocs/config.py b/mkdocs/config.py
index 65744fa9..408e3393 100644
--- a/mkdocs/config.py
+++ b/mkdocs/config.py
@@ -7,7 +7,7 @@ import os
 
 from six.moves.urllib.parse import urlparse
 
-from mkdocs import utils
+from mkdocs import utils, legacy
 from mkdocs.exceptions import ConfigurationError
 
 log = logging.getLogger(__name__)
@@ -68,7 +68,7 @@ DEFAULT_CONFIG = {
 
     # enabling strict mode causes MkDocs to stop the build when a problem is
     # encountered rather than display an error.
-    'strict': False
+    'strict': False,
 }
 
 
@@ -153,11 +153,27 @@ def validate_config(user_config):
         check for Windows style paths. If they are found, output a warning
         and continue.
         """
+
+        # TODO: Remove in 1.0
+        config_types = set(type(l) for l in config['pages'])
+        if list in config_types and dict not in config_types:
+            config['pages'] = legacy.pages_compat_shim(config['pages'])
+        elif list in config_types:
+            raise ConfigurationError("")
+
         for page_config in config['pages']:
+
             if isinstance(page_config, str):
                 path = page_config
+            elif isinstance(page_config, dict):
+                if len(page_config) > 1:
+                    raise ConfigurationError(
+                        "Invalid page config. Should have one key value only")
+                _, path = next(iter(page_config.items()))
             elif len(page_config) in (1, 2, 3):
                 path = page_config[0]
+            else:
+                raise ConfigurationError("Unrecognised page config")
 
             if ntpath.sep in path:
                 log.warning("The config path contains Windows style paths (\\ "
diff --git a/mkdocs/legacy.py b/mkdocs/legacy.py
new file mode 100644
index 00000000..0ac6d705
--- /dev/null
+++ b/mkdocs/legacy.py
@@ -0,0 +1,120 @@
+import logging
+
+from mkdocs.exceptions import ConfigurationError
+
+log = logging.getLogger(__name__)
+
+
+def pages_compat_shim(original_pages):
+    """
+    Support legacy pages configuration
+
+    Re-write the pages config fron MkDocs <=0.12 to match the
+    new nested structure added in 0.13.
+
+    Given a pages configuration in the old style of:
+
+        pages:
+        - ['index.md', 'Home']
+        - ['user-guide/writing-your-docs.md', 'User Guide']
+        - ['user-guide/styling-your-docs.md', 'User Guide']
+        - ['about/license.md', 'About', 'License']
+        - ['about/release-notes.md', 'About']
+        - ['help/contributing.md', 'Help', 'Contributing']
+        - ['support.md']
+        - ['cli.md', 'CLI Guide']
+
+    Rewrite it to look like:
+
+        pages:
+        - Home: index.md
+        - User Guide:
+            - user-guide/writing-your-docs.md
+            - user-guide/styling-your-docs.md
+        - About:
+            - License: about/license.md
+            - about/release-notes.md
+        - Help:
+            - Contributing: about/contributing.md
+        - support.md
+        - CLI Guide: cli.md
+
+    TODO: Remove in 1.0
+    """
+
+    log.warning("The pages config in the mkdocs.yml uses the deprecated "
+                "structure. This will be removed in the next release of "
+                "MkDocs. See for details on updating: "
+                "http://www.mkdocs.org/about/release-notes/")
+
+    new_pages = []
+
+    for config_line in original_pages:
+
+        if len(config_line) not in (1, 2, 3):
+            msg = (
+                "Line in 'page' config contained {0} items. In Line {1}. "
+                "Expected 1, 2 or 3 strings.".format(
+                    config_line, len(config_line))
+            )
+            raise ConfigurationError(msg)
+
+        # First we need to pad out the config line as it could contain
+        # 1-3 items.
+        path, category, title = (list(config_line) + [None, None])[:3]
+
+        if len(new_pages) > 0:
+            # Get the previous top-level page so we can see if the category
+            # matches up with the one we have now.
+            prev_cat, subpages = next(iter(new_pages[-1].items()))
+        else:
+            # We are on the first page
+            prev_cat, subpages = None, []
+
+        # If the category is different, add a new top level category. If the
+        # previous category is None, the it's another top level one too.
+        if prev_cat is None or prev_cat != category:
+            subpages = []
+            new_pages.append({category: subpages})
+
+        # Add the current page to the determined category.
+        subpages.append({title: path})
+
+    # We need to do a bit of cleaning up to match the new structure. In the
+    # above example, pages can either be `- file.md` or `- Title: file.md`.
+    # For pages without a title we currently have `- None: file.md` - so we
+    # need to remove those Nones by changing from a dict to just a string with
+    # the path.
+    for i, category in enumerate(new_pages):
+
+        # Categories are a dictionary with one key as the name and the value
+        # is a list of pages. So, grab that from the dict.
+        category, pages = next(iter(category.items()))
+
+        # If we only have one page, then we can assume it is a top level
+        # category and no further nesting is required unless that single page
+        # has a title itself,
+        if len(pages) == 1:
+            title, path = pages.pop().popitem()
+            # If we have a title, it should be a sub page
+            if title is not None:
+                pages.append({title: path})
+            # if we have a category, but no title it should be a top-level page
+            elif category is not None:
+                new_pages[i] = {category: path}
+            # if we have no category or title, it must be a top level page with
+            # an atomatic title.
+            else:
+                new_pages[i] = path
+        else:
+            # We have more than one page, so the category is valid. We just
+            # need to iterate through and convert any {None: path} dicts to
+            # be just the path string.
+            for j, page in enumerate(pages):
+                title, path = page.popitem()
+                if title:
+                    pages[j] = {title: path}
+                else:
+                    pages[j] = path
+
+    return new_pages
diff --git a/mkdocs/nav.py b/mkdocs/nav.py
index 85d33513..8323c149 100644
--- a/mkdocs/nav.py
+++ b/mkdocs/nav.py
@@ -28,7 +28,8 @@ def file_to_title(filename):
     try:
         with open(filename, 'r') as f:
             lines = f.read()
-            _, table_of_contents, meta = utils.convert_markdown(lines, ['meta', 'toc'])
+            _, table_of_contents, meta = utils.convert_markdown(
+                lines, ['meta', 'toc'])
             if "title" in meta:
                 return meta["title"][0]
             if len(table_of_contents.items) > 0:
@@ -53,8 +54,8 @@ class SiteNavigation(object):
     def __init__(self, pages_config, use_directory_urls=True):
         self.url_context = URLContext()
         self.file_context = FileContext()
-        self.nav_items, self.pages = \
-            _generate_site_navigation(pages_config, self.url_context, use_directory_urls)
+        self.nav_items, self.pages = _generate_site_navigation(
+            pages_config, self.url_context, use_directory_urls)
         self.homepage = self.pages[0] if self.pages else None
         self.use_directory_urls = use_directory_urls
 
@@ -122,7 +123,8 @@ class URLContext(object):
                 return '.'
             return url.lstrip('/')
         # Under Python 2.6, relative_path adds an extra '/' at the end.
-        relative_path = os.path.relpath(url, start=self.base_path).rstrip('/') + suffix
+        relative_path = os.path.relpath(url, start=self.base_path)
+        relative_path = relative_path.rstrip('/') + suffix
 
         return utils.path_to_url(relative_path)
 
@@ -154,6 +156,7 @@ class FileContext(object):
 
 class Page(object):
     def __init__(self, title, url, path, url_context):
+
         self.title = title
         self.abs_url = url
         self.active = False
@@ -177,6 +180,10 @@ class Page(object):
     def is_homepage(self):
         return utils.is_homepage(self.input_path)
 
+    @property
+    def is_top_level(self):
+        return len(self.ancestors) == 0
+
     def __str__(self):
         return self._indent_print()
 
@@ -196,10 +203,15 @@ class Header(object):
     def __init__(self, title, children):
         self.title, self.children = title, children
         self.active = False
+        self.ancestors = []
 
     def __str__(self):
         return self._indent_print()
 
+    @property
+    def is_top_level(self):
+        return len(self.ancestors) == 0
+
     def _indent_print(self, depth=0):
         indent = '    ' * depth
         active_marker = ' [*]' if self.active else ''
@@ -209,74 +221,102 @@ class Header(object):
         return ret
 
 
-def _generate_site_navigation(pages_config, url_context, use_directory_urls=True):
+def _path_to_page(path, title, url_context, use_directory_urls):
+    if title is None:
+        title = file_to_title(path.split(os.path.sep)[-1])
+    url = utils.get_url_path(path, use_directory_urls)
+    return Page(title=title, url=url, path=path,
+                url_context=url_context)
+
+
+def _follow(config_line, url_context, use_dir_urls, header=None, title=None):
+
+    if isinstance(config_line, str):
+        path = os.path.normpath(config_line)
+        page = _path_to_page(path, title, url_context, use_dir_urls)
+
+        if header:
+            page.ancestors = [header]
+            header.children.append(page)
+
+        yield page
+        raise StopIteration
+
+    elif not isinstance(config_line, dict):
+        msg = ("Line in 'page' config is of type {0}, dict or string "
+               "expected. Config: {1}").format(type(config_line), config_line)
+        raise exceptions.ConfigurationError(msg)
+
+    if len(config_line) > 1:
+        raise exceptions.ConfigurationError(
+            "Page configs should be in the format 'name: markdown.md'. The "
+            "config contains an invalid entry: {0}".format(config_line))
+    elif len(config_line) == 0:
+        log.warning("Ignoring empty line in the pages config.")
+        raise StopIteration
+
+    next_cat_or_title, subpages_or_path = next(iter(config_line.items()))
+
+    if isinstance(subpages_or_path, str):
+        path = subpages_or_path
+        for sub in _follow(path, url_context, use_dir_urls, header=header, title=next_cat_or_title):
+            yield sub
+        raise StopIteration
+
+    elif not isinstance(subpages_or_path, list):
+        msg = ("Line in 'page' config is of type {0}, list or string "
+               "expected for sub pages. Config: {1}"
+               ).format(type(config_line), config_line)
+        raise exceptions.ConfigurationError(msg)
+
+    next_header = Header(title=next_cat_or_title, children=[])
+    if header:
+        next_header.ancestors = [header]
+        header.children.append(next_header)
+    yield next_header
+
+    subpages = subpages_or_path
+
+    for subpage in subpages:
+        for sub in _follow(subpage, url_context, use_dir_urls, next_header):
+            yield sub
+
+
+def _generate_site_navigation(pages_config, url_context, use_dir_urls=True):
     """
     Returns a list of Page and Header instances that represent the
     top level site navigation.
     """
     nav_items = []
     pages = []
+
     previous = None
 
     for config_line in pages_config:
-        if isinstance(config_line, str):
-            path = os.path.normpath(config_line)
-            title, child_title = None, None
-        elif len(config_line) in (1, 2, 3):
-            # Pad any items that don't exist with 'None'
-            padded_config = (list(config_line) + [None, None])[:3]
-            path, title, child_title = padded_config
-            path = os.path.normpath(path)
-        else:
-            msg = (
-                "Line in 'page' config contained %d items.  "
-                "Expected 1, 2 or 3 strings." % len(config_line)
-            )
-            raise exceptions.ConfigurationError(msg)
-
-        # If both the title and child_title are None, then we
-        # have just been given a path. If that path contains a /
-        # then lets automatically nest it.
-        if title is None and child_title is None and os.path.sep in path:
-            filename = path.split(os.path.sep)[-1]
-            child_title = file_to_title(filename)
-
-        if title is None:
-            filename = path.split(os.path.sep)[0]
-            title = file_to_title(filename)
-
-        # If we don't have a child title but the other title is the same, we
-        # should be within a section and the child title needs to be inferred
-        # from the filename.
-        if len(nav_items) and title == nav_items[-1].title == title and child_title is None:
-            filename = path.split(os.path.sep)[-1]
-            child_title = file_to_title(filename)
-
-        url = utils.get_url_path(path, use_directory_urls)
-
-        if not child_title:
-            # New top level page.
-            page = Page(title=title, url=url, path=path, url_context=url_context)
-            nav_items.append(page)
-        elif not nav_items or (nav_items[-1].title != title):
-            # New second level page.
-            page = Page(title=child_title, url=url, path=path, url_context=url_context)
-            header = Header(title=title, children=[page])
-            nav_items.append(header)
-            page.ancestors = [header]
-        else:
-            # Additional second level page.
-            page = Page(title=child_title, url=url, path=path, url_context=url_context)
-            header = nav_items[-1]
-            header.children.append(page)
-            page.ancestors = [header]
 
-        # Add in previous and next information.
-        if previous:
-            page.previous_page = previous
-            previous.next_page = page
-        previous = page
+        for page_or_header in _follow(
+                config_line, url_context, use_dir_urls):
+
+            if isinstance(page_or_header, Header):
+
+                if page_or_header.is_top_level:
+                    nav_items.append(page_or_header)
+
+            elif isinstance(page_or_header, Page):
+
+                if page_or_header.is_top_level:
+                    nav_items.append(page_or_header)
+
+                pages.append(page_or_header)
+
+                if previous:
+                    page_or_header.previous_page = previous
+                    previous.next_page = page_or_header
+                previous = page_or_header
 
-        pages.append(page)
+    if len(pages) == 0:
+        raise exceptions.ConfigurationError(
+            "No pages found in the pages config. "
+            "Remove it entirely to enable automatic page discovery.")
 
     return (nav_items, pages)
diff --git a/mkdocs/themes/mkdocs/css/base.css b/mkdocs/themes/mkdocs/css/base.css
index 3a31ef73..8bfc7de2 100644
--- a/mkdocs/themes/mkdocs/css/base.css
+++ b/mkdocs/themes/mkdocs/css/base.css
@@ -224,3 +224,52 @@ h1:hover .headerlink, h2:hover .headerlink, h3:hover .headerlink, h4:hover .head
     font-weight: bold;
     text-align: left;
 }
+
+
+.dropdown-submenu {
+    position: relative;
+}
+
+.dropdown-submenu>.dropdown-menu {
+    top: 0;
+    left: 100%;
+    margin-top: -6px;
+    margin-left: -1px;
+    -webkit-border-radius: 0 6px 6px 6px;
+    -moz-border-radius: 0 6px 6px;
+    border-radius: 0 6px 6px 6px;
+}
+
+.dropdown-submenu:hover>.dropdown-menu {
+    display: block;
+}
+
+.dropdown-submenu>a:after {
+    display: block;
+    content: " ";
+    float: right;
+    width: 0;
+    height: 0;
+    border-color: transparent;
+    border-style: solid;
+    border-width: 5px 0 5px 5px;
+    border-left-color: #ccc;
+    margin-top: 5px;
+    margin-right: -10px;
+}
+
+.dropdown-submenu:hover>a:after {
+    border-left-color: #fff;
+}
+
+.dropdown-submenu.pull-left {
+    float: none;
+}
+
+.dropdown-submenu.pull-left>.dropdown-menu {
+    left: -100%;
+    margin-left: 10px;
+    -webkit-border-radius: 6px 0 6px 6px;
+    -moz-border-radius: 6px 0 6px 6px;
+    border-radius: 6px 0 6px 6px;
+}
diff --git a/mkdocs/themes/mkdocs/nav-sub.html b/mkdocs/themes/mkdocs/nav-sub.html
new file mode 100644
index 00000000..76210465
--- /dev/null
+++ b/mkdocs/themes/mkdocs/nav-sub.html
@@ -0,0 +1,14 @@
+{% if not nav_item.children %}
+<li {% if nav_item.active %}class="active"{% endif %}>
+    <a href="{{ nav_item.url }}">{{ nav_item.title }}</a>
+</li>
+{% else %}
+  <li class="dropdown-submenu">
+    <a tabindex="-1" href="">{{ nav_item.title }}</a>
+    <ul class="dropdown-menu">
+        {% for nav_item in nav_item.children %}
+            {% include "nav-sub.html" %}
+        {% endfor %}
+    </ul>
+  </li>
+{% endif %}
diff --git a/mkdocs/themes/mkdocs/nav.html b/mkdocs/themes/mkdocs/nav.html
index 5ab01c90..60cc5ca9 100644
--- a/mkdocs/themes/mkdocs/nav.html
+++ b/mkdocs/themes/mkdocs/nav.html
@@ -28,9 +28,7 @@
                         <a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ nav_item.title }} <b class="caret"></b></a>
                         <ul class="dropdown-menu">
                         {% for nav_item in nav_item.children %}
-                            <li {% if nav_item.active %}class="active"{% endif %}>
-                                <a href="{{ nav_item.url }}">{{ nav_item.title }}</a>
-                            </li>
+                            {% include "nav-sub.html" %}
                         {% endfor %}
                         </ul>
                     </li>
diff --git a/mkdocs/themes/readthedocs/base.html b/mkdocs/themes/readthedocs/base.html
index fe4c1474..b6b7ba65 100644
--- a/mkdocs/themes/readthedocs/base.html
+++ b/mkdocs/themes/readthedocs/base.html
@@ -57,7 +57,11 @@
       </div>
 
       <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
-      {% include "toc.html" %}
+        <ul class="current">
+          {% for nav_item in nav %}
+            <li>{% include "toc.html" %}<li>
+          {% endfor %}
+        </ul>
       </div>
       &nbsp;
     </nav>
diff --git a/mkdocs/themes/readthedocs/css/theme_extra.css b/mkdocs/themes/readthedocs/css/theme_extra.css
index 675760c9..ccb384e9 100644
--- a/mkdocs/themes/readthedocs/css/theme_extra.css
+++ b/mkdocs/themes/readthedocs/css/theme_extra.css
@@ -25,7 +25,7 @@ h3, h4, h5, h6 {
     color: #838383;
 }
 
-.wy-menu-vertical .toctree-l2 a {
+.wy-menu-vertical .subnav a {
     padding: 0.4045em 2.427em;
 }
 
@@ -116,3 +116,11 @@ code.cs, code.c {
     padding: 6px 12px;
     border-color: #D1D4D5;
 }
+
+.wy-menu-vertical li ul {
+    display: inherit;
+}
+
+.wy-menu-vertical li ul.subnav ul.subnav{
+    padding-left: 1em;
+}
diff --git a/mkdocs/themes/readthedocs/toc.html b/mkdocs/themes/readthedocs/toc.html
index c7f1d03d..6cd2fc9b 100644
--- a/mkdocs/themes/readthedocs/toc.html
+++ b/mkdocs/themes/readthedocs/toc.html
@@ -1,38 +1,23 @@
-<ul class="current">
-    {% for nav_item in nav %}
-        {% if nav_item.children %}
-            <span>{{ nav_item.title }}</span>
-            {% for nav_item in nav_item.children %}
-                <li class="toctree-l2 {% if nav_item.active%}current{%endif%}">
-                    <a class="{% if nav_item.active%}current{%endif%}" href="{{ nav_item.url }}">{{ nav_item.title }}</a>
-                    {% if nav_item == current_page %}
-                        <ul>
-                        {% for toc_item in toc %}
-                            <li class="toctree-l3"><a href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
-                            {% for toc_item in toc_item.children %}
-                                <li><a class="toctree-l4" href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
-                            {% endfor %}
-                        {% endfor %}
-                        </ul>
-                    {% endif %}
-                </li>
+{% if nav_item.children %}
+    <ul class="subnav">
+    <li><span>{{ nav_item.title }}</span></li>
+
+        {% for nav_item in nav_item.children %}
+            {% include 'toc.html' %}
+        {% endfor %}
+    </ul>
+{% else %}
+    <li class="toctree-l1 {% if nav_item.active%}current{%endif%}">
+        <a class="{% if nav_item.active%}current{%endif%}" href="{{ nav_item.url }}">{{ nav_item.title }}</a>
+        {% if nav_item == current_page %}
+            <ul>
+            {% for toc_item in toc %}
+                <li class="toctree-l3"><a href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
+                {% for toc_item in toc_item.children %}
+                    <li><a class="toctree-l4" href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
+                {% endfor %}
             {% endfor %}
-        {% else %}
-            <li class="toctree-l1 {% if nav_item.active%}current{%endif%}">
-                <a class="{% if nav_item.active%}current{%endif%}" href="{{ nav_item.url }}">{{ nav_item.title }}</a>
-                {% if nav_item == current_page %}
-                    <ul>
-                    {% for toc_item in toc %}
-                        <li class="toctree-l3"><a href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
-                        {% for toc_item in toc_item.children %}
-                            <li><a class="toctree-l4" href="{{ toc_item.url }}">{{ toc_item.title }}</a></li>
-                        {% endfor %}
-                    {% endfor %}
-                    </ul>
-                {% endif %}
-            </li>
+            </ul>
         {% endif %}
-
-    {% endfor %}
-</ul>
-
+    </li>
+{% endif %}


Test patch:
diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py
index 784d8925..728b4c7a 100644
--- a/mkdocs/tests/build_tests.py
+++ b/mkdocs/tests/build_tests.py
@@ -84,9 +84,9 @@ class BuildTests(unittest.TestCase):
     def test_convert_internal_media(self):
         """Test relative image URL's are the same for different base_urls"""
         pages = [
-            ('index.md',),
-            ('internal.md',),
-            ('sub/internal.md')
+            'index.md',
+            'internal.md',
+            'sub/internal.md',
         ]
 
         site_navigation = nav.SiteNavigation(pages)
@@ -107,9 +107,9 @@ class BuildTests(unittest.TestCase):
     def test_convert_internal_asbolute_media(self):
         """Test absolute image URL's are correct for different base_urls"""
         pages = [
-            ('index.md',),
-            ('internal.md',),
-            ('sub/internal.md')
+            'index.md',
+            'internal.md',
+            'sub/internal.md',
         ]
 
         site_navigation = nav.SiteNavigation(pages)
@@ -129,9 +129,9 @@ class BuildTests(unittest.TestCase):
 
     def test_dont_convert_code_block_urls(self):
         pages = [
-            ('index.md',),
-            ('internal.md',),
-            ('sub/internal.md')
+            'index.md',
+            'internal.md',
+            'sub/internal.md',
         ]
 
         site_navigation = nav.SiteNavigation(pages)
@@ -150,9 +150,9 @@ class BuildTests(unittest.TestCase):
     def test_anchor_only_link(self):
 
         pages = [
-            ('index.md',),
-            ('internal.md',),
-            ('sub/internal.md')
+            'index.md',
+            'internal.md',
+            'sub/internal.md',
         ]
 
         site_navigation = nav.SiteNavigation(pages)
@@ -172,7 +172,7 @@ class BuildTests(unittest.TestCase):
         md_text = 'An [internal link](internal.md) to another document.'
         expected = '<p>An <a href="internal/index.html">internal link</a> to another document.</p>'
         pages = [
-            ('internal.md',)
+            'internal.md',
         ]
         site_navigation = nav.SiteNavigation(pages, use_directory_urls=False)
         html, toc, meta = build.convert_markdown(md_text, site_navigation=site_navigation)
@@ -297,9 +297,9 @@ class BuildTests(unittest.TestCase):
 
     def test_strict_mode_valid(self):
         pages = [
-            ('index.md',),
-            ('internal.md',),
-            ('sub/internal.md')
+            'index.md',
+            'internal.md',
+            'sub/internal.md',
         ]
         site_nav = nav.SiteNavigation(pages)
 
@@ -309,9 +309,9 @@ class BuildTests(unittest.TestCase):
 
     def test_strict_mode_invalid(self):
         pages = [
-            ('index.md',),
-            ('internal.md',),
-            ('sub/internal.md')
+            'index.md',
+            'internal.md',
+            'sub/internal.md',
         ]
         site_nav = nav.SiteNavigation(pages)
 
diff --git a/mkdocs/tests/config_tests.py b/mkdocs/tests/config_tests.py
index 53d2e915..0687edfe 100644
--- a/mkdocs/tests/config_tests.py
+++ b/mkdocs/tests/config_tests.py
@@ -72,7 +72,7 @@ class ConfigTests(unittest.TestCase):
         expected_result = {
             'site_name': 'Example',
             'pages': [
-                ['index.md', 'Introduction']
+                {'Introduction': 'index.md'}
             ],
         }
         file_contents = dedent("""
diff --git a/mkdocs/tests/legacy_tests.py b/mkdocs/tests/legacy_tests.py
new file mode 100644
index 00000000..0951fce7
--- /dev/null
+++ b/mkdocs/tests/legacy_tests.py
@@ -0,0 +1,65 @@
+import unittest
+
+import yaml
+
+from mkdocs import legacy
+
+
+class TestCompatabilityShim(unittest.TestCase):
+
+    # TODO: Remove in 1.0
+
+    def test_convert(self):
+
+        self.maxDiff = None
+
+        pages_yaml_old = """
+        pages:
+        - ['index.md', 'Home']
+        - ['user-guide/writing-your-docs.md', 'User Guide']
+        - ['user-guide/styling-your-docs.md', 'User Guide']
+        - ['about/license.md', 'About', 'License']
+        - ['about/release-notes.md', 'About']
+        - ['about/contributing.md', 'About', 'Contributing']
+        - ['help/contributing.md', 'Help', 'Contributing']
+        - ['support.md']
+        - ['cli.md', 'CLI Guide']
+        """
+
+        pages_yaml_new = """
+        pages:
+        - Home: index.md
+        - User Guide:
+            - user-guide/writing-your-docs.md
+            - user-guide/styling-your-docs.md
+        - About:
+            - License: about/license.md
+            - about/release-notes.md
+            - Contributing: about/contributing.md
+        - Help:
+            - Contributing: help/contributing.md
+        - support.md
+        - CLI Guide: cli.md
+        """
+        self.assertEqual(
+            legacy.pages_compat_shim(yaml.load(pages_yaml_old)['pages']),
+            yaml.load(pages_yaml_new)['pages'])
+
+    def test_convert_no_home(self):
+
+        self.maxDiff = None
+
+        pages_yaml_old = """
+        pages:
+        - ['index.md']
+        - ['about.md', 'About']
+        """
+
+        pages_yaml_new = """
+        pages:
+        - index.md
+        - About: about.md
+        """
+        self.assertEqual(
+            legacy.pages_compat_shim(yaml.load(pages_yaml_old)['pages']),
+            yaml.load(pages_yaml_new)['pages'])
diff --git a/mkdocs/tests/nav_tests.py b/mkdocs/tests/nav_tests.py
index 90fc4469..263706a7 100644
--- a/mkdocs/tests/nav_tests.py
+++ b/mkdocs/tests/nav_tests.py
@@ -5,7 +5,7 @@ import mock
 import os
 import unittest
 
-from mkdocs import nav
+from mkdocs import nav, legacy
 from mkdocs.exceptions import ConfigurationError
 from mkdocs.tests.base import dedent
 
@@ -13,8 +13,8 @@ from mkdocs.tests.base import dedent
 class SiteNavigationTests(unittest.TestCase):
     def test_simple_toc(self):
         pages = [
-            ('index.md', 'Home'),
-            ('about.md', 'About')
+            {'Home': 'index.md'},
+            {'About': 'about.md'}
         ]
         expected = dedent("""
         Home - /
@@ -27,8 +27,8 @@ class SiteNavigationTests(unittest.TestCase):
 
     def test_empty_toc_item(self):
         pages = [
-            ('index.md',),
-            ('about.md', 'About')
+            'index.md',
+            {'About': 'about.md'}
         ]
         expected = dedent("""
         Home - /
@@ -41,36 +41,16 @@ class SiteNavigationTests(unittest.TestCase):
 
     def test_indented_toc(self):
         pages = [
-            ('index.md', 'Home'),
-            ('api-guide/running.md', 'API Guide', 'Running'),
-            ('api-guide/testing.md', 'API Guide', 'Testing'),
-            ('api-guide/debugging.md', 'API Guide', 'Debugging'),
-            ('about/release-notes.md', 'About', 'Release notes'),
-            ('about/license.md', 'About', 'License')
-        ]
-        expected = dedent("""
-        Home - /
-        API Guide
-            Running - /api-guide/running/
-            Testing - /api-guide/testing/
-            Debugging - /api-guide/debugging/
-        About
-            Release notes - /about/release-notes/
-            License - /about/license/
-        """)
-        site_navigation = nav.SiteNavigation(pages)
-        self.assertEqual(str(site_navigation).strip(), expected)
-        self.assertEqual(len(site_navigation.nav_items), 3)
-        self.assertEqual(len(site_navigation.pages), 6)
-
-    def test_indented_toc_missing_child_title(self):
-        pages = [
-            ('index.md', 'Home'),
-            ('api-guide/running.md', 'API Guide', 'Running'),
-            ('api-guide/testing.md', 'API Guide'),
-            ('api-guide/debugging.md', 'API Guide', 'Debugging'),
-            ('about/release-notes.md', 'About', 'Release notes'),
-            ('about/license.md', 'About', 'License')
+            {'Home': 'index.md'},
+            {'API Guide': [
+                {'Running': 'api-guide/running.md'},
+                {'Testing': 'api-guide/testing.md'},
+                {'Debugging': 'api-guide/debugging.md'},
+            ]},
+            {'About': [
+                {'Release notes': 'about/release-notes.md'},
+                {'License': 'about/license.md'}
+            ]}
         ]
         expected = dedent("""
         Home - /
@@ -89,9 +69,9 @@ class SiteNavigationTests(unittest.TestCase):
 
     def test_nested_ungrouped(self):
         pages = [
-            ('index.md', 'Home'),
-            ('about/contact.md', 'Contact'),
-            ('about/sub/license.md', 'License Title')
+            {'Home': 'index.md'},
+            {'Contact': 'about/contact.md'},
+            {'License Title': 'about/sub/license.md'},
         ]
         expected = dedent("""
         Home - /
@@ -105,45 +85,43 @@ class SiteNavigationTests(unittest.TestCase):
 
     def test_nested_ungrouped_no_titles(self):
         pages = [
-            ('index.md',),
-            ('about/contact.md'),
-            ('about/sub/license.md')
+            'index.md',
+            'about/contact.md',
+            'about/sub/license.md'
         ]
         expected = dedent("""
         Home - /
-        About
-            Contact - /about/contact/
-            License - /about/sub/license/
+        Contact - /about/contact/
+        License - /about/sub/license/
         """)
 
         site_navigation = nav.SiteNavigation(pages)
         self.assertEqual(str(site_navigation).strip(), expected)
-        self.assertEqual(len(site_navigation.nav_items), 2)
+        self.assertEqual(len(site_navigation.nav_items), 3)
         self.assertEqual(len(site_navigation.pages), 3)
 
     @mock.patch.object(os.path, 'sep', '\\')
     def test_nested_ungrouped_no_titles_windows(self):
         pages = [
-            ('index.md',),
-            ('about\\contact.md'),
-            ('about\\sub\\license.md')
+            'index.md',
+            'about\\contact.md',
+            'about\\sub\\license.md',
         ]
         expected = dedent("""
         Home - /
-        About
-            Contact - /about/contact/
-            License - /about/sub/license/
+        Contact - /about/contact/
+        License - /about/sub/license/
         """)
 
         site_navigation = nav.SiteNavigation(pages)
         self.assertEqual(str(site_navigation).strip(), expected)
-        self.assertEqual(len(site_navigation.nav_items), 2)
+        self.assertEqual(len(site_navigation.nav_items), 3)
         self.assertEqual(len(site_navigation.pages), 3)
 
     def test_walk_simple_toc(self):
         pages = [
-            ('index.md', 'Home'),
-            ('about.md', 'About')
+            {'Home': 'index.md'},
+            {'About': 'about.md'}
         ]
         expected = [
             dedent("""
@@ -161,8 +139,8 @@ class SiteNavigationTests(unittest.TestCase):
 
     def test_walk_empty_toc(self):
         pages = [
-            ('index.md',),
-            ('about.md', 'About')
+            'index.md',
+            {'About': 'about.md'}
         ]
         expected = [
             dedent("""
@@ -180,12 +158,16 @@ class SiteNavigationTests(unittest.TestCase):
 
     def test_walk_indented_toc(self):
         pages = [
-            ('index.md', 'Home'),
-            ('api-guide/running.md', 'API Guide', 'Running'),
-            ('api-guide/testing.md', 'API Guide', 'Testing'),
-            ('api-guide/debugging.md', 'API Guide', 'Debugging'),
-            ('about/release-notes.md', 'About', 'Release notes'),
-            ('about/license.md', 'About', 'License')
+            {'Home': 'index.md'},
+            {'API Guide': [
+                {'Running': 'api-guide/running.md'},
+                {'Testing': 'api-guide/testing.md'},
+                {'Debugging': 'api-guide/debugging.md'},
+            ]},
+            {'About': [
+                {'Release notes': 'about/release-notes.md'},
+                {'License': 'about/license.md'}
+            ]}
         ]
         expected = [
             dedent("""
@@ -255,7 +237,7 @@ class SiteNavigationTests(unittest.TestCase):
 
     def test_base_url(self):
         pages = [
-            ('index.md',)
+            'index.md'
         ]
         site_navigation = nav.SiteNavigation(pages, use_directory_urls=False)
         base_url = site_navigation.url_context.make_relative('/')
@@ -263,8 +245,8 @@ class SiteNavigationTests(unittest.TestCase):
 
     def test_relative_md_links_have_slash(self):
         pages = [
-            ('index.md',),
-            ('user-guide/styling-your-docs.md',)
+            'index.md',
+            'user-guide/styling-your-docs.md'
         ]
         site_navigation = nav.SiteNavigation(pages, use_directory_urls=False)
         site_navigation.url_context.base_path = "/user-guide/configuration"
@@ -277,17 +259,17 @@ class SiteNavigationTests(unittest.TestCase):
         """
 
         pages = [
-            ('index.md', ),
-            ('api-guide/running.md', ),
-            ('about/notes.md', ),
-            ('about/sub/license.md', ),
+            'index.md',
+            'api-guide/running.md',
+            'about/notes.md',
+            'about/sub/license.md',
         ]
 
         url_context = nav.URLContext()
         nav_items, pages = nav._generate_site_navigation(pages, url_context)
 
         self.assertEqual([n.title for n in nav_items],
-                         ['Home', 'Api guide', 'About'])
+                         ['Home', 'Running', 'Notes', 'License'])
         self.assertEqual([p.title for p in pages],
                          ['Home', 'Running', 'Notes', 'License'])
 
@@ -297,25 +279,25 @@ class SiteNavigationTests(unittest.TestCase):
         Verify inferring page titles based on the filename with a windows path
         """
         pages = [
-            ('index.md', ),
-            ('api-guide\\running.md', ),
-            ('about\\notes.md', ),
-            ('about\\sub\\license.md', ),
+            'index.md',
+            'api-guide\\running.md',
+            'about\\notes.md',
+            'about\\sub\\license.md',
         ]
 
         url_context = nav.URLContext()
         nav_items, pages = nav._generate_site_navigation(pages, url_context)
 
         self.assertEqual([n.title for n in nav_items],
-                         ['Home', 'Api guide', 'About'])
+                         ['Home', 'Running', 'Notes', 'License'])
         self.assertEqual([p.title for p in pages],
                          ['Home', 'Running', 'Notes', 'License'])
 
     def test_invalid_pages_config(self):
 
         bad_pages = [
-            (),  # too short
-            ('this', 'is', 'too', 'long'),
+            set(),  # should be dict or string only
+            {"a": "index.md", "b": "index.md"}  # extra key
         ]
 
         for bad_page in bad_pages:
@@ -325,15 +307,28 @@ class SiteNavigationTests(unittest.TestCase):
 
             self.assertRaises(ConfigurationError, _test)
 
+    def test_pages_config(self):
+
+        bad_page = {}  # empty
+
+        def _test():
+            return nav._generate_site_navigation((bad_page, ), None)
+
+        self.assertRaises(ConfigurationError, _test)
+
     def test_ancestors(self):
 
         pages = [
-            ('index.md', 'Home'),
-            ('api-guide/running.md', 'API Guide', 'Running'),
-            ('api-guide/testing.md', 'API Guide', 'Testing'),
-            ('api-guide/debugging.md', 'API Guide', 'Debugging'),
-            ('about/release-notes.md', 'About', 'Release notes'),
-            ('about/license.md', 'About', 'License')
+            {'Home': 'index.md'},
+            {'API Guide': [
+                {'Running': 'api-guide/running.md'},
+                {'Testing': 'api-guide/testing.md'},
+                {'Debugging': 'api-guide/debugging.md'},
+            ]},
+            {'About': [
+                {'Release notes': 'about/release-notes.md'},
+                {'License': 'about/license.md'}
+            ]}
         ]
         site_navigation = nav.SiteNavigation(pages)
 
@@ -361,3 +356,197 @@ class SiteNavigationTests(unittest.TestCase):
 
         title = nav.file_to_title("mkdocs/tests/resources/no_title_metadata.md")
         self.assertEqual(title, "Title")
+
+    def test_nesting(self):
+
+        pages_config = [
+            {'Home': 'index.md'},
+            {'Install': [
+                {'Pre-install': 'install/install-pre.md'},
+                {'The install': 'install/install-actual.md'},
+                {'Post install': 'install/install-post.md'},
+            ]},
+            {'Guide': [
+                {'Tutorial': [
+                    {'Getting Started': 'guide/tutorial/running.md'},
+                    {'Advanced Features': 'guide/tutorial/testing.md'},
+                    {'Further Reading': 'guide/tutorial/debugging.md'},
+                ]},
+                {'API Reference': [
+                    {'Feature 1': 'guide/api-ref/running.md'},
+                    {'Feature 2': 'guide/api-ref/testing.md'},
+                    {'Feature 3': 'guide/api-ref/debugging.md'},
+                ]},
+                {'Testing': 'guide/testing.md'},
+                {'Deploying': 'guide/deploying.md'},
+            ]}
+        ]
+
+        site_navigation = nav.SiteNavigation(pages_config)
+
+        self.assertEqual([n.title for n in site_navigation.nav_items],
+                         ['Home', 'Install', 'Guide'])
+        self.assertEqual(len(site_navigation.pages), 12)
+
+        expected = dedent("""
+        Home - /
+        Install
+            Pre-install - /install/install-pre/
+            The install - /install/install-actual/
+            Post install - /install/install-post/
+        Guide
+            Tutorial
+                Getting Started - /guide/tutorial/running/
+                Advanced Features - /guide/tutorial/testing/
+                Further Reading - /guide/tutorial/debugging/
+            API Reference
+                Feature 1 - /guide/api-ref/running/
+                Feature 2 - /guide/api-ref/testing/
+                Feature 3 - /guide/api-ref/debugging/
+            Testing - /guide/testing/
+            Deploying - /guide/deploying/
+        """)
+
+        self.maxDiff = None
+        self.assertEqual(str(site_navigation).strip(), expected)
+
+
+class TestLegacyPagesConfig(unittest.TestCase):
+
+    def test_walk_simple_toc(self):
+        pages = legacy.pages_compat_shim([
+            ('index.md', 'Home'),
+            ('about.md', 'About')
+        ])
+        expected = [
+            dedent("""
+                Home - / [*]
+                About - /about/
+            """),
+            dedent("""
+                Home - /
+                About - /about/ [*]
+            """)
+        ]
+        site_navigation = nav.SiteNavigation(pages)
+        for index, page in enumerate(site_navigation.walk_pages()):
+            self.assertEqual(str(site_navigation).strip(), expected[index])
+
+    def test_walk_empty_toc(self):
+        pages = legacy.pages_compat_shim([
+            ('index.md',),
+            ('about.md', 'About')
+        ])
+
+        expected = [
+            dedent("""
+                Home - / [*]
+                About - /about/
+            """),
+            dedent("""
+                Home - /
+                About - /about/ [*]
+            """)
+        ]
+        site_navigation = nav.SiteNavigation(pages)
+        for index, page in enumerate(site_navigation.walk_pages()):
+            self.assertEqual(str(site_navigation).strip(), expected[index])
+
+    def test_walk_indented_toc(self):
+        pages = legacy.pages_compat_shim([
+            ('index.md', 'Home'),
+            ('api-guide/running.md', 'API Guide', 'Running'),
+            ('api-guide/testing.md', 'API Guide', 'Testing'),
+            ('api-guide/debugging.md', 'API Guide', 'Debugging'),
+            ('about/release-notes.md', 'About', 'Release notes'),
+            ('about/license.md', 'About', 'License')
+        ])
+        expected = [
+            dedent("""
+                Home - / [*]
+                API Guide
+                    Running - /api-guide/running/
+                    Testing - /api-guide/testing/
+                    Debugging - /api-guide/debugging/
+                About
+                    Release notes - /about/release-notes/
+                    License - /about/license/
+            """),
+            dedent("""
+                Home - /
+                API Guide [*]
+                    Running - /api-guide/running/ [*]
+                    Testing - /api-guide/testing/
+                    Debugging - /api-guide/debugging/
+                About
+                    Release notes - /about/release-notes/
+                    License - /about/license/
+            """),
+            dedent("""
+                Home - /
+                API Guide [*]
+                    Running - /api-guide/running/
+                    Testing - /api-guide/testing/ [*]
+                    Debugging - /api-guide/debugging/
+                About
+                    Release notes - /about/release-notes/
+                    License - /about/license/
+            """),
+            dedent("""
+                Home - /
+                API Guide [*]
+                    Running - /api-guide/running/
+                    Testing - /api-guide/testing/
+                    Debugging - /api-guide/debugging/ [*]
+                About
+                    Release notes - /about/release-notes/
+                    License - /about/license/
+            """),
+            dedent("""
+                Home - /
+                API Guide
+                    Running - /api-guide/running/
+                    Testing - /api-guide/testing/
+                    Debugging - /api-guide/debugging/
+                About [*]
+                    Release notes - /about/release-notes/ [*]
+                    License - /about/license/
+            """),
+            dedent("""
+                Home - /
+                API Guide
+                    Running - /api-guide/running/
+                    Testing - /api-guide/testing/
+                    Debugging - /api-guide/debugging/
+                About [*]
+                    Release notes - /about/release-notes/
+                    License - /about/license/ [*]
+            """)
+        ]
+        site_navigation = nav.SiteNavigation(pages)
+        for index, page in enumerate(site_navigation.walk_pages()):
+            self.assertEqual(str(site_navigation).strip(), expected[index])
+
+    def test_indented_toc_missing_child_title(self):
+        pages = legacy.pages_compat_shim([
+            ('index.md', 'Home'),
+            ('api-guide/running.md', 'API Guide', 'Running'),
+            ('api-guide/testing.md', 'API Guide'),
+            ('api-guide/debugging.md', 'API Guide', 'Debugging'),
+            ('about/release-notes.md', 'About', 'Release notes'),
+            ('about/license.md', 'About', 'License')
+        ])
+        expected = dedent("""
+        Home - /
+        API Guide
+            Running - /api-guide/running/
+            Testing - /api-guide/testing/
+            Debugging - /api-guide/debugging/
+        About
+            Release notes - /about/release-notes/
+            License - /about/license/
+        """)
+        site_navigation = nav.SiteNavigation(pages)
+        self.assertEqual(str(site_navigation).strip(), expected)
+        self.assertEqual(len(site_navigation.nav_items), 3)
+        self.assertEqual(len(site_navigation.pages), 6)
diff --git a/mkdocs/tests/search_tests.py b/mkdocs/tests/search_tests.py
index c53f40b6..182f2693 100644
--- a/mkdocs/tests/search_tests.py
+++ b/mkdocs/tests/search_tests.py
@@ -103,8 +103,8 @@ class SearchTests(unittest.TestCase):
         """
 
         pages = [
-            ('index.md', 'Home'),
-            ('about.md', 'About')
+            {'Home': 'index.md'},
+            {'About': 'about.md'},
         ]
         site_navigation = nav.SiteNavigation(pages)
 
diff --git a/mkdocs/tests/utils_tests.py b/mkdocs/tests/utils_tests.py
index 23da4e07..e2854187 100644
--- a/mkdocs/tests/utils_tests.py
+++ b/mkdocs/tests/utils_tests.py
@@ -54,8 +54,8 @@ class UtilsTests(unittest.TestCase):
 
     def test_create_media_urls(self):
         pages = [
-            ('index.md', 'Home'),
-            ('about.md', 'About')
+            {'Home': 'index.md'},
+            {'About': 'about.md'}
         ]
         expected_results = {
             'https://media.cdn.org/jq.js': 'https://media.cdn.org/jq.js',