150

I'm trying to add an"active" class to my bootstrap navbar in MVC, but the following doesn't show the active class when written like this:

<ul class="nav navbar-nav">
  <li>@Html.ActionLink("Home", "Index", "Home", null, new {@class="active"})</li>
  <li>@Html.ActionLink("About", "About", "Home")</li>
  <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>

This resolves to what looks like a correctly formatted class, but doesn't work:

<a class="active" href="/">Home</a>

In the Bootstrap documentation it states that 'a' tags shouldn't be used in the navbar, but the above is how I believe is the correct way of adding a class to an Html.ActionLink. Is there another (tidy) way I can do this?

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
Gillespie
  • 2,228
  • 2
  • 18
  • 25
  • 3
    the code that resolves has class = active in it. Isn't that what you say you want in your question? How you have your code is how I add classes – Matt Bodily Dec 05 '13 at 21:05
  • you are not too clear!!! I dont exactly understand whats the problem adding it to nav, even though I get a feeling, the way it is should be just fine – JC Lizard Dec 05 '13 at 21:05
  • 1
    What's the problem with the resolved code? What are you expecting? Can you be more specific than "doesn't seem to work"? Your question isn't clear enough to help you right now. – dom Dec 05 '13 at 21:07
  • Edited! sorry, what I meant was that the "active" class isn't showing at all – Gillespie Dec 05 '13 at 21:10
  • 2
    Just glanced at Bootstrap's docs and I think you need to add the `active` class to the `li` element, not the `a`. See the first example here : http://getbootstrap.com/components/#navbar – dom Dec 05 '13 at 21:12
  • Yeah that's how I had it originally, I was just trying to keep my styling uniform throughout my code, so that I can employ a repeatable method of changing the "active" class to the selected nav object. – Gillespie Dec 05 '13 at 21:21

28 Answers28

337

In Bootstrap the active class needs to be applied to the <li> element and not the <a>. See the first example here: http://getbootstrap.com/components/#navbar

The way you handle your UI style based on what is active or not has nothing to do with ASP.NET MVC's ActionLink helper. This is the proper solution to follow how the Bootstrap framework was built.

<ul class="nav navbar-nav">
    <li class="active">@Html.ActionLink("Home", "Index", "Home")</li>
    <li>@Html.ActionLink("About", "About", "Home")</li>
    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>

Edit:

Since you will most likely be reusing your menu on multiple pages, it would be smart to have a way to apply that selected class automatically based on the current page rather than copy the menu multiple times and do it manually.

The easiest way is to simply use the values contained in ViewContext.RouteData, namely the Action and Controller values. We could build on what you currently have with something like this:

<ul class="nav navbar-nav">
    <li class="@(ViewContext.RouteData.Values["Action"].ToString() == "Index" ? "active" : "")">@Html.ActionLink("Home", "Index", "Home")</li>
    <li class="@(ViewContext.RouteData.Values["Action"].ToString() == "About" ? "active" : "")">@Html.ActionLink("About", "About", "Home")</li>
    <li class="@(ViewContext.RouteData.Values["Action"].ToString() == "Contact" ? "active" : "")">@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>

It's not pretty in code, but it'll get the job done and allow you to extract your menu into a partial view if you like. There are ways to do this in a much cleaner way, but since you're just getting started I'll leave it at that. Best of luck learning ASP.NET MVC!


Late edit:

This question seems to be getting a bit of traffic so I figured I'd throw in a more elegant solution using an HtmlHelper extension.

Edit 03-24-2015: Had to rewrite this method to allow for multiple actions and controllers triggering the selected behavior, as well as handling for when the method is called from a child action partial view, thought I'd share the update!

public static string IsSelected(this HtmlHelper html, string controllers = "", string actions = "", string cssClass = "selected")
{
    ViewContext viewContext = html.ViewContext;
    bool isChildAction = viewContext.Controller.ControllerContext.IsChildAction;

    if (isChildAction)
        viewContext = html.ViewContext.ParentActionViewContext;

    RouteValueDictionary routeValues = viewContext.RouteData.Values;
    string currentAction = routeValues["action"].ToString();
    string currentController = routeValues["controller"].ToString();

    if (String.IsNullOrEmpty(actions))
        actions = currentAction;

    if (String.IsNullOrEmpty(controllers))
        controllers = currentController;

    string[] acceptedActions = actions.Trim().Split(',').Distinct().ToArray();
    string[] acceptedControllers = controllers.Trim().Split(',').Distinct().ToArray();

    return acceptedActions.Contains(currentAction) && acceptedControllers.Contains(currentController) ?
        cssClass : String.Empty;
}

Works with .NET Core:

public static string IsSelected(this IHtmlHelper htmlHelper, string controllers, string actions, string cssClass = "selected")
{
    string currentAction = htmlHelper.ViewContext.RouteData.Values["action"] as string;
    string currentController = htmlHelper.ViewContext.RouteData.Values["controller"] as string;

    IEnumerable<string> acceptedActions = (actions ?? currentAction).Split(',');
    IEnumerable<string> acceptedControllers = (controllers ?? currentController).Split(',');

    return acceptedActions.Contains(currentAction) && acceptedControllers.Contains(currentController) ?
        cssClass : String.Empty;
}

Sample usage:

<ul>
    <li class="@Html.IsSelected(actions: "Home", controllers: "Default")">
        <a href="@Url.Action("Home", "Default")">Home</a>
    </li>
    <li class="@Html.IsSelected(actions: "List,Detail", controllers: "Default")">
        <a href="@Url.Action("List", "Default")">List</a>
    </li>
</ul>
dom
  • 6,702
  • 3
  • 30
  • 35
  • Thanks, as I mentioned this is how I had it originally, but I was hoping to have one method throughout my code of changing the "active" class between selected objects. – Gillespie Dec 05 '13 at 21:29
  • 2
    @Gillespie no worries! I've edited my answer to add some info that might help you a bit more. – dom Dec 05 '13 at 21:47
  • Thanks a lot for this, I'll try and figure it out as I get an error regarding being able to apply indexing to the routedata expression, but I'm guessing this is due to me needing to learn more about MVC :) – Gillespie Dec 05 '13 at 21:55
  • 6
    With regards to the first edit, it actually worked for the Index page only. Seems the reason is that when it was making the comparison, it failed as ViewContext.RouteData.Values["Action"] does not return a String type object (still don't know how it worked for the first page....). When I changed all ViewContext.RouteData.Values["Action"] to ViewContext.RouteData.Values["Action"].toString(), it worked for all pages. Might want to add this in your solution just in case someone experiences the same problem. – user3281466 Oct 14 '14 at 14:45
  • @Dom where are you placing the IsSelected function? – Peter H Jan 23 '15 at 01:29
  • @PeterH in a static class wherever you feel appropriate, make sure you include the namespace in your View folder's `web.config`. – dom Jan 23 '15 at 01:58
  • I had the same problem as user3281466 pointed out in the comment above me. If `ToString()` is added, it'll work for all checks. – QuantumHive Mar 03 '15 at 17:40
  • This is entirely broken in ASP.NET 5 MVC 6. The `ViewContext` class seems to be a different one, missing a few members now. – ygoe Nov 20 '15 at 15:11
  • 2
    @LonelyPixel maybe [this answer](http://stackoverflow.com/a/27257593/380096) works for you? – dom Nov 20 '15 at 15:19
  • I had used last edited codes but done a little changes. Because I don't like to use unnecessary class tag. My extension method returns class=active or empty. With this usage, there is no unnecessary class tag. My usage like this:
  • @Html.ActionLink("Index", "Index", "Home")
  • – bafsar Jan 05 '16 at 21:19
  • @Dom how would you write a unit test to test that HtmlHelper? I tried all sorts and couldn't :( – J86 Jul 13 '16 at 08:41
  • @Ciwan I'd say that falls outside the scope of this question and answer, probably a good idea to ask a new question on that topic if the search function doesn't turn up anything relevant for you. Good luck! – dom Jul 13 '16 at 12:02
  • 1
    For VB.NET users:
  • @Html.ActionLink("Home", "Index", "Home")
  • – Andrea Antonangeli Sep 24 '16 at 17:45
  • Im using admin lte and having a bit of an issue but love ur appraoch for the update admin lte is using the following plus then it obv adds active to the one required – c-sharp-and-swiftui-devni Jul 22 '20 at 15:23
  • THIS worked like a charm!!! loved the HtmlHelper extension, there is only one small piece of info missing here, you have to add a @using pointing to the extension class at the top of the View using it. – AcidRod75 Apr 02 '21 at 06:55
  • Any idea on how to extend .net core helper class for submenus like thsi example : `` – Abdi fatah Apr 10 '23 at 12:07