Fixing Navigation That AI Agents Cannot Follow
An AI agent moves around a website the way a careful keyboard user does: it reads the list of links and buttons on the page, picks the one whose name matches its task, and follows it. Navigation that works by hovering, by clicking on unnamed icons or by scripts attached to plain boxes gives it nothing to pick.
The agent then guesses, loops back to the home page or gives up. This guide explains how agents see a menu, how to spot the patterns that defeat them, and how to rebuild menus, icon controls, breadcrumbs and long listings so an agent can reach any page in a few steps.
How an agent sees your menu
A browser-driving agent gets two views of the page. One is a screenshot. The other is the accessibility tree, a simplified outline the browser builds for assistive technology, listing each landmark, heading, link, button and form field with its role and name. When the agent decides what to click, the tree is what tells it that a thing is a link called "Boiler servicing" or a button called "Open Services menu".
Agents that fetch HTML without running scripts see even less. They get the links that are in the HTML
as <a href> elements, and nothing else. A menu built after load, or items that only
exist when a script runs on hover, simply are not there for them.
That gives three questions to ask of every navigation element:
- Is it the right kind of element? Going somewhere is a link. Doing something on this page, such as opening a menu, is a button.
- Does it have a name? The name comes from the element's text, an
aria-label, or the alt text of an image inside it. An icon with no text has no name. - Can it be reached without a mouse? If it only appears on hover or only responds to a mouse click, an agent may never find or trigger it.
Patterns that stop agents, and what to use instead
| Pattern | What the agent sees | Replace with |
|---|---|---|
A div or span with a click handler that changes page | Plain text, or a generic element with no role | An <a href> to the real address |
A link with href="#" or javascript:void(0) that navigates by script | A link that goes nowhere | A link with the real address in href |
Sub-menu shown only by CSS :hover | The top item only; children hidden and unreachable by keyboard | A disclosure button that opens on click and keyboard |
| Sub-menu items added to the page by script on hover | Nothing, until something hovers | Items present in the HTML from the start, hidden until opened |
| Search, account, basket or hamburger shown only as an icon | An unnamed link or button | Visible text or an accessible name on each control |
| Link text such as "Read more" or "Click here" repeated | Many identical links with no clue which is which | Link text that names the destination |
| Infinite scroll with no page addresses | The first batch of items only | Numbered pages or a Load more button backed by page links |
Links versus buttons
The single most useful rule: if clicking it changes the address, use a link; if it changes something on the current page, use a button. Agents treat the two differently. A link tells them where they will end up before they click, which lets them plan. A button tells them they will stay on the page and something will change.
<!-- The agent sees a generic element with no destination -->
<div class="nav-item" data-go="/services/">Services</div>
<!-- The agent sees a link called Services that goes to /services/ -->
<a href="/services/">Services</a>
If your site uses a JavaScript framework, its link component usually renders a real anchor already. The problem appears when a developer attaches a click listener to a list item or a card instead. Moving the listener to a proper link, or removing it and letting the link work normally, fixes it. Single-page app routing has its own set of issues, covered in the single-page apps guide.
Drop-downs and mega menus that work for agents
The pattern that works for people, keyboard users, screen readers and agents alike is the disclosure
navigation: each top-level item that has children gets a button that shows and hides a list of real links.
The button says whether the list is open with aria-expanded. The children are in the HTML
from the start.
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li>
<a href="/services/">Services</a>
<button type="button" aria-expanded="false" aria-controls="menu-services">
<span class="visually-hidden">Show Services menu</span>
</button>
<ul id="menu-services" hidden>
<li><a href="/services/boiler-servicing/">Boiler servicing</a></li>
<li><a href="/services/boiler-repairs/">Boiler repairs</a></li>
<li><a href="/services/new-boilers/">New boilers</a></li>
</ul>
</li>
<li><a href="/contact/">Contact</a></li>
</ul>
</nav>
The script behind it only needs to do three things: when the button is clicked, flip aria-expanded
between true and false and toggle the hidden attribute on the list; close the list when
Escape is pressed; and close it when focus moves elsewhere.
document.querySelectorAll('nav button[aria-controls]').forEach(function (btn) {
var list = document.getElementById(btn.getAttribute('aria-controls'));
btn.addEventListener('click', function () {
var open = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!open));
list.hidden = open;
});
list.addEventListener('keydown', function (e) {
if (e.key === 'Escape') { btn.setAttribute('aria-expanded', 'false'); list.hidden = true; btn.focus(); }
});
});
You can keep opening on hover as well, for mouse users who expect it. What matters is that hover is an extra, not the only way in. Notice too that "Services" stays a link to its own page. That gives the agent a route even if it never opens the menu.
role="menu" and role="menuitem"
for site navigation. It is meant for application menus like those in desktop software, changes how keys
behave and hides the fact that the items are links. A plain list of links with a disclosure button is
simpler and better understood.Mega menus
Large menus with columns, images and promotions follow the same rules. Use headings inside the panel for each column, keep every destination a real link, give promotional images alt text that names what they link to, and do not put the only link to an important section inside an image. If the mega menu is heavy, consider a simpler menu with a link to each section's landing page, and let the landing page do the detailed work.
Icons, hamburgers and repeated links
Header icons are the most common unnamed controls. A magnifying glass, a person outline, a bag and three horizontal lines are obvious to most people and meaningless in the accessibility tree unless they carry a name.
<!-- No name: the agent sees "link" or "button" and nothing more -->
<a href="/basket/"><svg>...</svg></a>
<!-- Named, including the count -->
<a href="/basket/"><svg aria-hidden="true" focusable="false">...</svg>
<span class="visually-hidden">Basket, 2 items</span></a>
<!-- The mobile menu toggle is a button, not a link -->
<button type="button" aria-expanded="false" aria-controls="mobile-nav">
<svg aria-hidden="true" focusable="false">...</svg>
<span class="visually-hidden">Menu</span>
</button>
The visually-hidden class is a standard CSS pattern that hides text from sight while
leaving it in the accessibility tree. Do not use display: none for this, as that hides the
text from agents and screen readers too.
Many sites show a different menu on small screens. Some agents browse with a narrow window, so the
mobile menu needs the same care as the desktop one: a named toggle button, aria-expanded,
and real links inside. Check it at a narrow width as well as a wide one.
Link text
A page with ten links called "Read more" forces an agent to work out which is which from their position. Name the destination: "Read more about boiler servicing", or make the card's heading the link. If the design must keep short text, add the rest as visually hidden text inside the link.
The fix, step by step
Step 1
Read the menu the way an agent does
Run the AI Agent Compatibility audit on your home page and a deep page, then open developer tools and switch to the accessibility tree view. Walk through the header, menu and footer and note every item whose role is generic rather than link or button, every link or button with an empty name, and every menu item that only appears when you hover. That list is your work plan for the rest of the steps.
Step 2
Turn scripted boxes into real links
Search
the templates for click handlers on div, span and li elements,
and for links whose href is a hash or a javascript: address. Replace each with
an <a> element whose href is the real destination. If a script must still
run, attach it to the link and let the link carry the address, so a visitor or agent that ignores the
script still arrives.
Step 3
Open sub-menus on click with a disclosure button
For each top-level item with children, keep the item as a link to its section
page and add a separate button next to it with aria-expanded and aria-controls
pointing at the sub-menu list. Put the child links in the HTML from the start, hidden until opened. Make
the button toggle on click, Enter and Space, close on Escape, and keep hover opening only as an extra
for mouse users.
Step 4
Name every icon-only control
Go
through the header and footer for icons with no text: search, account, basket, wishlist, language, social
links and the mobile menu toggle. Give each a name with visually hidden text or aria-label,
mark the icon itself aria-hidden, and include live details such as the basket count. Make
the mobile toggle a button with aria-expanded, and rename repeated Read more links so each
names its destination.
Step 5
Give each section a landing page and breadcrumbs
Make sure every menu section has a page at its own address that lists and links
to everything beneath it, so the agent can get there from the parent link without opening any menu. Add
a breadcrumb trail inside a nav labelled Breadcrumb on deeper pages, with each level as
a link and the current page marked with aria-current="page", so the agent can step back
up the structure.
Step 6
Replace endless scroll with pages that have addresses
On blog listings, product categories and search results that load more items
as you scroll, add numbered page links or a Load more button that is backed by a real link to the next
page, such as ?page=2. Make sure that address works when loaded directly and shows the right
items. The agent can then move through the listing in steps and return to a place it has seen.
Step 7
Check the whole path with a keyboard and an agent
Unplug the mouse or ignore it, and use Tab, Shift and Tab, Enter and Escape to reach three deep pages from the home page through the menu, at a wide and a narrow window size. Focus should always be visible and never trapped. Then run the audit again and give an AI agent a task that needs the menu, such as finding a specific service page, and watch its route.
Breadcrumbs, pagination and footers
Breadcrumbs
Breadcrumbs give an agent a map of where it is and a quick way back up. A simple, correct pattern:
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/services/">Services</a></li>
<li><a href="/services/boiler-repairs/" aria-current="page">Boiler repairs</a></li>
</ol>
</nav>
Adding BreadcrumbList structured data that matches the visible trail is useful for search engines and some AI tools, but the visible links are what a browsing agent follows.
Pagination and infinite scroll
Infinite scroll suits casual browsing and is hard for agents. A browser-driving agent has to keep scrolling and has no reliable way to know when it has seen everything; a fetching agent only ever sees the first batch. Numbered pages with real addresses solve both. If you want to keep the scrolling feel, use a Load more button that fetches the next page and also updates the address, and keep page links in the HTML as a fallback.
Footers and landmarks
Label each nav element when there is more than one, for example Main, Breadcrumb and
Footer, so an agent can tell them apart. The footer is a good place for links to contact, delivery, returns
and opening hours, which are exactly the pages agents are often sent to find. A skip link at the top
of the page, pointing at the main content, helps keyboard users and costs nothing.
Edge cases
- Tabs and accordions on a page. These are fine if the tab labels are buttons and the content is in the HTML. If each tab loads its content only when clicked, the agent may never read it. The JavaScript-only content guide covers this.
- Location or language selectors. Use a list of real links to each version of the site rather than a custom drop-down that redirects by script.
- Menus behind a login. Agents usually stop at a login and hand back to the person. Keep public sections public, and do not hide help, pricing or contact pages behind an account.
- Sticky headers that cover content. A tall fixed header can sit on top of the element
the agent is trying to click after scrolling. Keep it compact and use
scroll-margin-topso linked sections are not hidden beneath it. - Carousels as navigation. If category links only appear inside a sliding carousel, list them somewhere static as well.
1. Why can AI agents not use my drop-down menu?
Most often because the sub-menu only opens on mouse hover, or because its items are added by a script when something hovers. Agents often do not hover, and agents that fetch HTML without running scripts never see items that are not in the page. Rebuild the menu so each parent is a link to its section page and has a separate button that opens the sub-menu on click, with the child links already in the HTML and hidden until opened.
2. Should my top-level menu items be links or buttons?
Ideally both, as separate controls. Make the text of each top-level item a link to that section's landing page, and put a small button beside it that opens the sub-menu. That way an agent, or a person, can either go straight to the section or open the menu to pick a page within it. If you can only have one, make it a button that opens the sub-menu, and include a link to the section page as the first item inside.
3. Does aria-label help AI agents?
Yes, when it is used to name a control that has no visible text, such as an icon button. The name becomes part of the accessibility tree, which is one of the main things browser-driving agents read. It does not help when the element is the wrong type in the first place, for example a div with a click handler, and it should not be used to replace good visible text. Prefer visible text, then visually hidden text, then aria-label.
4. Is infinite scroll bad for AI agents?
It makes their job harder. An agent that drives a browser has to keep scrolling and cannot tell when the list ends, and an agent that fetches the HTML only gets the first batch of items. Numbered pages with their own addresses, or a Load more button that also updates the address and is backed by real page links, let agents move through a long listing in steps and come back to the same place.
5. Do breadcrumbs help agents?
They do. Breadcrumbs show an agent where the current page sits in the site and give it a one-click route back up to the parent section, which is useful when it has landed deep in the site from a search result and needs to compare options. Put them in a nav element labelled Breadcrumb, make each level a real link, and mark the current page. Matching BreadcrumbList structured data is a helpful extra, not a replacement.
6. My menu works with a keyboard. Is that enough for agents?
It is the best single test and covers most problems, because agents and keyboard users both depend on real links, named buttons and controls that respond without a mouse. Also check that the menu items are present in the HTML before any script runs, that icon controls have names rather than just being focusable, and that the mobile menu works as well as the desktop one. Then try a real agent on a real task to confirm.
7. Will changing my menu affect my SEO?
Usually positively. Search engines follow real links with href addresses and do not reliably follow scripted navigation, so turning divs into links can help pages get found and crawled. Keeping section landing pages that link to their children also strengthens internal linking. Keep the same destination addresses when you rebuild the menu, and check that nothing important is removed, so the change does not break existing links or lose pages from the structure.
The AI Agent Compatibility audit tests your page for bot blocks, content hidden behind JavaScript, unlabelled forms and controls, navigation agents cannot follow and missing structured data, and links each finding to the guide that fixes it.
Open AI Agent Compatibility Fixes โ