FAB

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJS-moduleJSApache-2.0
Installation mix mishka.ui.gen.headless fab
Source View Source

Overview of the Phoenix and LiveView Headless Fab Component

The Mishka Chelekom Headless Fab is an unstyled Phoenix function component for a floating action button that can fan out into a speed dial. The trigger is a real button driven by the shared Popup engine, so it carries aria-expanded, closes on Escape and on an outside click, and marks its popup with data-open or data-closed for your styles. With no actions it is just a button, and no popup is rendered.

The icon slot is the trigger's glyph and each action slot adds one item to the dial. In a LiveView page an action can push an event with on_click or navigate with navigate. The parts take trigger_class, icon_class, popup_class, action_class, action_icon_class, label_class and main_action_class, and the root takes class.

Base UI Fab example - speed dial with three actions (Phoenix headless)

Base UI Fab example - speed dial with three actions (Phoenix headless)

heex
<div class="relative h-60 w-64 overflow-hidden rounded-lg border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-950">
  <.fab
    id="baseui-fab-hero"
    label="Actions"
    contained
    class="absolute right-3 bottom-3 flex flex-col-reverse items-end gap-2"
    trigger_class="size-12 rounded-full border border-neutral-950 bg-neutral-950 text-lg text-white dark:border-white dark:bg-white dark:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
    popup_class="flex flex-col-reverse items-end gap-2 data-closed:hidden"
    action_class="inline-flex size-10 items-center justify-center rounded-full border border-neutral-200 bg-white text-sm text-neutral-950 hover:bg-neutral-100 dark:border-neutral-800 dark:bg-neutral-950 dark:text-white dark:hover:bg-neutral-800"
  >
    <:icon>+</:icon>
    <:action label="Share">S</:action>
    <:action label="Copy">C</:action>
    <:action label="Edit">E</:action>
  </.fab>
</div>

Actions

Each action slot is one item of the dial, and its content is the glyph. label is required and becomes the item's aria-label. show_label also draws the label as text beside the glyph, in a span with data-part="label" that is hidden from assistive technology because the aria-label already says it. tip sets data-tip for a tooltip style. disabled sets data-disabled and, on a button, disables it. class adds classes to that one item.

An action is a button unless it has navigate, a LiveView navigation drawn as a link, or href, a plain link. In both cases it renders as an a. On a button, on_click takes a LiveView event name or a Phoenix.LiveView.JS command. Every item has role="menuitem" and a data-index.

heex
<.fab id="compose-fab" label="Compose">
  <:icon>+</:icon>
  <:action label="Share" on_click="share">S</:action>
  <:action label="Open drafts" navigate={~p"/drafts"} show_label>D</:action>
</.fab>

Placement, direction and containment

placement is bottom-end by default, or bottom-start, top-end or top-start. direction is up by default, or down, left, right or flower. contained is a boolean. They are written to the root as data-placement, data-direction and data-contained, and your classes read them, for example data-[placement=bottom-end]:right-4 or data-contained:absolute. direction also sets data-side on the root, which the Popup engine uses to place the popup. flower is left entirely to your layout.

Close icon and main action

Fill close_icon to show another glyph while the dial is open. Both glyphs are rendered in spans marked data-part="icon", with data-state="closed" or data-state="open", and your CSS shows one of them using the popup's data-open. When close_icon is given, the trigger's aria-label is close_label, which defaults to Close, instead of label.

A main_action slot adds one more button to the popup, marked data-part="main-action". A style can lay it over the trigger while the dial is open. It takes label (required), on_click, navigate, href and class, and main_action_class is added on top of action_class.

Choosing an item closes the dial. When you build your own JS chain, the generated module has close/2. It removes data-open from the popup, sets data-closed, and sets aria-expanded to false on the trigger.

Parts & data attributes

The root carries data-part="root", data-side, data-align, data-placement, data-direction and data-contained. The parts are trigger, icon, popup, action, main-action and label. The popup has role="menu" and starts with data-closed. The Popup engine swaps that for data-open while the dial is open. Each element also has a structural class such as chelekom-fab__popup.

Accessibility

Follows the WAI-ARIA Menu Button pattern. When there are actions, the trigger has aria-haspopup, aria-expanded and aria-controls. Enter or Space opens the dial and moves focus to the first action. Tab moves through the actions. Escape closes the dial and returns focus to the trigger. A click outside it closes the dial too. The popup is named by label.

The items are reached with Tab. The arrow keys do not move between them.