User Dashboard Tab Management System.
PhoenixKit's dashboard provides a flexible, extensible navigation system that parent applications can customize with their own tabs, badges, and features.
Features
- Dynamic Tabs: Register tabs from config or at runtime
- Live Badges: Real-time badge updates via PubSub
- Grouping: Organize tabs into logical sections with headers
- Conditional Visibility: Show/hide tabs based on roles or custom logic
- Attention Indicators: Pulse, bounce, shake animations to draw attention
- Presence Tracking: Show how many users are viewing each tab
- Path Matching: Flexible active state detection (exact, prefix, custom)
Quick Start
1. Configure Tabs in config.exs
config :phoenix_kit, :user_dashboard_tabs, [
%{
id: :orders,
label: "My Orders",
icon: "hero-shopping-bag",
path: "/dashboard/orders",
priority: 100
},
%{
id: :notifications,
label: "Notifications",
icon: "hero-bell",
path: "/dashboard/notifications",
priority: 200,
badge: %{type: :count, value: 0, color: :error}
}
]2. Register Tabs at Runtime (Optional)
# In your application startup or a LiveView mount
PhoenixKit.Dashboard.register_tabs(:my_app, [
%{
id: :printers,
label: "Printers",
icon: "hero-cube",
path: "/dashboard/printers",
priority: 150,
badge: %{
type: :count,
subscribe: {"farm:stats", fn msg -> msg.printing_count end}
}
}
])3. Update Badges Live
# From anywhere in your app
PhoenixKit.Dashboard.update_badge(:notifications, 5)
PhoenixKit.Dashboard.update_badge(:printers, count: 3, color: :warning)4. Trigger Attention
# Make a tab pulse to draw attention
PhoenixKit.Dashboard.set_attention(:alerts, :pulse)Tab Groups
Organize tabs into sections:
config :phoenix_kit, :user_dashboard_tab_groups, [
%{id: :main, label: nil, priority: 100},
%{id: :farm, label: "Farm Management", priority: 200, icon: "hero-cube"},
%{id: :account, label: "Account", priority: 900}
]Then assign tabs to groups:
%{id: :printers, label: "Printers", path: "/dashboard/printers", group: :farm}Conditional Visibility
Use visible for non-permission conditional logic (feature flags, user data).
For access control, use the permission field instead.
%{
id: :beta_feature,
label: "Beta",
path: "/dashboard/beta",
visible: fn scope ->
scope.user.features["beta_enabled"] == true
end
}Live Badges with PubSub
Badges can subscribe to PubSub topics for real-time updates:
%{
id: :notifications,
label: "Notifications",
path: "/dashboard/notifications",
badge: %{
type: :count,
color: :error,
subscribe: {"user:#{user_id}:notifications", :unread_count}
}
}When a message is broadcast to the topic, the badge automatically updates.
Presence Tracking
Track which users are viewing which tabs:
# In your LiveView
def mount(_params, _session, socket) do
if connected?(socket) do
PhoenixKit.Dashboard.Presence.track_tab(socket, :orders)
end
{:ok, socket}
endThe sidebar will show "2 viewing" indicators.
Summary
Functions
Clears attention animation from a tab.
Clears a tab's badge.
Checks if the context selector feature is enabled.
Creates a count badge.
Gets the current context from socket assigns.
Gets the current context ID from socket assigns.
Decrements a tab's count badge by a given amount.
Creates a divider for visual separation in the sidebar.
Creates a dot badge.
Gets all admin-level tabs, filtered by permission and module-enabled status.
Gets viewer counts for all tabs.
Gets all registered tab groups.
Gets all subtabs for a given parent tab ID.
Gets a specific tab by ID.
Gets all registered tabs, sorted by priority.
Gets all tabs with their active state for the given path.
Gets only top-level tabs (tabs without a parent).
Gets all user-level tabs, filtered by visibility and scope.
Gets the number of users viewing a specific tab.
Creates a group header for organizing tabs.
Checks if the user has multiple contexts available.
Checks if a tab has any subtabs.
Increments a tab's count badge by a given amount.
Creates a live badge that subscribes to PubSub updates.
Loads the default admin tabs into the registry.
Checks if a tab matches the given path.
Creates a new Badge struct.
Creates a new Tab struct.
Creates a new Tab struct, raising on error.
Gets the PubSub topic for tab updates.
Registers admin tabs for an application namespace.
Registers tab groups for organizing the sidebar.
Registers dashboard tabs for an application namespace.
Sets an attention animation on a tab.
Checks if subtabs should be shown for a tab based on its display setting and active state.
Creates a status badge.
Subscribes the current process to tab updates.
Checks if a tab is a subtab (has a parent).
Tracks a user's presence on a dashboard tab.
Unregisters a specific tab by ID.
Unregisters all tabs for a namespace.
Updates a tab's badge.
Updates an existing tab's attributes by ID.
Checks if a tab is visible for the given scope.
Functions
@spec clear_attention(atom()) :: :ok
Clears attention animation from a tab.
@spec clear_badge(atom()) :: :ok
Clears a tab's badge.
@spec context_selector_enabled?() :: boolean()
Checks if the context selector feature is enabled.
Examples
if PhoenixKit.Dashboard.context_selector_enabled?() do
# Context switching is available
end
@spec count_badge( integer(), keyword() ) :: PhoenixKit.Dashboard.Badge.t()
Creates a count badge.
@spec current_context(Phoenix.LiveView.Socket.t() | map()) :: any() | nil
Gets the current context from socket assigns.
Returns nil if context selector is not configured or no context is selected.
Examples
context = PhoenixKit.Dashboard.current_context(socket)
# => %MyApp.Farm{id: 1, name: "My Farm"}
context = PhoenixKit.Dashboard.current_context(socket.assigns)
# => %MyApp.Farm{id: 1, name: "My Farm"}
@spec current_context_id(Phoenix.LiveView.Socket.t() | map()) :: any() | nil
Gets the current context ID from socket assigns.
Convenience function that extracts just the ID.
Examples
context_id = PhoenixKit.Dashboard.current_context_id(socket)
# => 1
Decrements a tab's count badge by a given amount.
Will not go below 0.
@spec divider(keyword()) :: PhoenixKit.Dashboard.Tab.t()
Creates a divider for visual separation in the sidebar.
Examples
PhoenixKit.Dashboard.divider(priority: 150)
PhoenixKit.Dashboard.divider(priority: 200, label: "Account")
@spec dot_badge(keyword()) :: PhoenixKit.Dashboard.Badge.t()
Creates a dot badge.
@spec get_admin_tabs(keyword()) :: [PhoenixKit.Dashboard.Tab.t()]
Gets all admin-level tabs, filtered by permission and module-enabled status.
Options
:scope- The current authentication scope for permission filtering
Examples
tabs = PhoenixKit.Dashboard.get_admin_tabs(scope: scope)
@spec get_all_viewer_counts() :: map()
Gets viewer counts for all tabs.
Examples
counts = PhoenixKit.Dashboard.get_all_viewer_counts()
# => %{orders: 3, settings: 1, printers: 2}
@spec get_groups() :: [PhoenixKit.Dashboard.Group.t()]
Gets all registered tab groups.
@spec get_subtabs( atom(), keyword() ) :: [PhoenixKit.Dashboard.Tab.t()]
Gets all subtabs for a given parent tab ID.
Examples
PhoenixKit.Dashboard.get_subtabs(:orders)
# => [%Tab{id: :pending_orders, parent: :orders, ...}, ...]
@spec get_tab(atom()) :: PhoenixKit.Dashboard.Tab.t() | nil
Gets a specific tab by ID.
Examples
tab = PhoenixKit.Dashboard.get_tab(:orders)
@spec get_tabs(keyword()) :: [PhoenixKit.Dashboard.Tab.t()]
Gets all registered tabs, sorted by priority.
Options
:scope- Filter by visibility using the current scope:include_hidden- Include tabs that would be hidden (default: false)
Examples
tabs = PhoenixKit.Dashboard.get_tabs()
tabs = PhoenixKit.Dashboard.get_tabs(scope: socket.assigns.phoenix_kit_current_scope)
Gets all tabs with their active state for the given path.
Returns tabs with an additional :active key.
Examples
tabs = PhoenixKit.Dashboard.get_tabs_with_active("/dashboard/orders")
@spec get_top_level_tabs(keyword()) :: [PhoenixKit.Dashboard.Tab.t()]
Gets only top-level tabs (tabs without a parent).
Examples
PhoenixKit.Dashboard.get_top_level_tabs()
# => [%Tab{id: :orders, parent: nil, ...}, ...]
@spec get_user_tabs(keyword()) :: [PhoenixKit.Dashboard.Tab.t()]
Gets all user-level tabs, filtered by visibility and scope.
Options
:scope- The current authentication scope for visibility filtering
Examples
tabs = PhoenixKit.Dashboard.get_user_tabs(scope: scope)
Gets the number of users viewing a specific tab.
Examples
count = PhoenixKit.Dashboard.get_viewer_count(:orders)
@spec group_header(keyword()) :: PhoenixKit.Dashboard.Tab.t()
Creates a group header for organizing tabs.
Examples
PhoenixKit.Dashboard.group_header(id: :farm, label: "Farm Management", priority: 200)
@spec has_multiple_contexts?(Phoenix.LiveView.Socket.t() | map()) :: boolean()
Checks if the user has multiple contexts available.
Returns true only if context selector is enabled and user has 2+ contexts.
Examples
if PhoenixKit.Dashboard.has_multiple_contexts?(socket) do
# Show context-specific UI
end
Checks if a tab has any subtabs.
Examples
PhoenixKit.Dashboard.has_subtabs?(:orders)
# => true
Increments a tab's count badge by a given amount.
Examples
PhoenixKit.Dashboard.increment_badge(:notifications)
PhoenixKit.Dashboard.increment_badge(:notifications, 5)
@spec live_badge(String.t(), atom() | (map() -> any()), keyword()) :: PhoenixKit.Dashboard.Badge.t()
Creates a live badge that subscribes to PubSub updates.
@spec load_admin_defaults() :: :ok
Loads the default admin tabs into the registry.
Called automatically on Registry startup, but can be called manually to reload defaults after changes.
@spec matches_path?(PhoenixKit.Dashboard.Tab.t(), String.t()) :: boolean()
Checks if a tab matches the given path.
@spec new_badge(map() | keyword()) :: {:ok, PhoenixKit.Dashboard.Badge.t()} | {:error, String.t()}
Creates a new Badge struct.
See PhoenixKit.Dashboard.Badge.new/1 for options.
@spec new_tab(map() | keyword()) :: {:ok, PhoenixKit.Dashboard.Tab.t()} | {:error, String.t()}
Creates a new Tab struct.
See PhoenixKit.Dashboard.Tab.new/1 for options.
@spec new_tab!(map() | keyword()) :: PhoenixKit.Dashboard.Tab.t()
Creates a new Tab struct, raising on error.
@spec pubsub_topic() :: String.t()
Gets the PubSub topic for tab updates.
Subscribe to this topic to receive real-time tab updates in LiveViews.
Example
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(PubSubHelper.pubsub(), PhoenixKit.Dashboard.pubsub_topic())
end
{:ok, socket}
end
def handle_info({:tab_updated, tab}, socket) do
# Handle tab update - refresh sidebar
{:noreply, assign(socket, tabs: PhoenixKit.Dashboard.get_tabs())}
end
def handle_info(:tabs_refreshed, socket) do
# Full tab list refresh
{:noreply, assign(socket, tabs: PhoenixKit.Dashboard.get_tabs())}
end
@spec register_admin_tabs(atom(), [map() | PhoenixKit.Dashboard.Tab.t()]) :: :ok | {:error, term()}
Registers admin tabs for an application namespace.
Automatically sets level: :admin on all tabs.
Examples
PhoenixKit.Dashboard.register_admin_tabs(:my_app, [
%{id: :admin_analytics, label: "Analytics", path: "/admin/analytics",
icon: "hero-chart-bar", permission: "dashboard"}
])
@spec register_groups([PhoenixKit.Dashboard.Group.t() | map()]) :: :ok
Registers tab groups for organizing the sidebar.
Examples
PhoenixKit.Dashboard.register_groups([
%{id: :main, label: nil, priority: 100},
%{id: :farm, label: "Farm Management", priority: 200, icon: "hero-cube"},
%{id: :account, label: "Account", priority: 900}
])
@spec register_tabs(atom(), [map() | PhoenixKit.Dashboard.Tab.t()]) :: :ok | {:error, term()}
Registers dashboard tabs for an application namespace.
Examples
# Register multiple tabs
PhoenixKit.Dashboard.register_tabs(:my_app, [
%{id: :orders, label: "Orders", path: "/dashboard/orders", icon: "hero-shopping-bag"},
%{id: :history, label: "History", path: "/dashboard/history", icon: "hero-clock"}
])
# Register a single tab
PhoenixKit.Dashboard.register_tabs(:my_app, [
Tab.new!(id: :custom, label: "Custom", path: "/dashboard/custom")
])
Sets an attention animation on a tab.
Animation Types
:pulse- Gentle pulsing glow:bounce- Bouncing motion:shake- Shaking motion (for errors/alerts):glow- Glowing effect
Examples
PhoenixKit.Dashboard.set_attention(:alerts, :pulse)
PhoenixKit.Dashboard.set_attention(:errors, :shake)
@spec show_subtabs?(PhoenixKit.Dashboard.Tab.t(), boolean()) :: boolean()
Checks if subtabs should be shown for a tab based on its display setting and active state.
Examples
PhoenixKit.Dashboard.show_subtabs?(tab, true) # parent is active
# => true (for :when_active or :always)
PhoenixKit.Dashboard.show_subtabs?(tab, false) # parent not active
# => true (only for :always)
@spec status_badge( atom() | String.t(), keyword() ) :: PhoenixKit.Dashboard.Badge.t()
Creates a status badge.
@spec subscribe() :: :ok | {:error, term()}
Subscribes the current process to tab updates.
Convenience wrapper around Phoenix.PubSub.subscribe/2.
@spec subtab?(PhoenixKit.Dashboard.Tab.t()) :: boolean()
Checks if a tab is a subtab (has a parent).
Examples
PhoenixKit.Dashboard.subtab?(:pending_orders)
# => true
@spec track_presence(Phoenix.LiveView.Socket.t(), atom(), keyword()) :: {:ok, String.t()} | {:error, term()}
Tracks a user's presence on a dashboard tab.
Examples
PhoenixKit.Dashboard.track_presence(socket, :orders)
@spec unregister_tab(atom()) :: :ok
Unregisters a specific tab by ID.
Examples
PhoenixKit.Dashboard.unregister_tab(:orders)
@spec unregister_tabs(atom()) :: :ok
Unregisters all tabs for a namespace.
Examples
PhoenixKit.Dashboard.unregister_tabs(:my_app)
@spec update_badge( atom(), integer() | map() | keyword() | PhoenixKit.Dashboard.Badge.t() | nil ) :: :ok
Updates a tab's badge.
Examples
# Set a count badge
PhoenixKit.Dashboard.update_badge(:notifications, 5)
# Set badge with options
PhoenixKit.Dashboard.update_badge(:alerts, count: 3, color: :error, pulse: true)
# Set a dot badge
PhoenixKit.Dashboard.update_badge(:status, type: :dot, color: :success)
# Clear a badge
PhoenixKit.Dashboard.update_badge(:notifications, nil)
Updates an existing tab's attributes by ID.
Examples
PhoenixKit.Dashboard.update_tab(:admin_dashboard, %{label: "Home", icon: "hero-home"})
@spec visible?(PhoenixKit.Dashboard.Tab.t(), map()) :: boolean()
Checks if a tab is visible for the given scope.