NavBuddy

View Source

Hex version badge Hex docs badge License badge

Advanced Configurable Navigation Component for Phoenix LiveView

NavBuddy provides comprehensive navigation components for Phoenix LiveView applications, featuring dropdowns, mega menus, mobile-responsive design, native DaisyUI integration, and full accessibility support.

Features

  • 🎯 Advanced Menu System: Single-layer dropdowns, multi-layer dropdowns, and mega menus
  • 📱 Mobile-Responsive: Drawer, collapse, and overlay modes for mobile devices
  • 🎨 Native DaisyUI Integration: True Tailwind CSS + DaisyUI support with theme compatibility
  • 🎨 Standalone DaisyUI: Built-in compatibility with DaisyUI components and themes
  • Accessibility First: WCAG 2.1 AA compliant with screen reader support
  • ⌨️ Keyboard Navigation: Full arrow key navigation with focus management
  • 🎨 Modern Theming: DaisyUI theme tokens with light/dark/auto theme support
  • 🔧 Highly Configurable: Extensive configuration options
  • 🚀 Phoenix 1.8+ Ready: Enhanced for Phoenix 1.8+ and LiveView 1.1+
  • 📊 Analytics Ready: Built-in event tracking support

Quick Start

Add to your mix.exs:

def deps do
  [
    {:navbuddy, "~> 0.4.0"}
  ]
end

Installation & Setup

Choose your integration approach:

For projects with Tailwind CSS + DaisyUI already set up:

# Add to mix.exs
{:navbuddy, "~> 0.4.0"}

Then use native components:

<NavBuddy.NativeComponent.native_nav
  items={@nav_items}
  brand={%{name: "MyApp", href: "/"}}
  theme="light"
/>

📖 Complete Native Setup Guide

If you're using DaisyUI in your Phoenix project, NavBuddy will automatically integrate with your existing themes and styling:

<!-- NavBuddy CSS will work seamlessly with your DaisyUI setup -->
<link rel="stylesheet" href="/assets/navbuddy.css" />
<script src="/assets/navbuddy.js"></script>

Standalone (Without DaisyUI)

Include the CSS and JavaScript in your layout:

<link rel="stylesheet" href="/assets/navbuddy.css" />
<script src="/assets/navbuddy.js"></script>

Use in your LiveView:

defmodule MyAppWeb.PageLive do
  use MyAppWeb, :live_view
  import NavBuddy.Component

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

  def render(assigns) do
    ~H"""
    <.nav_menu items={@navigation_items} current_path={@current_path} />
    """
  end

  defp navigation_items do
    [
      %{id: "home", label: "Home", navigate: "/"},
      %{id: "about", label: "About", navigate: "/about"},
      %{
        id: "products",
        label: "Products",
        navigate: "/products",
        dropdown: [
          %{id: "web", label: "Web Apps", navigate: "/products/web"},
          %{id: "mobile", label: "Mobile Apps", navigate: "/products/mobile"}
        ]
      }
    ]
  end
end

NavBuddy supports various navigation structures with comprehensive validation and helpful error messages.

Required vs Optional Fields

Required fields:

  • id (string) - Unique identifier for the navigation item
  • label (string) - Display text for the navigation item

Optional fields:

  • navigate (string) - Path to navigate to (required for clickable items)
  • icon (string) - Icon class or identifier
  • dropdown (list) - List of child navigation items
  • mega_menu (map) - Mega menu configuration
  • active (boolean) - Whether the item is currently active
  • disabled (boolean) - Whether the item is disabled
  • permissions (string | list) - Required permissions to see the item

Convenience Functions

NavBuddy provides helper functions to create navigation items easily:

# Simple navigation item
NavBuddy.nav_item("home", "Home", "/")
# => %{id: "home", label: "Home", navigate: "/"}

# With additional options
NavBuddy.nav_item("admin", "Admin", "/admin", permissions: "admin", icon: "admin-icon")
# => %{id: "admin", label: "Admin", navigate: "/admin", permissions: "admin", icon: "admin-icon"}

# Dropdown item
dropdown_items = [
  NavBuddy.nav_item("web", "Web Apps", "/products/web"),
  NavBuddy.nav_item("mobile", "Mobile Apps", "/products/mobile")
]
NavBuddy.dropdown_item("products", "Products", dropdown_items)

# Mega menu item
categories = %{
  "Frontend" => [NavBuddy.nav_item("react", "React", "/tools/react")],
  "Backend" => [NavBuddy.nav_item("elixir", "Elixir", "/tools/elixir")]
}
NavBuddy.mega_menu_item("tools", "Tools", categories)

Validation and Error Handling

NavBuddy provides comprehensive validation with helpful error messages:

# Invalid item - missing required fields
NavBuddy.validate_nav_item(%{label: "Home"})
# => {:error, "Navigation item missing required field 'id'. Required fields: id (string), label (string). Optional fields: navigate, icon, dropdown, mega_menu, active, disabled, permissions"}

# Invalid field type
NavBuddy.validate_nav_item(%{id: "home", label: "Home", active: "yes"})
# => {:error, "Field 'active' must be a boolean, got: \"yes\""}

# Conflicting navigation types
NavBuddy.validate_nav_item(%{id: "item", label: "Item", dropdown: [], mega_menu: %{}})
# => {:error, "Navigation item cannot have both 'dropdown' and 'mega_menu'. Choose one or the other."}
%{id: "home", label: "Home", navigate: "/", icon: "home"}

Single-Layer Dropdowns

%{
  id: "products",
  label: "Products",
  navigate: "/products",
  icon: "package",
  dropdown: [
    %{id: "web", label: "Web Development", navigate: "/products/web"},
    %{id: "mobile", label: "Mobile Apps", navigate: "/products/mobile"}
  ]
}

Multi-Layer Dropdowns

%{
  id: "services",
  label: "Services",
  dropdown: [
    %{
      id: "development",
      label: "Development",
      dropdown: [
        %{id: "frontend", label: "Frontend", navigate: "/services/frontend"},
        %{id: "backend", label: "Backend", navigate: "/services/backend"}
      ]
    }
  ]
}

Mega Menus

%{
  id: "solutions",
  label: "Solutions",
  mega_menu: %{
    title: "Our Solutions",
    description: "Comprehensive business solutions",
    sections: [
      %{
        title: "For Small Business",
        description: "Perfect for startups",
        items: [
          %{id: "starter", label: "Starter Package", navigate: "/solutions/starter"},
          %{id: "growth", label: "Growth Package", navigate: "/solutions/growth"}
        ]
      },
      %{
        title: "For Enterprise",
        description: "Scalable solutions",
        items: [
          %{id: "enterprise", label: "Enterprise Suite", navigate: "/solutions/enterprise"},
          %{id: "custom", label: "Custom Solutions", navigate: "/solutions/custom"}
        ]
      }
    ]
  }
}

Permissions-Based Navigation

NavBuddy supports permissions-based filtering to control which navigation items are visible to users. Add a permissions key to any navigation item to require specific permissions.

Basic Permissions

navigation_items = [
  %{id: "home", label: "Home", navigate: "/"},
  %{id: "dashboard", label: "Dashboard", navigate: "/dashboard", permissions: "user"},
  %{id: "admin", label: "Admin Panel", navigate: "/admin", permissions: "admin"}
]

Multiple Required Permissions

Use a list to require multiple permissions (user must have ALL permissions):

navigation_items = [
  %{
    id: "sensitive",
    label: "Sensitive Data",
    navigate: "/sensitive",
    permissions: ["admin", "data_access"]  # Requires BOTH permissions
  }
]

Nested Structure Permissions

Permissions work with dropdowns and mega-menus:

navigation_items = [
  %{
    id: "reports",
    label: "Reports",
    dropdown: [
      %{id: "basic_reports", label: "Basic Reports", navigate: "/reports/basic"},
      %{id: "admin_reports", label: "Admin Reports", navigate: "/reports/admin", permissions: "admin"},
      %{id: "analytics", label: "Analytics", navigate: "/reports/analytics", permissions: ["analyst", "premium"]}
    ]
  }
]

Using in LiveView

Pass user permissions to the component:

# In your LiveView mount/3 or assign
def mount(_params, session, socket) do
  current_user = get_current_user(session)
  user_permissions = get_user_permissions(current_user)

  socket = assign(socket, user_permissions: user_permissions)
  {:ok, socket}
end

# In your template
<NavBuddy.Component.nav_menu
  items={@navigation_items}
  user_permissions={@user_permissions}
  config={@nav_config}
  current_path={@current_path} />

Permission Examples

# String permission - user needs "admin" permission
%{id: "admin", label: "Admin", navigate: "/admin", permissions: "admin"}

# List of permissions - user needs ALL listed permissions
%{id: "sensitive", label: "Sensitive", navigate: "/sensitive", permissions: ["admin", "security"]}

# No permissions - visible to everyone
%{id: "home", label: "Home", navigate: "/"}

# nil permissions - same as no permissions key
%{id: "public", label: "Public", navigate: "/public", permissions: nil}

The filtering automatically:

  • Shows items with no permissions key to everyone
  • Shows items with permissions: nil to everyone
  • Requires exact string match for string permissions
  • Requires ALL permissions in list for list permissions
  • Recursively filters dropdown and mega-menu items
  • Removes parent items if all children are filtered out

Configuration

Create a configuration struct:

config = %NavBuddy.Config{
  orientation: :horizontal,        # :horizontal | :vertical
  theme: :auto,                   # :light | :dark | :auto
  dropdown_trigger: :hover,       # :hover | :click
  mobile_behavior: :drawer,       # :drawer | :collapse | :overlay
  animations: true,               # boolean
  accessibility: true,            # boolean
  mobile_breakpoint: 768,         # pixels
  max_dropdown_depth: 5,          # levels
  auto_close_delay: 300,          # milliseconds
  touch_gestures: true,           # boolean
  analytics: false                # boolean
}

Use with your navigation:

<.nav_menu items={@navigation_items} config={config} current_path={@current_path} />

LiveView Integration

NavBuddy includes comprehensive LiveView helpers:

defmodule MyAppWeb.PageLive do
  use MyAppWeb, :live_view
  use NavBuddy.LiveHelpers  # Adds event handlers

  def mount(_params, _session, socket) do
    socket =
      socket
      |> assign_navbuddy_state()
      |> assign(:navigation_items, navigation_items())

    {:ok, socket}
  end

  # Event handlers are automatically available:
  # handle_event("navbuddy:item_click", ...)
  # handle_event("navbuddy:dropdown_toggle", ...)
  # handle_event("navbuddy:mobile_toggle", ...)
end

Mobile Optimization

Drawer Mode (Default)

Slide-out navigation drawer on mobile:

%NavBuddy.Config{mobile_behavior: :drawer}

Collapse Mode

Collapsible menu sections:

%NavBuddy.Config{mobile_behavior: :collapse}

Overlay Mode

Full-screen navigation overlay:

%NavBuddy.Config{mobile_behavior: :overlay}

Theming

Automatic Theme Detection

%NavBuddy.Config{theme: :auto}  # Follows system preference

Manual Theme Selection

%NavBuddy.Config{theme: :light}  # or :dark

Custom Themes

Override CSS custom properties:

:root {
  --navbuddy-bg-primary: #your-color;
  --navbuddy-text-primary: #your-color;
  --navbuddy-accent-primary: #your-color;
}

Accessibility Features

NavBuddy is built with accessibility in mind:

  • ARIA Support: Proper ARIA labels, roles, and states
  • Keyboard Navigation: Full keyboard support with arrow keys
  • Screen Readers: Compatible with screen reader software
  • Focus Management: Intelligent focus handling
  • High Contrast: Support for high contrast themes
  • Reduced Motion: Respects prefers-reduced-motion

Keyboard Navigation

  • Arrow Keys: Navigate between menu items
  • Enter/Space: Activate menu items
  • Escape: Close menus and return focus
  • Home/End: Jump to first/last items
  • Tab: Standard tab navigation

Automatic breadcrumb generation:

<.nav_breadcrumbs
  items={@navigation_items}
  current_path={@current_path}
/>

Advanced Features

Smart Positioning

Automatically adjusts dropdown and mega menu positions to stay within viewport:

%NavBuddy.Config{smart_positioning: true}

Touch Gestures

Mobile-optimized touch interactions:

%NavBuddy.Config{touch_gestures: true}

Analytics Integration

Built-in event tracking:

%NavBuddy.Config{analytics: true}

# Events emitted:
# navbuddy:item_click
# navbuddy:dropdown_open
# navbuddy:dropdown_close
# navbuddy:mobile_toggle

Animation Control

Control animations:

%NavBuddy.Config{animations: true, auto_close_delay: 300}

CSS Classes

NavBuddy provides semantic CSS classes for customization:

  • .navbuddy-nav - Main navigation container
  • .navbuddy-nav-item - Navigation item wrapper
  • .navbuddy-nav-link - Navigation links
  • .navbuddy-dropdown - Dropdown menus
  • .navbuddy-mega-menu - Mega menus
  • .navbuddy-mobile-toggle - Mobile menu toggle
  • .navbuddy-breadcrumbs - Breadcrumb navigation

JavaScript API

Access the JavaScript API directly:

// Initialize manually
const nav = new NavBuddy(document.querySelector(".navbuddy-nav"), {
  smartPositioning: true,
  keyboardNavigation: true,
  analytics: true,
});

// Methods
nav.openDropdown("products");
nav.closeMegaMenu("solutions");
nav.toggleMobileNav();
nav.closeAllMenus();

// Events
nav.trackEvent("custom_event", { custom: "data" });

// State
const state = nav.getState();

Browser Support

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

Examples

Check out the included example navigation structure:

NavBuddy.example_navigation()

This returns a comprehensive navigation structure showcasing all features:

  • Simple links with icons
  • Single and multi-level dropdowns
  • Mega menus with grid layouts
  • Mixed navigation types

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Changelog

v0.1.0 (2025-01-25)

  • Initial release
  • Advanced dropdown and mega menu support
  • Mobile-responsive design
  • Full accessibility implementation
  • Comprehensive theming system
  • LiveView integration
  • Touch gesture support
  • Analytics integration

License

MIT License - see LICENSE for details.

Acknowledgments

  • Phoenix LiveView team for the excellent framework
  • Tailwind CSS for design inspiration
  • Accessibility guidelines from WCAG 2.1

Made with ❤️ for the Phoenix community