---
title: "Active nav with LiveView"
description: "Keep the active tab highlighted between page views."
---

# Active nav with LiveView

[Annie Ruygt](https://annieruygtillustration.com/)

One of the most important challenges when we are developing a new website is to give the user a great navigation experience, the user must know where they are and what navigation options they have at their disposal within the website. For this we use navigation components such as navbars or sidebars and we as developers are faced with the challenge of showing the user in each interaction with the website the place where they are in a clear and intuitive way.

Today I’ll share with you a recipe on how we can handle navigation within our Liveview applications and how we can offer the user a navigation bar that shows the active tab in which the user is located.

## Defining the sidebar markup

First we’ll define the markup of the application sidebar within our live layout in `live.html.heex`, in this way it will be part of the life cycle of our LiveView components.

```
<%= if @current_user do %>
  <.sidebar_nav current_user={@current_user} active_tab={@active_tab}/>
<% end %>
```

Let’s take a closer look at the previous code; there are two assigns defined, `@active_tab` and `@current_user` (in a few moments I’ll describe in detail where they were assigned) and, on the other hand, we have a function component called `sidebar_nav_links`, to which we are passing as parameters the `current_user` and `active_tab` assigns and is defined in the `LiveBeatsWeb.LayoutView` module as follows:

```
def sidebar_nav(assigns) do
  ~H"""
  <nav class="px-3 mt-6 space-y-1">
    <%= if @current_user do %>
      <.link
        navigate={profile_path(@current_user)}
        class={"#{if @active_tab == :profile, 
                    do: "bg-gray-200", else: "hover:bg-gray-50"}"}
      >
        <.icon name={:music_note} outlined />
        My Songs
      </.link>

      <.link
        navigate={Routes.settings_path(Endpoint, :edit)}
        class={"#{if @active_tab == :settings, 
                    do: "bg-gray-200", else: "hover:bg-gray-50"}"}
      >
        <.icon name={:adjustments} outlined/>
        Settings
      </.link>
    <% end %>
  </nav>
  """
end
```

We are using the `sigil_H` that returns a rendered structure that contains two links. The first with the text *My songs* and another with the text *Settings* , which would look as follows:

In addition, we have a css class in each of the links that we defined, depending on the content of the assign `@active_tab`, the css class `bg-gray-200` or the `hover:bg-gray-50` class will be applied:

```
"#{if @active_tab == :profile, do: "bg-gray-200", else: "hover:bg-gray-50"}"
```

In this way, the user will be able to perceive in which part of the navigation options he is (in this example, within *My songs* page):

## Setting the @active_tab assign

So far we have defined a list of `active_tabs` to which a conditional css class will be assigned in such a way that the location of the user within our application is perceived, however, we don’t know how the `active_tab` and `current_user` assigns were assigned. We also need this assign to be available in all LiveViews since it is rendered in the live layout. Fortunately, LiveView lifecycle hooks make this easy.

Let’s take a look at the following code inside `router.ex`:

```
live_session :authenticated, on_mount: [
  {LiveBeatsWeb.UserAuth, :ensure_authenticated}, 
  LiveBeatsWeb.Nav
] do
 live "/:profile_username/songs/new", ProfileLive, :new
 live "/:profile_username", ProfileLive, :index
 live "/profile/settings", SettingsLive, :edit
end
```

We can see that a `live_session` is defined as `:authenticated`, that session will include a list of routes that will go through a user validation process and will show content if the user is valid and authenticated in our application, otherwise user will be redirected to the log-in page.

Later (lines 2 and 3), we attached a couple of hooks to the “mount” life cycle of each of the LiveViews defined in the session. The `live_session` macro allows us to define a group of routes with shared life-cycle hooks, and live navigate between them.

The first will be in charge of performing the user validation process by invoking the `on_mount` callback of the `LiveBeatsWeb.UserAuth` module with the parameter `:ensure_authenticated`.

Lets see the callback definition in `LiveBeatsWeb.UserAuth`:

```
 def on_mount(:ensure_authenticated, _params, session, socket) do
    case session do
      %{"user_id" => user_id} ->
        new_socket = LiveView.assign_new(socket, :current_user, fn -> 
            Accounts.get_user!(user_id) 
          end)

          {:cont, new_socket}

      %{} ->
        {:halt, redirect_require_login(socket)}
    end
  rescue
    Ecto.NoResultsError -> {:halt, redirect_require_login(socket)}
  end
```

As we can see, in line 4 we are assigning the value of `current_user` to the socket assigns in case of getting the current user of the session and get a result from the database, otherwise, we redirect the user to the log-in view.

The second hook is in charge of setting the assigns we need in order to show the active tab in the sidebar.

Let’s see the content of the `LiveBeatsWeb.Nav` module:

```
defmodule LiveBeatsWeb.Nav do
  import Phoenix.LiveView

  def on_mount(:default, _params, _session, socket) do
    {:cont,
     socket
     |> attach_hook(:active_tab, :handle_params, &set_active_tab/3)}
  end

  defp set_active_tab(params, _url, socket) do
    active_tab =
      case {socket.view, socket.assigns.live_action} do
        {ProfileLive, _} ->
          if params["profile_username"] == current_user(socket) do
            :profile
          end

        {SettingsLive, _} ->
          :settings

        {_, _} ->
          nil
      end

    {:cont, assign(socket, active_tab: active_tab)}
  end

  defp current_user(socket) do
    socket.assigns.current_user[:username]
  end
end
```

First, in line 4 we are defining the `on_mount` callback that will be used as `:default` if no other option is sent in the invocation of the hook within `router.ex`

The most important part of this callback can be seen in line 7, where we call `attach_hook/4`. We named our hook `:active_tab` and attached it to the `handle_params` stage of the `socket` life cycle and we passed our `set_active_tab/3` function to be invoked for this stage.

Within `set_active_tab/3` , we implemented logic to set the `@active_tab` based on the params, LiveView module, and live action from the router. When viewing a user’s profile, we only set the active tab to `:profile` if the current user is viewing their own profile. Likewise, we set the active tab to `:settings` if routed to the `SettingsLive` LiveView. Otherwise, `@active_tab` is set to nil.

Now when the user navigations across LiveViews, the live layout will reactively update as the URL changes, and our tabs will be highlighted appropriately.

Let’s see it in action!
