PhiaUi.Components.TabsNav (phia_ui v0.1.17)

Copy Markdown View Source

Horizontal tab navigation component for page-level and section-level routing.

TabsNav renders accessible tab-style navigation using <a> links rather than <button> elements, making it suitable for route-based navigation where each tab corresponds to a different URL or live action.

When to use TabsNav vs Tabs

ComponentUse case
TabsNavRoute-based navigation — tabs link to different pages/actions
TabsContent panels — tabs switch content on the same page

Use TabsNav with @live_action to highlight the current section:

<.tabs_nav>
  <.tabs_nav_item href="/dashboard" active={@live_action == :index}>
    Dashboard
  </.tabs_nav_item>
  <.tabs_nav_item href="/analytics" active={@live_action == :analytics}>
    Analytics
  </.tabs_nav_item>
</.tabs_nav>

Visual variants

Three visual styles are available via the :variant attribute:

VariantAppearanceBest for
:underlineBottom border indicator on active tab (default)Standard page navigation
:pillsFilled primary background on active tab, roundedCompact section switching
:segmentSegmented control inside a muted container, no borderDense dashboards

Underline variant (default)

<.tabs_nav>
  <.tabs_nav_item href="/overview" active>Overview</.tabs_nav_item>
  <.tabs_nav_item href="/analytics">Analytics</.tabs_nav_item>
  <.tabs_nav_item href="/reports">Reports</.tabs_nav_item>
</.tabs_nav>

Pills variant

<.tabs_nav variant={:pills}>
  <.tabs_nav_item href="#overview" variant={:pills} active>Overview</.tabs_nav_item>
  <.tabs_nav_item href="#analytics" variant={:pills}>Analytics</.tabs_nav_item>
</.tabs_nav>

Segment variant

The segment variant wraps items inside a bg-muted container with p-1, creating the look of a segmented control (similar to iOS segment pickers):

<.tabs_nav variant={:segment}>
  <.tabs_nav_item href="#day"   variant={:segment} active>Day</.tabs_nav_item>
  <.tabs_nav_item href="#week"  variant={:segment}>Week</.tabs_nav_item>
  <.tabs_nav_item href="#month" variant={:segment}>Month</.tabs_nav_item>
</.tabs_nav>

Driven by live_action

defmodule MyAppWeb.ReportsLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  def handle_params(%{"section" => section}, _uri, socket) do
    {:noreply, assign(socket, section: section)}
  end

  def render(assigns) do
    ~H"""
    <.tabs_nav>
      <.tabs_nav_item href="/reports" active={@section == "overview"}>
        Overview
      </.tabs_nav_item>
      <.tabs_nav_item href="/reports/revenue" active={@section == "revenue"}>
        Revenue
      </.tabs_nav_item>
      <.tabs_nav_item href="/reports/users" active={@section == "users"}>
        Users
      </.tabs_nav_item>
    </.tabs_nav>

    <%!-- Render different content based on section --%>
    <%= case @section do %>
      <% "overview" -> %> <.overview_content />
      <% "revenue"  -> %> <.revenue_report data={@revenue} />
      <% "users"    -> %> <.users_report data={@users} />
    <% end %>
    """
  end
end

Dashboard tabs with phx-click (no navigation)

When you want tab-like navigation without changing the URL, use phx-click on an in-page anchor and drive state from LiveView assigns:

<.tabs_nav variant={:pills}>
  <.tabs_nav_item
    href="#"
    variant={:pills}
    active={@view == "chart"}
    phx-click="set_view"
    phx-value-view="chart"
  >
    Chart
  </.tabs_nav_item>
  <.tabs_nav_item
    href="#"
    variant={:pills}
    active={@view == "table"}
    phx-click="set_view"
    phx-value-view="table"
  >
    Table
  </.tabs_nav_item>
</.tabs_nav>

Accessibility

  • role="tablist" on the <nav> container with aria-orientation="horizontal"
  • role="tab" on each <a> item
  • aria-selected="true|false" reflects active state
  • tabindex="0" on the active item; tabindex="-1" on inactive items (keyboard navigation follows the ARIA tabs pattern)
  • Disabled items: no href, pointer-events-none opacity-50

Summary

Functions

Renders a horizontal tab navigation container.

Renders an individual tab navigation item inside tabs_nav/1.

Functions

tabs_nav(assigns)

Renders a horizontal tab navigation container.

Variant reference

VariantContainer classes
:underlineinline-flex items-center border-b w-full gap-4
:pillsinline-flex items-center gap-1
:segmentinline-flex items-center rounded-lg bg-muted p-1 gap-0.5

Example

<.tabs_nav variant={:segment}>
  <.tabs_nav_item href="#week"  variant={:segment} active>Week</.tabs_nav_item>
  <.tabs_nav_item href="#month" variant={:segment}>Month</.tabs_nav_item>
  <.tabs_nav_item href="#year"  variant={:segment}>Year</.tabs_nav_item>
</.tabs_nav>

Attributes

  • variant (:atom) - Visual style variant for the tab navigation container.

    • :underline — bottom border on the container; active tab has border-b-2 border-primary indicator
    • :pills — no container border; active tab has bg-primary text-primary-foreground filled pill
    • :segmentbg-muted p-1 rounded-lg container; active tab has bg-background text-foreground shadow-sm
    • :scrollable — horizontally scrollable underline variant for many tabs

    Defaults to :underline. Must be one of :underline, :pills, :segment, or :scrollable.

  • class (:string) - Additional CSS classes for the nav element. Defaults to nil.

  • Global attributes are accepted. HTML attributes forwarded to the <nav> element (e.g. aria-label).

Slots

tabs_nav_item(assigns)

Renders an individual tab navigation item inside tabs_nav/1.

Uses an <a> element (not <button>) so it is naturally keyboard-focusable and screen-reader-friendly as a navigation link. The role="tab" attribute overrides the link semantics to signal tablist membership.

When disabled is true, href is set to nil which prevents the browser from navigating while keeping the element in the tab order.

Example

<.tabs_nav_item
  href="/reports/revenue"
  variant={:underline}
  active={@section == "revenue"}
>
  Revenue
</.tabs_nav_item>

Attributes

  • href (:string) - Navigation href for the anchor element. Use a real URL for route-based navigation ("/analytics"), an anchor hash for same-page navigation ("#revenue"), or "#" with phx-click for purely LiveView-driven state.

    Defaults to "#".

  • active (:boolean) - Whether this tab is currently selected. Drives active styles and aria-selected. Typically derived from @live_action or a local assign: active={@live_action == :analytics}.

    Defaults to false.

  • disabled (:boolean) - When true, the tab is non-interactive: href is set to nil (preventing navigation), and pointer-events-none opacity-50 is applied. Use for features that are coming soon or not available on the current plan.

    Defaults to false.

  • variant (:atom) - Visual style variant. Must match the parent tabs_nav/1 variant so the active/inactive styles are consistent. When using tabs_nav/1 you are responsible for passing the same variant to each item.

    Defaults to :underline. Must be one of :underline, :pills, :segment, or :scrollable.

  • class (:string) - Additional CSS classes for the anchor element. Defaults to nil.

  • Global attributes are accepted. HTML attributes forwarded to the <a> element. Use phx-click and phx-value-* for LiveView-driven tab switching without route changes.

Slots

  • inner_block (required) - Tab label — text, icon, or both.