Floating Action Button
<loomi-fab> — a floating action button. On its own it’s a single circular action
button; add <loomi-fab-item> children and it becomes a speed-dial menu.
npm install @loomidev/fab litimport "@loomidev/fab";Single Action Button
Section titled “Single Action Button”With no children, <loomi-fab> is just a floating button. It fires a normal click
event — wire it up like any button.
<loomi-fab icon="plus" onclick="router.push('/patients/create')"></loomi-fab>Speed-Dial Menu
Section titled “Speed-Dial Menu”Add <loomi-fab-item> children and the trigger becomes a menu toggle. Listen for
loomi-select on the <loomi-fab> to route each choice.
<loomi-fab placement="bottom-right" icon="plus"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item> <loomi-fab-item icon="document-plus" label="Create Report" value="report"></loomi-fab-item></loomi-fab>
<script type="module"> document.querySelector("loomi-fab").addEventListener("loomi-select", (event) => { const { value } = event.detail; if (value === "patient") router.push("/patients/create"); if (value === "appointment") router.push("/appointments/create"); if (value === "report") router.push("/reports/create"); });</script>The trigger’s icon rotates 45° when the menu opens — a plain “plus” glyph reads as a close (×) button without needing a second icon.
Placement
Section titled “Placement”placement picks a viewport corner in the default variant="floating" and, in
variant="docked", which edge the menu aligns its items to.
<loomi-fab placement="bottom-right"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab><loomi-fab placement="bottom-left"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab><loomi-fab placement="top-right"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab><loomi-fab placement="top-left"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab>Direction
Section titled “Direction”direction controls which way the menu expands. Left blank (the default), it’s inferred
from placement — bottom-* expands up, top-* expands down — so items always grow
into free space instead of off-screen.
<loomi-fab placement="bottom-right" direction="up"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab><loomi-fab placement="bottom-left" direction="down"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab><loomi-fab placement="top-right" direction="left"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab><loomi-fab placement="top-left" direction="right"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab>Trigger
Section titled “Trigger”trigger="click" (default) toggles the menu on click. trigger="hover" opens it on
mouseenter/focus, closing shortly after the pointer or focus leaves — clicking still
works too, so touch and keyboard users aren’t left without a way in.
<loomi-fab trigger="hover"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab>Variant
Section titled “Variant”variant="floating" (default) anchors the button to a viewport corner via
position: fixed, matching <loomi-bottom-nav>. Like any position: fixed element, an
ancestor with its own transform/filter/contain establishes a new containing block
and confines it to that ancestor instead of the real viewport — usually not what you
want on a live page, but handy for demoing several placements in one scrollable frame
(see <loomi-bottom-nav>’s example page for the technique). variant="docked" renders
in normal document flow — drop it into a toolbar, card footer, or bottom bar and it
stays exactly where you put it.
<div class="my-toolbar"> <loomi-fab variant="docked" placement="bottom-right"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item> </loomi-fab></div><loomi-fab size="small" placement="bottom-right"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item></loomi-fab><loomi-fab size="medium" placement="bottom-left"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item></loomi-fab><loomi-fab size="regular" placement="top-right"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item></loomi-fab><!-- regular is the default -->Backdrop & Closing
Section titled “Backdrop & Closing”backdrop(defaultfalse) — dims the rest of the page while the menu is open; clicking it closes the menu.close-on-select(defaulttrue) — closes the menu after aloomi-fab-itemis selected. Set it tofalsefor menus where picking one action shouldn’t dismiss the others.- Clicking outside the button and menu, or pressing Esc while focus is inside, always closes it.
<loomi-fab backdrop close-on-select="false"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab>color accents the trigger’s fill and softly tints each item’s icon circle —
primary (default), secondary, info, success, error, warning, or gray.
<loomi-fab color="success" icon="check"> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item></loomi-fab>Icons render through <loomi-icon> — the same registry and source attribute
(heroicons default, iconsax, untitledui) used by <loomi-bottom-nav>. Set
icon-source on <loomi-fab> to change the set for every icon at once, or on a single
<loomi-fab-item> to override just that one.
<loomi-fab icon="add" icon-source="iconsax"> <loomi-fab-item icon="user" label="Profile" value="profile"></loomi-fab-item> <loomi-fab-item icon="calendar" icon-source="heroicons" label="Book" value="book"></loomi-fab-item></loomi-fab>Icons Only
Section titled “Icons Only”Set icons-only on <loomi-fab> to hide every item’s visible label, showing only the
icon circle. Each label still reaches assistive tech (as the button’s aria-label) and
sighted users (as a <loomi-tooltip> that appears on hover/focus) — it just isn’t
rendered as inline text. The tooltip’s side is inferred the same way the label’s position
inside the pill normally is: away from the nearest screen edge (e.g. to the left for a
bottom-right-placed FAB).
<loomi-fab icons-only> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item></loomi-fab>Styling
Section titled “Styling”Every visual is a CSS custom property on :host, so re-theming needs no rebuild:
| Property | Default |
|---|---|
--loomi-fab-offset | 1.5rem — distance from the viewport edge (variant="floating") |
--loomi-fab-gap | 0.875rem — space between items |
--loomi-fab-trigger-gap | calc(var(--loomi-fab-gap) + 0.375rem) — space between the trigger and the menu (a bit more than --loomi-fab-gap, since the trigger’s bigger and more heavily shadowed) |
--loomi-fab-z-index | 1000 |
--loomi-fab-diameter | 2.75rem / 3.25rem / 3.75rem per size |
--loomi-fab-icon-size | 1.125rem / 1.25rem / 1.5rem per size |
--loomi-fab-item-diameter | 2rem / 2.25rem / 2.5rem per size |
--loomi-fab-item-icon-size | 0.9rem / 1rem / 1.125rem per size |
--loomi-fab-bg / --loomi-fab-bg-hover | derived from color |
--loomi-fab-fg | var(--loomi-text-on-primary) |
--loomi-fab-shadow | a soft drop shadow |
--loomi-fab-backdrop | rgba(15, 23, 42, 0.45) — matches <loomi-modal>’s backdrop tint |
--loomi-fab-backdrop-blur | blur(5px) — matches <loomi-modal>’s default blur-size="medium" |
loomi-fab { --loomi-fab-offset: 2rem; --loomi-fab-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);}Accessibility
Section titled “Accessibility”- The trigger is a real
<button>; when it has items it carriesaria-haspopup="menu"andaria-expanded. Give it a custom label withlabel— otherwise it falls back to a localized “Actions”. - Each
<loomi-fab-item>is a real<button role="menuitem">;disabledremoves it from click and keyboard navigation. - With the menu open: the arrow key pointing away from the trigger (matching
direction— e.g. ↑ forup) moves focus outward through items, the opposite arrow moves back and returns focus to the trigger past the first item, Home/End jump to the first/last item, and Esc closes the menu and refocuses the trigger. - Supports keyboard focus with visible
:focus-visiblestyling on the trigger and every item.
For the library-wide baseline, see Foundations — Accessibility.
Responsive behavior
Section titled “Responsive behavior”The trigger is a fixed-diameter circle and the menu sizes to its content — both work
unmodified from small phone screens to desktop. variant="floating" keeps a
--loomi-fab-offset margin from the viewport edge on any screen size; variant="docked"
just follows its parent’s own responsive layout.
For the shared container and viewport rules, see Foundations — Responsive behavior.
Dark mode
Section titled “Dark mode”Item pills and their icon circles use --loomi-surface, --loomi-surface-border, and
--loomi-text-secondary; the trigger and item-icon accent flow through the same
--loomi-<color>-* tokens as every other component. Respects .dark on <html> via
@loomidev/theme-switcher or your app theme.
For theme activation, token overrides, and contrast guidance, see Foundations — Dark mode.
Attributes — <loomi-fab>
Section titled “Attributes — <loomi-fab>”| Attribute | Default | Description |
|---|---|---|
placement | bottom-right | bottom-right | bottom-left | top-right | top-left |
direction | (blank) | up | down | left | right. Blank = infer from placement. |
trigger | click | click | hover |
variant | floating | floating | docked |
size | regular | small | medium | regular |
color | primary | primary | secondary | info | success | error | warning | gray |
icon | plus | Trigger icon name. |
icon-source | heroicons | heroicons | iconsax | untitledui |
icons-only | false | Hide every item’s visible label; show it as a <loomi-tooltip> on hover/focus instead. (boolean) |
label | (blank) | Accessible label for the trigger and menu. |
open | false | Menu open state (reflected). (boolean) |
disabled | false | Disable the trigger entirely. (boolean) |
close-on-select | true | Close the menu after an item is selected. (boolean) |
backdrop | false | Dim the page while the menu is open. (boolean) |
locale | (blank) | Locale override for the built-in aria label. |
Boolean attributes can be omitted, present, or set to "false" in HTML.
Methods: show(), hide(), toggle() (all void; no-ops when there are no items).
Attributes — <loomi-fab-item>
Section titled “Attributes — <loomi-fab-item>”| Attribute | Default | Description |
|---|---|---|
icon | (blank) | Icon name. |
icon-source | (blank) | Overrides the parent’s icon-source for just this item. |
label | (blank) | Visible text next to the icon. |
value | (blank) | Reported on event.detail.value when selected. |
disabled | false | Excludes the item from click and keyboard nav. (boolean) |
Fires: loomi-select — detail: { value, label }, bubbles/composed (also reaches a
listener on the parent <loomi-fab>).
| Slot | Description |
|---|---|
| (default) | Content placed inside the component. |
Events
Section titled “Events”| Event | Description |
|---|---|
close | Fired when the component closes. |
loomi-select | Fired when a FAB item is selected. |
open | Fired when the component opens. |
Full Example
Section titled “Full Example”<loomi-fab placement="bottom-right" direction="up" trigger="click" size="regular" color="primary" icon="plus" close-on-select backdrop> <loomi-fab-item icon="user-plus" label="Add Patient" value="patient"></loomi-fab-item> <loomi-fab-item icon="calendar" label="Book Appointment" value="appointment"></loomi-fab-item> <loomi-fab-item icon="document-plus" label="Create Report" value="report"></loomi-fab-item></loomi-fab>
<script type="module"> document.querySelector("loomi-fab").addEventListener("loomi-select", (event) => { console.log("selected:", event.detail.value); });</script>Framework integration
Section titled “Framework integration”<loomi-fab> is a standard custom element, so the browser can use it in plain HTML,
Blade, React, Vue, Angular, Svelte, Astro, and most other frameworks. The important
beginner rule is: install the package, import it once before the tag is rendered, then
write the Loomi tag in your template.
Where to run commands
Section titled “Where to run commands”Run install commands from the app where you want to use this component. That means the
folder that contains that app’s package.json. Do not run these install commands from
packages/fab unless you are editing LoomiUI itself.
cd /path/to/your-appnpm install @loomidev/fab litIf you are contributing to LoomiUI itself, first move to the top-level components
folder. That is where the main package.json for all packages lives, and
pnpm --filter ... commands should be run from there:
cd /path/to/your-copy-of-loomiui/componentspnpm --filter @loomidev/fab buildpnpm --filter @loomidev/fab typecheckChoose your framework
Section titled “Choose your framework”Use the CDN version for prototypes, documentation pages, or a quick reproduction. The import map tells the browser where to find Lit, which Loomi components use internally.
<script type="importmap"> { "imports": { "lit": "https://esm.sh/lit@3.3.3", "lit/": "https://esm.sh/lit@3.3.3/" } }</script><script type="module" src="https://esm.sh/@loomidev/fab"></script>
<loomi-fab icon="plus"> <loomi-fab-item label="Add Patient" value="patient"></loomi-fab-item></loomi-fab>In Vite, Webpack, Parcel, Rollup, or a framework build pipeline, install the package and import it once in your main app JavaScript file. After that, you can use the Loomi tag anywhere in your app.
import "@loomidev/fab";Run the install command from your Laravel project root, then import the component in
resources/js/app.js. If your project uses Laravel Vite, npm run dev and
npm run build should also be run from the Laravel project root.
cd /path/to/your-laravel-appnpm install @loomidev/fab litnpm run devimport "@loomidev/fab";<loomi-fab icon="plus"> <loomi-fab-item label="Add Patient" value="patient"></loomi-fab-item></loomi-fab>React can render Loomi tags directly. If you are on React 18, or if you need to pass arrays, objects, or functions, use a ref and assign those values after the component mounts.
import "@loomidev/fab";
export function LoomiExample() { return ( <loomi-fab icon="plus" onloomi-select={(e) => console.log(e.detail.value)}> <loomi-fab-item label="Add Patient" value="patient"></loomi-fab-item> </loomi-fab> );}If TypeScript does not recognize the Loomi tag in JSX, add it to your app’s JSX type declarations.
Import the package in the component that uses it, or once in your main Vue file. Vue templates can use Loomi tags directly. For arrays, objects, or functions, pass the value as a JavaScript property instead of as plain text.
<script setup>import "@loomidev/fab";</script>
<template> <loomi-fab icon="plus"> <loomi-fab-item label="Add Patient" value="patient"></loomi-fab-item> </loomi-fab></template>If Vue warns that the tag is an unknown component, configure
compilerOptions.isCustomElement for tags that start with loomi- in your Vite or Vue
config.
Import the package once and tell Angular to allow custom HTML tags with
CUSTOM_ELEMENTS_SCHEMA. For NgModule apps, add the schema to the module instead of the
standalone component.
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";import "@loomidev/fab";
@Component({ selector: "app-root", standalone: true, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: ` <loomi-fab icon="plus"> <loomi-fab-item label="Add Patient" value="patient"></loomi-fab-item> </loomi-fab> `,})export class AppComponent {}Svelte can import the package inside a component script. Astro can import it in the frontmatter of the page or layout where the tag appears.
<script> import "@loomidev/fab";</script>
<loomi-fab icon="plus"> <loomi-fab-item label="Add Patient" value="patient"></loomi-fab-item></loomi-fab>---import "@loomidev/fab";---
<loomi-fab icon="plus"> <loomi-fab-item label="Add Patient" value="patient"></loomi-fab-item></loomi-fab>Server-side rendering notes
Section titled “Server-side rendering notes”Frameworks such as Next.js, Nuxt, SvelteKit, and Astro sometimes render HTML on the
server before browser-only code runs. If your framework complains, move the Loomi import
to client-side code. In Next.js, that usually means a component with "use client"; in
Nuxt, it often means a .client.ts plugin.
Dependencies
Section titled “Dependencies”@loomidev/core@loomidev/icon@loomidev/tooltip