Skip to main content

App Navigation Links

Sets the content and behavior of the app navigation sidebar (on the left-side of a desktop app).

General

Unlike other layout hooks, you should not provide your own HTML to the app_navigation_links option. The sidebar navigation is a relatively complex HTML component, so the gem provides a simple interface that you can use to specify your application's navigation: an array of hashes. For example, the sidebar navigation shown in the example image above was rendered with this data:

[
  { title: 'Navigation Links',
    href: root_path,
    icon: 'home',
    active_when: controller_path == 'application' },
  { title: 'Manage Resources',
    href: '#',
    icon: 'pencil' },
  { title: 'Dropdown Section',
    children: [
      { title: 'Option 1',
        href: '#' },
      { title: 'Option 2',
        href: '#' },
      { title: 'Nested Dropdown',
        icon: 'bullseye',
        children: [
          { title: 'Some Page',
            href: '#' },
          { title: 'Other Page',
            href: '#',
            icon: 'cogs' },
        ] }
    ] }
]

You may use { static: content } to add arbitrary content to the sidebar. Otherwise, you must follow the rules of a navigation link:

A typical navigation link:

  • must have a title
  • must have either an href or children
  • must not have both an href and children
    • the children key must be an array of navigation link hashes
  • may have an icon
    • the icon key must be the name of a FontAwesome 5 icon
    • it should contain the fully-qualified prefix (e.g. icon: 'fas fa-eye' for the solid eye icon)
    • the icon key may omit the fa fa- prefix and just use the icon name (e.g. icon: 'map' and icon: 'fa-map' both produce fa fa-map)
  • may have an active_when boolean value
    • determines if the navigation link is shown as being active
    • defaults to false
  • may have an external boolean value
    • determines if the navigation link should open in a new tab
    • defaults to false

Thus, the simplest possible navigation link hash would have this shape:

{
  title: 'Navigation Link',
  href: some_path
}

The most complex navigation link hash would have this shape:

{
  title: 'Dropdown Section',
  icon: 'bookmark',
  html: {
    link: { props: :values },
    item: { props: :values }
  }
  active_when: some_condition_is_met?,
  children_are_open: true,
  children: [
    {
      title: 'First Page',
      href: some_path,
      active_when: true
    },
    {
      title: 'Second Page',
      href: '/second_page',
      external: true
    }
  ]
}

You can specify this array of navigation link hashes in any of the 3 locations; however, for most applications, it will probably make the most sense to specify your app_navigation_links in a controller method that you make available as a helper_method. This will allow you to use the controller context to manage which navigation link is shown as active. Consider this example:

class ApplicationController < ActionController::Base
  # ...

  def app_navigation_links
    [
      { title: 'Dashboard',
        href: root_path,
        active_when: controller_path == 'application' },
      { title: 'Manage Things',
        href: things_path,
        icon: 'edit',
        active_when: controller_path == 'things' },
      { title: 'Manage Roles',
        children: [
          { title: 'By Person',
            href: roles_users_path,
            active_when: controller_path == 'roles/users' },
          { title: 'By Resource',
            href: roles_resources_path,
            active_when: controller_path == 'roles/resources' },
        ] }
    ]
  end
  helper_method :app_navigation_links
end

Using this mechanism, you can ensure that the appropriate navigation link is shown as active, using controller_path, action_name, and/or params as determining factors.

If you need to specify a special set of app_navigation_links for one or a few particular views, you need to use the set_app_navigation_links helper method, and not simply pass the array of hashes to content_for (as content_for requires strings and doesn't handle more complex objects like arrays or hashes).

A navigation link will be styled in an "active" state if it's associated active_when attribute resolves to true when the view is resolved. The specific style is attached to the link's parent li by toggling the active class on that element.

You can easily use any logic available to the controller at the time the view is rendered. For example you could use the following ActiveController methods:

def app_navigation_links
  [
    # ...
    { title: 'View Applicants',
      href: applicants_path,
      active_when: controller_path == 'applicants' && action_name == 'index' }
    # ...
  ]
end

The item above would be displayed as active when viewing the applicants#index action, and any parent navigation links would be expanded to ensure it was visible. The same is true of any parent navigation link if it has a child with an active_when: true attribute, or one that resolves to true.

Gotchas

An html hash can be passed in for navigation links and their containers ("items") like so:

# within app_navigation_links array

{ title: 'Cool beans' ,
  icon: 'beans',
  href: beans_path,
  html: {
    item: { class: 'bean-class', data: { is_beans: true } },
    link: { class: 'underline' }
  } }

This can be useful for attaching data attributes and classes as shown above. It is worth noting that if you have CSP headers that prevent inline styles, attaching custom styles to navigation links via html: { link: { style: ... } } will not display in the browser. If your browser's console shows any such warnings it is advisable to apply styles using either Tailwind's functional classes or your own classes and targeted CSS.

For advanced or specific cases, you can also add arbitrary "static" content to the app navigation sidebar by including an entry in the array you supply to app_navigation_links. This type of sidebar element is not covered by the standard Frontend Team Warranty.

An example of the interface is as follows:

def app_navigation_links
  [
    # ...
    { static: helpers.tag.div('', class: 'w-full mx-8 border border-grey-light') },
    # ...
  ]
end

NOTE you will have to monitor this static element carefully and potentially tune its content or styles by hand. Very tall or wide content may produce odd scrolling or unexpected overflow. Caveat lector.

The item must be a hash with a key/value pair of static and something that can be inserted directly into a view partial. You can use the helpers variable to access methods used only in views, like tag. You can also insert an HTML-Safe String among other things.

Use cases

This type of entry can be used to insert non-link content such as a horizontal divider (as in the example above) or an "empty state" when the sidebar is empty but will contain user-configured entries (like adding favorites in the My Dot Med Gateway).

NOTE Please consider every other option before adding custom interactive elements to the sidebar. The app navigation sidebar is universally used for app navigation—adding other elements risks breaking its semantic importance.