9 overlay components — modal dialogs, side drawers, sheets, dropdown menus, context menus, popovers, tooltips, hover cards, and the command menu primitive.
Module: PhiaUi.Components.Overlay
import PhiaUi.Components.OverlayAll overlays use Phoenix.LiveView.JS for open/close with focus management. No extra hooks needed except where noted.
Table of Contents
dialog
Modal dialog with focus trap, Escape to close, and scroll lock.
Sub-components: dialog_trigger, dialog_content, dialog_header, dialog_title, dialog_description, dialog_footer, dialog_close
<%!-- Trigger + dialog --%>
<.dialog id="edit-user">
<:trigger>
<.button variant="outline">Edit user</.button>
</:trigger>
<:content>
<.dialog_header>
<.dialog_title>Edit user</.dialog_title>
<.dialog_description>Update the user's profile information.</.dialog_description>
</.dialog_header>
<.form for={@form} phx-submit="update_user" class="space-y-4 mt-4">
<.phia_input field={@form[:name]} label="Name" />
<.phia_input field={@form[:email]} type="email" label="Email" />
<.dialog_footer>
<.dialog_close><.button variant="outline">Cancel</.button></.dialog_close>
<.button type="submit">Save changes</.button>
</.dialog_footer>
</.form>
</:content>
</.dialog><%!-- Server-controlled dialog --%>
<.button phx-click={JS.show(to: "#confirm-dialog")}>Delete</.button>
<.dialog id="confirm-dialog">
<:content>
<.dialog_header>
<.dialog_title>Confirm deletion</.dialog_title>
</.dialog_header>
<p class="text-muted-foreground">This action cannot be undone.</p>
<.dialog_footer class="mt-4">
<.button variant="outline" phx-click={JS.hide(to: "#confirm-dialog")}>Cancel</.button>
<.button variant="destructive" phx-click="confirm_delete">Delete</.button>
</.dialog_footer>
</:content>
</.dialog>drawer
Full-height side panel that slides in from left or right.
<.drawer id="user-details" side={:right}>
<:trigger>
<.button variant="ghost" size="sm">View details</.button>
</:trigger>
<:content>
<div class="p-6 h-full overflow-y-auto">
<h2 class="text-lg font-semibold"><%= @user.name %></h2>
<p class="text-muted-foreground"><%= @user.email %></p>
<.separator class="my-6" />
<.description_list>
<:item label="Role"><.badge><%= @user.role %></.badge></:item>
<:item label="Joined"><.relative_time datetime={@user.inserted_at} /></:item>
<:item label="Status"><.status_indicator status={if @user.active, do: :online, else: :offline} /></:item>
</.description_list>
</div>
</:content>
</.drawer>Attrs: id (required), side (:left | :right, default :right), size (:sm | :md | :lg | :full)
sheet
Smaller bottom or side sheet. Ideal for mobile overlays or quick actions.
<.sheet id="quick-add" side={:bottom}>
<:trigger>
<.button><.icon name="plus" class="mr-2" />New task</.button>
</:trigger>
<:content>
<div class="p-4 space-y-3">
<h3 class="font-semibold">New task</h3>
<.input name="title" placeholder="Task title…" />
<.button class="w-full" phx-click="add_task">Add</.button>
</div>
</:content>
</.sheet>Attrs: side (:top | :right | :bottom | :left)
dropdown_menu
Dropdown with items, submenus, separators, and labels.
<.dropdown_menu id="actions-dd">
<:trigger>
<.button variant="ghost" size="icon" aria-label="More options">
<.icon name="more-horizontal" />
</.button>
</:trigger>
<:content>
<.dropdown_menu_item phx-click="edit" phx-value-id={@record.id}>
<.icon name="pencil" size="sm" class="mr-2" />Edit
</.dropdown_menu_item>
<.dropdown_menu_item phx-click="duplicate">
<.icon name="copy" size="sm" class="mr-2" />Duplicate
</.dropdown_menu_item>
<.dropdown_menu_separator />
<.dropdown_menu_item variant="destructive" phx-click="delete" phx-value-id={@record.id}>
<.icon name="trash" size="sm" class="mr-2" />Delete
</.dropdown_menu_item>
</:content>
</.dropdown_menu>context_menu
Right-click context menu.
<.context_menu id="row-ctx">
<:trigger>
<div class="p-3 hover:bg-muted/50 rounded cursor-context-menu">
Right-click me
</div>
</:trigger>
<:content>
<.context_menu_item phx-click="open">Open</.context_menu_item>
<.context_menu_item phx-click="rename">Rename</.context_menu_item>
<.context_menu_separator />
<.context_menu_item variant="destructive" phx-click="delete">Delete</.context_menu_item>
</:content>
</.context_menu>popover
Non-modal popup anchored to a trigger — for forms, colour pickers, or rich content.
<.popover id="date-picker-pop">
<:trigger>
<.button variant="outline" class="gap-2">
<.icon name="calendar" size="sm" />
<%= if @date, do: Calendar.strftime(@date, "%b %d, %Y"), else: "Pick a date" %>
</.button>
</:trigger>
<:content>
<.calendar id="pop-calendar" selected={@date} on_select="set_date" />
</:content>
</.popover>tooltip
Hover tooltip anchored to any element.
<.tooltip content="Copy to clipboard">
<.button variant="ghost" size="icon" phx-click="copy">
<.icon name="copy" />
</.button>
</.tooltip>
<%!-- Rich tooltip with side --%>
<.tooltip side={:right}>
<:content>
<p class="font-medium">Pro plan</p>
<p class="text-xs text-muted-foreground">Unlimited seats, priority support</p>
</:content>
<.button variant="outline">Upgrade</.button>
</.tooltip>Attrs: content (string shorthand), side (:top | :right | :bottom | :left), delay (ms)
hover_card
Larger hover card — show preview content on mouse-over.
<.hover_card id="user-hc">
<:trigger>
<a href={~p"/users/#{@user}"} class="font-medium hover:underline"><%= @user.name %></a>
</:trigger>
<:content>
<div class="flex gap-3 p-1">
<.avatar size="md">
<.avatar_image src={@user.avatar_url} />
<.avatar_fallback name={@user.name} />
</.avatar>
<div>
<p class="font-semibold"><%= @user.name %></p>
<p class="text-sm text-muted-foreground"><%= @user.bio %></p>
<div class="flex gap-2 mt-2">
<.badge variant="secondary"><%= @user.role %></.badge>
<.relative_time datetime={@user.inserted_at} class="text-xs text-muted-foreground" />
</div>
</div>
</div>
</:content>
</.hover_card>command
Low-level command menu primitive — used to build command_palette. Use this for custom command UIs embedded within pages.
<.command id="inline-cmd">
<.command_input placeholder="Type a command…" />
<.command_list>
<.command_group label="Suggestions">
<.command_item phx-click="new_file">
<.icon name="file-plus" class="mr-2" />New file
</.command_item>
<.command_item phx-click="new_folder">
<.icon name="folder-plus" class="mr-2" />New folder
</.command_item>
</.command_group>
</.command_list>
</.command>For the full keyboard-accessible command palette (⌘K), use command_palette from the Navigation module.