Navs and tabs

Navs and tabs organize and allow navigation between groups of content that are related and at the same level of hierarchy.

Overview #

Halfmoon comes with three different styles of navigation component: tabs, pills, and underline. They all share the same general markup and styles, extending from the base .nav class. While the base is generally not useful on its own (mainly because of its lack of styling for the .active state), you can extend it to create custom navigation. Read more about the base .nav component in its own section.

Tabs #

Create tabbed interfaces with the .nav-tabs class added to the navigation component. Use them to build tabbable regions with the tab JavaScript plugin.

HTML
<!-- Tabs -->
<ul class="nav nav-tabs">
  <li class="nav-item">
    <a class="nav-link active" aria-current="true" href="#">Tab 1</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Tab 2</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Tab 3</a>
  </li>
  <li class="nav-item">
    <a class="nav-link disabled">Tab 4</a>
  </li>
</ul>

Heads up! The active tab link has its background-color set to var(--bs-content-bg) (so that it can be used seamlessly inside cards). If you want to use it outside of cards, please set the background-color to var(--bs-body-bg) (or whatever is the background color of the area you want the tab to blend into):

HTML
<!-- Tabs -->
<ul class="nav nav-tabs" style="--bs-nav-tabs-link-active-bg: var(--bs-body-bg);">
  ...
</ul>

Pills #

Create pill-like navigation interfaces using the .nav-pills class. Here the active item is highlighted with the var(--bs-primary) color for its background.

HTML
<!-- Pills -->
<ul class="nav nav-pills">
  <li class="nav-item">
    <a class="nav-link active" aria-current="true" href="#">Link 1</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link 2</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link 3</a>
  </li>
  <li class="nav-item">
    <a class="nav-link disabled">Link 4</a>
  </li>
</ul>

Underline #

Create underlined navigation interfaces using the .nav-underline class. By default, the wrapping .nav-underline has no borders to keep it simple. However, you can easily set one on the bottom using .border-bottom for better framing (if you want).

HTML
<!-- Underline -->
<ul class="nav nav-underline border-bottom">
  <li class="nav-item">
    <a class="nav-link active" aria-current="true" href="#">Link 1</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link 2</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link 3</a>
  </li>
  <li class="nav-item">
    <a class="nav-link disabled">Link 4</a>
  </li>
</ul>

Horizontal alignment #

By default, navs are left-aligned, but you can easily center them using .justify-content-center, or align them to the right using .justify-content-end. The custom alignments will work with all three variations.

HTML
<!-- Left-aligned nav (default) -->
<ul class="nav nav-tabs">
  ...
</ul>

<!-- Centered nav -->
<ul class="nav nav-pills justify-content-center">
  ...
</ul>

<!-- Right-aligned nav -->
<ul class="nav nav-underline justify-content-end border-bottom">
  ...
</ul>

Vertical #

Change the flex-direction to column with .flex-column / .flex-{breakpoint}-column to stack the links vertically. It's important to note that due to the design of the tabs and underline variations, only pills will look good vertically without any custom CSS.

HTML
<!-- Vertical pills -->
<ul class="nav nav-pills flex-column">
  ...
</ul>

Fill and justify #

Force your .nav contents to extend the full available width with one of two modifier classes. To proportionately fill all available space with your .nav-items, use .nav-fill. Notice that all horizontal space is occupied, but not every item has the same width (the third link takes a little extra space because of the extra content).

HTML
<!-- Nav-fill -->
<ul class="nav nav-pills nav-fill">
  <li class="nav-item">
    <a class="nav-link active" aria-current="true" href="#">Link 1</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link 2</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-seam me-1" viewBox="0 0 16 16">
        <path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2l-2.218-.887zm3.564 1.426L5.596 5 8 5.961 14.154 3.5l-2.404-.961zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923l6.5 2.6zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464L7.443.184z"/>
      </svg>
      Link 3
    </a>
  </li>
</ul>

For equal-width elements, use .nav-justified. All horizontal space will be occupied, but unlike the .nav-fill above, every item will be the same width.

HTML
<!-- Nav-justified -->
<ul class="nav nav-pills nav-justified">
  <li class="nav-item">
    <a class="nav-link active" aria-current="true" href="#">Link</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-seam me-1" viewBox="0 0 16 16">
        <path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2l-2.218-.887zm3.564 1.426L5.596 5 8 5.961 14.154 3.5l-2.404-.961zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923l6.5 2.6zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464L7.443.184z"/>
      </svg>
      Link
    </a>
  </li>
</ul>

Working with flex utilities #

If you need responsive variations, consider using a series of flexbox utilities. While more verbose, these utilities offer greater customization across responsive breakpoints. In the example below, the navigation component will be stacked vertically on the lowest breakpoint, then adapt to a horizontal layout that fills the available width starting from the small breakpoint.

HTML
<!-- Using flex utilities -->
<ul class="nav nav-pills flex-column flex-sm-row nav-justified">
  ...
</ul>

General accessibility #

If you're using navs to provide a navigation bar, be sure to add a role="navigation" to the most logical parent container of the <ul>, or wrap a <nav> element around the whole navigation. Do not add the role to the <ul> itself, as this would prevent it from being announced as an actual list by assistive technologies. To convey the active state to assistive technologies, use the aria-current attribute — using the page value for current page, or true for the current item in a set.

Note that navigation bars, even if visually styled as tabs with the .nav-tabs class, should not be given role="tablist", role="tab" or role="tabpanel" attributes. These are only appropriate for dynamic tabbed interfaces, as described in the ARIA Authoring Practices Guide tabs pattern (opens in new tab). See the tab JavaScript plugin for an example of dynamic tabbed interfaces. The aria-current attribute is not necessary on dynamic tabbed interfaces since our JavaScript handles the selected state by adding aria-selected="true" on the active tab.

Using dropdowns #

Add dropdown menus with a little extra HTML and the dropdowns JavaScript plugin. Dropdowns will work with all three of the navigation variations.

HTML
<!-- Dropdown with tabs -->
<ul class="nav nav-tabs">
  <li class="nav-item">
    <a class="nav-link active" aria-current="true" href="#">Tab 1</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Tab 2</a>
  </li>
  <li class="nav-item dropdown">
    <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</a>
    <ul class="dropdown-menu">
      <li><a class="dropdown-item" href="#">Action 1</a></li>
      <li><a class="dropdown-item" href="#">Action 2</a></li>
      <li><a class="dropdown-item" href="#">Action 3</a></li>
      <li><hr class="dropdown-divider"></li>
      <li><a class="dropdown-item" href="#">Separated link</a></li>
    </ul>
  </li>
</ul>

<!-- Dropdown with pills -->
<ul class="nav nav-pills">
  <li class="nav-item">
    <a class="nav-link active" aria-current="true" href="#">Link 1</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link 2</a>
  </li>
  <li class="nav-item dropdown">
    <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</a>
    <ul class="dropdown-menu">
      <li><a class="dropdown-item" href="#">Action 1</a></li>
      <li><a class="dropdown-item" href="#">Action 2</a></li>
      <li><a class="dropdown-item" href="#">Action 3</a></li>
      <li><hr class="dropdown-divider"></li>
      <li><a class="dropdown-item" href="#">Separated link</a></li>
    </ul>
  </li>
</ul>

Base navigation component #

The base .nav component is built with flexbox and provides a strong foundation for building all types of navigation components. It includes some style overrides (for working with lists), some link padding for larger hit areas, and basic disabled styling. Please note, it does not include any .active state. The following examples include the class, mainly to demonstrate that this particular class does not trigger any special styling.

HTML
<!-- Base nav -->
<ul class="nav">
  <li class="nav-item">
    <a class="nav-link active" aria-current="page" href="#">Active</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
  </li>
  <li class="nav-item">
    <a class="nav-link disabled">Disabled</a>
  </li>
</ul>

Classes are used throughout, so your markup can be super flexible. Use <ul> elements like above, <ol> if the order of your items is important, or roll your own with a <nav> element. Because the .nav uses display: flex, the nav links behave the same as nav items would, but without the extra markup.

HTML
<!-- Base nav (alternate markup) -->
<nav class="nav">
  <a class="nav-link active" aria-current="page" href="#">Active</a>
  <a class="nav-link" href="#">Link</a>
  <a class="nav-link" href="#">Link</a>
  <a class="nav-link disabled">Disabled</a>
</nav>

JavaScript tab plugin #

Use the tab JavaScript plugin to create tabbable panes of local content. The tab plugin will work with all of the examples on this page, excluding the one with the dropdowns.

Home

Account

Contact

Tasks

HTML
<!-- JavaScript tab plugin -->
<ul class="nav nav-tabs" id="js-tabs-1" role="tablist">
  <li class="nav-item" role="presentation">
    <button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#home-tab-pane" type="button" role="tab" aria-controls="home-tab-pane" aria-selected="true">Home</button>
  </li>
  <li class="nav-item" role="presentation">
    <button class="nav-link" id="account-tab" data-bs-toggle="tab" data-bs-target="#account-tab-pane" type="button" role="tab" aria-controls="account-tab-pane" aria-selected="false">Account</button>
  </li>
  <li class="nav-item" role="presentation">
    <button class="nav-link" id="contact-tab" data-bs-toggle="tab" data-bs-target="#contact-tab-pane" type="button" role="tab" aria-controls="contact-tab-pane" aria-selected="false">Contact</button>
  </li>
  <li class="nav-item" role="presentation">
    <button class="nav-link disabled" id="disabled-tab" data-bs-toggle="tab" data-bs-target="#disabled-tab-pane" type="button" role="tab" aria-controls="disabled-tab-pane" aria-selected="false" disabled>Tasks</button>
  </li>
</ul>
<div class="tab-content" id="js-tabs-content-1">
  <div class="tab-pane fade show active" id="home-tab-pane" role="tabpanel" aria-labelledby="home-tab" tabindex="0">...</div>
  <div class="tab-pane fade" id="account-tab-pane" role="tabpanel" aria-labelledby="account-tab" tabindex="0">...</div>
  <div class="tab-pane fade" id="contact-tab-pane" role="tabpanel" aria-labelledby="contact-tab" tabindex="0">...</div>
  <div class="tab-pane fade" id="disabled-tab-pane" role="tabpanel" aria-labelledby="disabled-tab" tabindex="0">...</div>
</div>

To help fit your needs, this works with <ul>-based markup, as shown above, or with any arbitrary "roll your own" markup. Note that if you're using <nav>, you shouldn't add role="tablist" directly to it, as this would override the element's native role as a navigation landmark. Instead, switch to an alternative element (in the example below, a simple <div>) and wrap the <nav> around it.

HTML
<!-- JavaScript tab plugin (alternate markup) -->
<nav>
  <div class="nav nav-tabs" id="js-tabs-2" role="tablist">
    <button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-home" type="button" role="tab" aria-controls="nav-home" aria-selected="true">Home</button>
    <button class="nav-link" id="nav-account-tab" data-bs-toggle="tab" data-bs-target="#nav-account" type="button" role="tab" aria-controls="nav-account" aria-selected="false">Account</button>
    <button class="nav-link" id="nav-contact-tab" data-bs-toggle="tab" data-bs-target="#nav-contact" type="button" role="tab" aria-controls="nav-contact" aria-selected="false">Contact</button>
    <button class="nav-link disabled" id="nav-disabled-tab" data-bs-toggle="tab" data-bs-target="#nav-disabled" type="button" role="tab" aria-controls="nav-disabled" aria-selected="false" disabled>Tasks</button>
  </div>
</nav>
<div class="tab-content" id="js-tabs-content-2">
  <div class="tab-pane fade show active" id="nav-home" role="tabpanel" aria-labelledby="nav-home-tab" tabindex="0">...</div>
  <div class="tab-pane fade" id="nav-account" role="tabpanel" aria-labelledby="nav-account-tab" tabindex="0">...</div>
  <div class="tab-pane fade" id="nav-contact" role="tabpanel" aria-labelledby="nav-contact-tab" tabindex="0">...</div>
  <div class="tab-pane fade" id="nav-disabled" role="tabpanel" aria-labelledby="nav-disabled-tab" tabindex="0">...</div>
</div>

The tabs plugin also works with pills (data-bs-toggle="pill"). Here's an example showing this with vertical pills. Ideally, for vertical tabs, you should also add aria-orientation="vertical" to the tab list container.

Profile

Billing

Messages

Stats

Premium

HTML
<!-- JavaScript pill plugin (vertical) -->
<div class="row">
  <div class="col-sm-4">
    <ul class="nav nav-pills flex-column p-2 border rounded-3" id="js-pills-1" role="tablist" aria-orientation="vertical">
      <li class="nav-item" role="presentation">
        <button class="nav-link active w-100 text-start d-flex align-items-center" id="profile-pill" data-bs-toggle="pill" data-bs-target="#profile-pill-pane" type="button" role="tab" aria-controls="profile-pill-pane" aria-selected="true">
          <i class="fa-light fa-user" style="width: 30px;"></i>
          Profile
        </button>
      </li>
      <li class="nav-item" role="presentation">
        <button class="nav-link w-100 text-start d-flex align-items-center" id="billing-pill" data-bs-toggle="pill" data-bs-target="#billing-pill-pane" type="button" role="tab" aria-controls="billing-pill-pane" aria-selected="false">
          <i class="fa-light fa-credit-card" style="width: 30px;"></i>
          Billing
        </button>
      </li>
      <li class="nav-item" role="presentation">
        <button class="nav-link w-100 text-start d-flex align-items-center" id="messages-pill" data-bs-toggle="pill" data-bs-target="#messages-pill-pane" type="button" role="tab" aria-controls="messages-pill-pane" aria-selected="false">
          <i class="fa-light fa-envelope" style="width: 30px;"></i>
          Messages
        </button>
      </li>
      <li class="nav-item" role="presentation">
        <button class="nav-link w-100 text-start d-flex align-items-center" id="stats-pill" data-bs-toggle="pill" data-bs-target="#stats-pill-pane" type="button" role="tab" aria-controls="stats-pill-pane" aria-selected="false">
          <i class="fa-light fa-line-chart" style="width: 30px;"></i>
          Stats
        </button>
      </li>
      <li class="nav-item" role="presentation">
        <button class="nav-link w-100 text-start d-flex align-items-center" id="premium-pill" data-bs-toggle="pill" data-bs-target="#premium-pill-pane" type="button" role="tab" aria-controls="premium-pill-pane" aria-selected="false">
          <i class="fa-light fa-star" style="width: 30px;"></i>
          Premium
        </button>
      </li>
    </ul>
  </div>
  <div class="col-sm-8">
    <div class="tab-content" id="js-pills-content-1">
      <div class="tab-pane fade show active" id="profile-pill-pane" role="tabpanel" aria-labelledby="profile-pill" tabindex="0">...</div>
      <div class="tab-pane fade" id="billing-pill-pane" role="tabpanel" aria-labelledby="billing-pill" tabindex="0">...</div>
      <div class="tab-pane fade" id="messages-pill-pane" role="tabpanel" aria-labelledby="messages-pill" tabindex="0">...</div>
      <div class="tab-pane fade" id="stats-pill-pane" role="tabpanel" aria-labelledby="stats-pill" tabindex="0">...</div>
      <div class="tab-pane fade" id="premium-pill-pane" role="tabpanel" aria-labelledby="premium-pill" tabindex="0">...</div>
    </div>
  </div>
</div>

Note the generous use of utility classes to create the above design. Also note, the icons being used are taken from the Font Awesome icon library (opens in new tab).

JavaScript tab plugin Accessibility #

Dynamic tabbed interfaces, as described in the ARIA Authoring Practices Guide tabs pattern (opens in new tab), require role="tablist", role="tab", role="tabpanel", and additional aria-* attributes in order to convey their structure, functionality, and current state to users of assistive technologies (such as screen readers). As a best practice, we recommend using <button> elements for the tabs, as these are controls that trigger a dynamic change, rather than links that navigate to a new page or location.

In line with the ARIA Authoring Practices pattern, only the currently active tab receives keyboard focus. When the JavaScript plugin is initialized, it will set tabindex="-1" on all inactive tab controls. Once the currently active tab has focus, the cursor keys activate the previous/next tab, with the plugin changing the roving tabindex (opens in new tab) accordingly. However, note that the JavaScript plugin does not distinguish between horizontal and vertical tab lists when it comes to cursor key interactions: regardless of the tab list's orientation, both the up and left cursor go to the previous tab, and down and right cursor go to the next tab.

In general, to facilitate keyboard navigation, it's recommended to make the tab panels themselves focusable as well, unless the first element containing meaningful content inside the tab panel is already focusable. The JavaScript plugin does not try to handle this aspect—where appropriate, you'll need to explicitly make your tab panels focusable by adding tabindex="0" in your markup.

JavaScript usage #

The examples on this page use data-bs-* attributes for functionality, which is usually enough to cover a majority of use-cases. You can read about methods, events, and more in the official Bootstrap documentation.

Navs & tabs - Bootstrap
Continue reading on the offical documentation website

Up next
Offcanvas

Help us grow

Our main goal is to make Halfmoon the go-to framework for building websites, dashboards and tools. If you believe in our mission, consider becoming a sponsor and help us grow.

You can email us directly if you have any queries. We are always happy to answer.

Subscribe for updates

We will notify you when the framework gets a substantial update. No spam ever.

Follow us on Twitter so that you can stay updated that way.