Nested Lists and Multi Level Menus
A sublist goes inside the li it belongs to, not beside it. That one rule fixes almost every broken nested list, and it is what makes a multi level menu work.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
Concept
Lists nest. A list item can contain a whole list of its own, which is how you express a hierarchy: chapters and sections, categories and subcategories, a menu with submenus.
There is exactly one rule, and it is the rule everyone gets wrong the first time:
The nested list goes inside the
liit belongs to, before that item is closed. Not between twolielements.
Syntax
<!-- correct -->
<ul>
<li>Fruit
<ul>
<li>Mango</li>
<li>Guava</li>
</ul>
</li>
<li>Vegetables</li>
</ul>
<!-- invalid: the ul is a direct child of the outer ul -->
<ul>
<li>Fruit</li>
<ul>
<li>Mango</li>
</ul>
</ul>The invalid version usually still looks right, because the browser indents it anyway. But the sublist is no longer attached to Fruit, so a screen reader announces it as a separate list rather than as part of that item, and the relationship is lost.
Example: a category tree
<ul>
<li>Kitchen
<ul>
<li>Cookware
<ul>
<li>Pans</li>
<li>Pressure cookers</li>
</ul>
</li>
<li>Storage</li>
</ul>
</li>
<li>Lighting</li>
</ul>Three levels deep is about the practical limit. Beyond that, readers lose track and screen reader announcements - list, three items, item one, list, two items - become exhausting.
Mixing list types
The inner list does not have to match the outer one. Choose each level on its own merits.
<ol>
<li>Prepare the surface
<ul>
<li>Wire brush</li>
<li>Detergent</li>
<li>Clean cloth</li>
</ul>
</li>
<li>Apply the primer</li>
<li>Apply two coats of paint</li>
</ol>The steps are ordered; the tools for a step are not.
A multi level menu
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li>
<button type="button" aria-expanded="false" aria-controls="menu-courses">
Courses
</button>
<ul id="menu-courses" hidden>
<li><a href="/courses/design">Design</a></li>
<li>
<button type="button" aria-expanded="false" aria-controls="menu-data">
Data
</button>
<ul id="menu-data" hidden>
<li><a href="/courses/data/analytics">Analytics</a></li>
<li><a href="/courses/data/engineering">Engineering</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>Every submenu sits inside the item that owns it. Every trigger is a button, because opening a menu is an action rather than navigation. Every trigger declares aria-expanded, which the script must keep in step with reality - that attribute is the only signal a screen reader gets that the menu opened.
hidden on the closed submenu matters too: it removes those links from the tab order. A menu hidden only with opacity: 0 is invisible but still focusable, so a keyboard user tabs through links they cannot see.
Styling the tree
/* remove default look, keep the structure */
nav ul {
list-style: none;
margin: 0;
padding: 0;
}
/* indent each level */
nav li ul {
padding-left: 1.25rem;
}
/* different marker per level, when markers are wanted */
ul { list-style-type: disc; }
ul ul { list-style-type: circle; }
ul ul ul { list-style-type: square; }
ol { list-style-type: decimal; }
ol ol { list-style-type: lower-alpha; }
ol ol ol { list-style-type: lower-roman; }Important rules
- A nested list is always a child of an
li. - Close the inner list before closing the
lithat contains it. - Any depth is technically legal; three levels is the practical limit.
- Indentation in the source is for humans. The nesting is what the browser reads.
- Hidden submenus should use
hiddenordisplay: noneso their links leave the tab order.
Common mistakes
- Putting the sublist between two
lielements. It looks right and is structurally wrong. - Forgetting to close the inner list, so the parser nests everything that follows.
- Building a menu five levels deep.
- Hover only submenus, which no keyboard user can open.
- Leaving
aria-expandedstuck atfalsewhile a class does the real toggling. - Using nested lists purely to indent a paragraph.
Best practices
- Indent your source one level per nesting level so mistakes are visible.
- Keep menus to two levels; put the rest on a landing page.
- Use a
buttonfor every submenu trigger and keeparia-expandedaccurate. - Close open menus on
Escapeand return focus to the trigger. - Validate the markup - an unclosed nested list is a classic silent bug.
Practice
- Build a three level category tree and validate it. Then move one sublist outside its
liand see which errors appear. - Write an ordered list of steps where one step contains an unordered list of tools.
- Build a two level menu with button triggers and
hiddensubmenus, then tab through it and confirm hidden links are skipped. - Style a nested list with a different marker at each of three levels.