How to use the ASP.NET Menu control: static items, Web.sitemap and styling
The Menu control renders a navigation menu with optional fly-out sub-menus. You can type the items into the markup, or point the menu at a Web.sitemap file so the same structure drives the menu, breadcrumbs and access rules. Since .NET 4 it renders clean nested lists that you style with your own CSS.
A menu with static items
<asp:Menu ID="mnuMain" runat="server" Orientation="Horizontal"
StaticDisplayLevels="1" MaximumDynamicDisplayLevels="2" CssClass="main-menu">
<Items>
<asp:MenuItem Text="Home" NavigateUrl="~/Default.aspx" />
<asp:MenuItem Text="Products" NavigateUrl="~/Products/Default.aspx">
<asp:MenuItem Text="Software" NavigateUrl="~/Products/Software.aspx" />
<asp:MenuItem Text="Training" NavigateUrl="~/Products/Training.aspx" />
</asp:MenuItem>
<asp:MenuItem Text="Contact" NavigateUrl="~/Contact.aspx" />
</Items>
</asp:Menu>
Orientation:Horizontalfor a top bar,Vertical(default) for a sidebar.StaticDisplayLevels: how many levels are always visible. With 1, only the top level shows; children appear as fly-outs.MaximumDynamicDisplayLevels: how many fly-out levels below the static ones (default 3).NavigateUrlwith~/is resolved relative to the application root, so the menu works from any folder.
An item without NavigateUrl causes a postback when clicked and raises MenuItemClick, which you can handle on the server. For navigation, links are better: they work without JavaScript and can be opened in a new tab.
Driving the menu from Web.sitemap
A site map is an XML file at the application root that describes the site's hierarchy:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0">
<siteMapNode url="~/Default.aspx" title="Home" description="Start page">
<siteMapNode url="~/Products/Default.aspx" title="Products" description="All products">
<siteMapNode url="~/Products/Software.aspx" title="Software" description="" />
<siteMapNode url="~/Products/Training.aspx" title="Training" description="" />
</siteMapNode>
<siteMapNode url="~/Contact.aspx" title="Contact" description="How to reach us" />
<siteMapNode url="~/Admin/Default.aspx" title="Administration" description="" roles="Admins" />
</siteMapNode>
</siteMap>
Each URL may appear only once in the file. Bind the menu through a SiteMapDataSource:
<asp:SiteMapDataSource ID="smds" runat="server" ShowStartingNode="false" />
<asp:Menu ID="mnuMain" runat="server" DataSourceID="smds" Orientation="Horizontal"
StaticDisplayLevels="1" CssClass="main-menu" />
ShowStartingNode="false" hides the root ("Home") node so its children become the top level; add a separate Home link if you want one. The node's description becomes the item's tooltip.
The same Web.sitemap feeds a SiteMapPath breadcrumb, described in the breadcrumb example.
Hiding items by role
The roles attribute above only takes effect with security trimming enabled:
<system.web>
<siteMap defaultProvider="XmlSiteMapProvider" enabled="true">
<providers>
<clear />
<add name="XmlSiteMapProvider" type="System.Web.XmlSiteMapProvider"
siteMapFile="Web.sitemap" securityTrimmingEnabled="true" />
</providers>
</siteMap>
</system.web>
With trimming on, a node is shown only if the user is authorised for its URL (by the <authorization> rules) or belongs to a role listed in roles. Hiding a menu item is not access control: protect ~/Admin with <authorization> rules in web.config as well.
Rendering and styling
Since .NET 4, RenderingMode="List" (the default for applications targeting 4.0 or later) outputs nested <ul>/<li>/<a> elements; earlier versions produced nested tables. The old style properties (StaticMenuItemStyle and friends) become inline styles, so it is cleaner to style through your own class:
.main-menu ul { list-style: none; margin: 0; padding: 0; }
.main-menu a { display: block; padding: .5rem .9rem; color: #1b2a3a; text-decoration: none; }
.main-menu a:hover, .main-menu a:focus { background: #e8eef5; }
.main-menu a.selected { font-weight: 700; border-bottom: 2px solid #1b6aa5; }
/* fly-out level */
.main-menu ul.level2 { background: #fff; border: 1px solid #ccd6e0; min-width: 12rem; }
The control adds classes such as level1, level2, static, dynamic and selected to the generated elements. Inspect the output in the browser's developer tools to see exactly what your version emits. The fly-out behaviour itself is implemented by a script the control registers, which also sets some inline positioning styles; set IncludeStyleBlock="false" to stop it from adding its default style block and keep full control.
Highlighting the current page
When bound to a site map, the menu marks the item for the current URL as selected automatically. With static items, select it yourself, for example in a master page:
protected void Page_Load(object sender, EventArgs e)
{
string current = Request.AppRelativeCurrentExecutionFilePath; // e.g. "~/Contact.aspx"
foreach (MenuItem item in mnuMain.Items)
{
if (string.Equals(item.NavigateUrl, current, StringComparison.OrdinalIgnoreCase))
{
item.Selected = true;
break;
}
}
}
Handling clicks on the server
<asp:Menu ID="mnuTabs" runat="server" Orientation="Horizontal" OnMenuItemClick="mnuTabs_MenuItemClick">
<Items>
<asp:MenuItem Text="Details" Value="0" Selected="true" />
<asp:MenuItem Text="History" Value="1" />
</Items>
</asp:Menu>
<asp:MultiView ID="mvTabs" runat="server" ActiveViewIndex="0">
<asp:View runat="server">Details...</asp:View>
<asp:View runat="server">History...</asp:View>
</asp:MultiView>
protected void mnuTabs_MenuItemClick(object sender, MenuEventArgs e)
{
mvTabs.ActiveViewIndex = int.Parse(e.Item.Value);
}
This classic tab pattern uses postbacks; each click reloads the page.
Accessibility and mobile
- Fly-out menus that open on hover are hard to use on touch screens and with a keyboard. Keep important pages reachable from the top level, or use a static (always visible) second level in a sidebar.
- Wrap the menu in
<nav aria-label="Main">so screen-reader users can jump to it. - For a fully responsive design, many teams replace the control with a hand-written list bound to
SiteMap.RootNode.ChildNodesin aRepeater, which keeps the site map but gives complete control of the markup.
Troubleshooting
- The menu renders as tables. The application runs with
controlRenderingCompatibilityVersion="3.5"inweb.config, orRenderingMode="Table"is set. Remove or update the setting to get list rendering. - Fly-outs appear behind other content. Give the menu's container a
positionand a higherz-indexthan the content that overlaps it, and check for ancestors that create a new stacking context (transform,opacity). - Nothing appears with a site map. The site map's root is hidden by
ShowStartingNode="false"andStaticDisplayLevelsis too low, or security trimming hides every node becauserolesor<authorization>rules exclude the current user. - "Multiple nodes with the same URL" at startup: a URL appears twice in
Web.sitemap. Add a harmless query string to one of them or restructure the file.
Official documentation
- Microsoft Learn: Menu class, Menu.RenderingMode, MenuItem
- Microsoft Learn: SiteMapDataSource, XmlSiteMapProvider
- Microsoft Learn: ASP.NET site-map security trimming