Dock
v0.0.10
mix mishka.ui.gen.headless dock
Overview of the Phoenix and LiveView Headless Dock Component
The Mishka Chelekom Headless Dock is a bar of destinations for Phoenix and LiveView, usually pinned to the bottom of a phone-sized layout. It renders a <nav> with an accessible name. The active destination gets aria-current="page" as well as data-active, so a screen reader announces it as the current one instead of only coloring it.
Each item is a link when it has navigate, patch or href, a button when it has on_select, and a plain <span> otherwise. The slot body is the icon and label is the text. The root takes class, and the parts take item_class, icon_class and label_class.
Base UI Dock example - three destinations in a frame (Phoenix headless)
The frame is relative and the dock is absolute inset-x-0 bottom-0, so the bar sits at the bottom edge of the frame instead of the viewport. contained records that choice on the root as data-contained. The Inbox item is active.
Base UI Dock example - three destinations in a frame (Phoenix headless)
<div class="relative h-40 w-72 overflow-hidden rounded-lg border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-950">
<.dock
id="baseui-dock-hero"
label="Sections"
contained
class="absolute inset-x-0 bottom-0 flex items-stretch justify-around border-t border-neutral-200 bg-white p-1 dark:border-neutral-800 dark:bg-neutral-950"
item_class="flex flex-1 flex-col items-center gap-1 rounded-md py-2 text-xs text-neutral-600 hover:bg-neutral-100 data-[active]:text-neutral-950 dark:text-neutral-400 dark:hover:bg-neutral-800 dark:data-[active]:text-white"
>
<:item label="Home" href="#">
<.icon name="hero-home" class="size-5" />
</:item>
<:item label="Inbox" href="#" active>
<.icon name="hero-inbox" class="size-5" />
</:item>
<:item label="Settings" href="#">
<.icon name="hero-cog-6-tooth" class="size-5" />
</:item>
</.dock>
</div>Item types and on_select
An item with an address is a link. navigate and patch keep LiveView navigation working, and href is a normal link. An item with on_select is a <button type="button"> for a dock that switches a panel instead of a route. on_select takes a LiveView event name, which receives %{"index" => index}, or a Phoenix.LiveView.JS command. An item with neither is plain text and takes no tab stop.
<.dock label="Sections" contained class="flex justify-around">
<:item label="Home" on_select="show_panel" active={@panel == 0}>
<.icon name="hero-home" class="size-5" />
</:item>
<:item label="Inbox" on_select="show_panel" active={@panel == 1}>
<.icon name="hero-inbox" class="size-5" />
</:item>
</.dock>def handle_event("show_panel", %{"index" => index}, socket) do
{:noreply, assign(socket, :panel, index)}
enddisabled sets data-disabled and aria-disabled="true", and a button item also gets the native disabled attribute. A link keeps its address, so leave the address out too when the item must not be followed.
Position, contained and show_labels
The component adds no positioning of its own. position (bottom by default, or top) is written to data-position, and contained to data-contained. Use those two attributes in your classes to choose fixed or absolute placement. show_labels={false} sets data-hidden on every label. The label text stays in the DOM, so style data-[hidden]:sr-only to hide it visually and keep it for screen readers.
<.dock
label="Primary"
position="bottom"
class="fixed inset-x-0 flex justify-around bg-white data-[position=bottom]:bottom-0 data-[position=top]:top-0 data-[contained]:absolute"
label_class="data-[hidden]:sr-only"
show_labels={false}
>
<:item label="Home" navigate={~p"/"}><.icon name="hero-home" class="size-5" /></:item>
<:item label="Settings" navigate={~p"/settings"}><.icon name="hero-cog-6-tooth" class="size-5" /></:item>
</.dock>Parts & data attributes
Each part carries a stable data-part and a structural chelekom-dock__* class you can target, plus the state attributes below.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Root | root | aria-label, data-position, data-contained | The nav landmark. |
| Item | item | data-index, data-active, data-disabled, aria-current, aria-disabled | A link, a button or a span, depending on the item attributes. |
| Icon | icon | aria-hidden | Wraps the slot body. |
| Label | label | data-hidden | The destination's name. Rendered only when the item has a label. |
Accessibility
There is no formal WAI-ARIA APG pattern for a dock. It follows the navigation landmark practice: a nav with an accessible name, destinations as links, and aria-current="page" on the current one. Tab moves through the items. Enter follows a link and presses a button, and Space also presses a button. A destination with no address and no on_select is text, so it never takes a tab stop it cannot use.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | No | Unique id |
label
|
string
|
Primary
| — | No | Accessible name for the nav landmark |
position
|
string
|
bottom
|
bottomtop
| No | Which edge the bar is pinned to |
contained
|
boolean
|
false
| — | No | Drop the fixed positioning so the dock sits inside its container |
show_labels
|
boolean
|
true
| — | No | When false the labels stay in the DOM for screen readers but are visually hidden |
class
|
any
| — | — | No | Extra classes for the root |
item_class
|
any
| — | — | No | — |
label_class
|
any
| — | — | No | — |
icon_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
item
| No | One destination; the slot body is the icon |
Attributes of the item slot
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
label
|
string
| — | — | No | The destination's name |
active
|
boolean
| — | — | No | Marks this the current destination |
navigate
|
string
| — | — | No | — |
patch
|
string
| — | — | No | — |
href
|
string
| — | — | No | — |
on_select
|
any
| — | — | No | A JS command or event name; renders a button instead of a link |
disabled
|
boolean
| — | — | No | — |
class
|
any
| — | — | No | — |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
dock
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless dock