Skip to content
ShineUI
Install

SpeedDial

Quick creation from one floating point.

<sui-speed-dial>ButtonsStable

About

Floating quick-create dial: an orange launcher that fans navy satellite actions up, down, left or right. Satellite labels show as the dial's own navy capsules on hover and focus. Place the host yourself, usually fixed at the bottom right of a dense screen; the satellites sit out of flow, so opening the dial moves nothing on the page.

Usage

<sui-speed-dial aria-label="Create"></sui-speed-dial> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-speed-dial"); el.items = [ { label: "New invoice", icon: "fa-regular fa-file-lines" }, { label: "Receive payment", icon: "fa-solid fa-circle-dollar-to-slot" }, { label: "Enter bill", icon: "fa-regular fa-clipboard" }, ]; /* SuiSpeedDialItem[] */ el.addEventListener("suiPick", (e) => console.log(e.detail)); </script>
import { SuiSpeedDial } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const items = [ { label: "New invoice", icon: "fa-regular fa-file-lines" }, { label: "Receive payment", icon: "fa-solid fa-circle-dollar-to-slot" }, { label: "Enter bill", icon: "fa-regular fa-clipboard" }, ]; /* SuiSpeedDialItem[] */ return <SuiSpeedDial aria-label="Create" items={items} onSuiPick={(e) => console.log(e.detail)} />; }
<script setup> import { SuiSpeedDial } from "@shineui/vue"; import "@shineui/core/shineui.css"; const items = [ { label: "New invoice", icon: "fa-regular fa-file-lines" }, { label: "Receive payment", icon: "fa-solid fa-circle-dollar-to-slot" }, { label: "Enter bill", icon: "fa-regular fa-clipboard" }, ]; // SuiSpeedDialItem[] const onChange = (e) => console.log(e.detail); </script> <template> <SuiSpeedDial aria-label="Create" :items="items" @sui-pick="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiSpeedDial } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiSpeedDial], template: `<sui-speed-dial aria-label="Create" [(open)]="open" [items]="items" (suiPick)="onChange($event)"></sui-speed-dial>`, }) export class Example { open = false; items = [ { label: "New invoice", icon: "fa-regular fa-file-lines" }, { label: "Receive payment", icon: "fa-solid fa-circle-dollar-to-slot" }, { label: "Enter bill", icon: "fa-regular fa-clipboard" }, ]; // SuiSpeedDialItem[] onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

The launcher's accessible name is the element's own aria-label, "Quick actions" by default. Each satellite is a button named by its label, which also shows as a navy capsule on hover and focus.

<div style="min-height:220px;display:flex;align-items:flex-end"> <sui-speed-dial id="sd-basic" aria-label="Create"></sui-speed-dial> </div>
// setup(root): properties and events setup(root) { root.querySelector('#sd-basic').items = actions(); }

Directions

direction takes up, down, left and right; the first item is always nearest the launcher. A sideways fan draws each label above its satellite, and the launcher turns 45 degrees while open. The names are sides of the screen in either reading direction, so this row is pinned dir="ltr".

<div dir="ltr" style="min-height:240px;display:flex;align-items:flex-start;justify-content:space-between;padding:22px 24px 0"> <sui-speed-dial id="sd-right" direction="right" open aria-label="Create"></sui-speed-dial> <sui-speed-dial id="sd-down" direction="down" open aria-label="Create"></sui-speed-dial> <sui-speed-dial id="sd-left" direction="left" open aria-label="Create"></sui-speed-dial> </div>
// setup(root): properties and events setup(root) { ['#sd-right', '#sd-down', '#sd-left'].forEach((id) => { root.querySelector(id).items = actions(); }); }

Own glyphs

icon changes the launcher glyph and icon-open the glyph shown while fanned out. With icon-open set the launcher no longer rotates, because the turn only reads as a close on a plus.

<div style="min-height:220px;display:flex;align-items:flex-end;gap:24px"> <sui-speed-dial id="sd-glyph" icon="fa-solid fa-bolt" icon-open="fa-solid fa-xmark" aria-label="Quick actions"></sui-speed-dial> <sui-speed-dial id="sd-glyph-open" icon="fa-solid fa-bolt" icon-open="fa-solid fa-xmark" open aria-label="Quick actions"></sui-speed-dial> </div>
// setup(root): properties and events setup(root) { root.querySelector('#sd-glyph').items = actions(); root.querySelector('#sd-glyph-open').items = actions(); }

Fixed placement

The satellites are out of flow, so opening the dial moves nothing. The label capsules sit at the leading side of each satellite, the left here and the right under dir="rtl", so keep the dial clear of that edge.

The dial floats over this panel. Opening it moves nothing.

<div style="position:relative;height:200px;border:1px dashed var(--border);border-radius:var(--radius-md);overflow:hidden"> <p style="padding:12px;margin:0;font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">The dial floats over this panel. Opening it moves nothing.</p> <sui-speed-dial id="sd-fixed" aria-label="Create" style="position:absolute;inset-inline-end:16px;bottom:16px"></sui-speed-dial> </div>
// setup(root): properties and events setup(root) { root.querySelector('#sd-fixed').items = actions(); }

Disabled

disabled blocks the launcher and every satellite and closes an open dial. A single satellite can be disabled through its item.

<div style="min-height:80px;display:flex;align-items:flex-end"> <sui-speed-dial id="sd-off" disabled aria-label="Create"></sui-speed-dial> </div>
// setup(root): properties and events setup(root) { root.querySelector('#sd-off').items = actions(); }

Events

suiOpen fires after the element fans the dial out itself, on a launcher press or an arrow key; a cancelable suiClose fires before it closes itself, with the reason as detail; a satellite emits suiPick and runs item.command(). Writing open from outside echoes neither.

Nothing picked yet.
<div style="min-height:220px;display:flex;align-items:flex-end;gap:14px"> <sui-speed-dial id="sd-live" aria-label="Create"></sui-speed-dial> <span id="sd-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Nothing picked yet.</span> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#sd-log'); const dial = root.querySelector('#sd-live'); dial.items = [ { label: 'New invoice', icon: 'fa-regular fa-file-lines' }, { label: 'Receive payment', icon: 'fa-solid fa-circle-dollar-to-slot' }, { label: 'Enter bill', icon: 'fa-regular fa-clipboard', disabled: true }, ]; dial.addEventListener('suiPick', (e) => { log.textContent = `Picked ${e.detail.label}.`; }); dial.addEventListener('suiOpen', () => { log.textContent = 'Pick an action.'; }); dial.addEventListener('suiClose', (e) => { log.textContent = `Closed (${e.detail}).`; }); }

Keyboard

KeyAction
Enter SpaceOn the launcher, opens or closes the dial. On a satellite, runs it, closes the dial and returns focus to the launcher.
ArrowUp ArrowDown ArrowLeft ArrowRightOn the launcher, open the dial and focus the first satellite; the arrow that points back at the launcher focuses the last. In the fan, the pair along the fan's axis moves away from and back toward the launcher, wrapping at the ends and skipping disabled satellites; the other pair moves down and up the list, the horizontal one in reading order.
Home EndFocus the first or the last satellite.
Tab Shift+TabMove through the satellites; each is its own tab stop. Tabbing out of the dial closes it.
EscapeCloses the dial and returns focus to the launcher.

API

<sui-speed-dial>

Properties

PropertyAttributeTypeDefaultDescription
directiondirection"down" | "left" | "right" | "up"'up'Where the satellites fan out from the launcher. The first item is always the one nearest the launcher. An unrecognised value falls back to up.
disableddisabledbooleanfalseBlocks the launcher and every satellite, and closes the dial. Reflected.
iconiconstring'fa-solid fa-plus'Launcher glyph, a Font Awesome class. The default assumes the Font Awesome sheet is on the page, as every icon prop in the library does; a page without it sets its own class here.
iconOpenicon-openstringstring | undefinedGlyph shown while open. Set it and the launcher stops rotating: the 45 degree turn only reads as a close affordance on a plus.
itemsproperty onlySuiSpeedDialItem[][]Satellite actions: { label, icon, disabled, command }[]. Property, not attribute. Each satellite is a button named by its label, which is also the capsule text. On an up or down dial the capsule sits at the satellite's inline-start (its left, or its right under dir="rtl"), so keep the dial clear of that viewport edge; on a left or right dial it sits above. direction itself names a side of the screen in either reading direction. The launcher's own name is the element's aria-label attribute, "Quick actions" by default; there is no ariaLabel prop.
openopenbooleanfalseFanned out. Mutable: the launcher toggles it, and Escape, an outside pointer, Tabbing out and a pick all clear it, announcing each with a cancelable suiClose, so plain HTML works with no listener at all. It is what you asked for, never rewritten behind your back: a disabled dial draws no satellites whatever open says, and they fan back out when it is enabled again.

Events

EventDetailReactVueDescription
suiCloseSuiCloseReason"cancel" | "close-button" | "escape" | "outside" | "pick" | "scrim" | "trigger"onSuiClose@sui-closeFires before the element closes the dial itself; detail is why: 'pick' (a satellite ran), 'trigger' (the launcher was pressed again), 'outside' (a pointer landed elsewhere, or Tab moved focus out of the dial), 'escape', or 'cancel' (the dial went disabled while it was fanned out). Cancelable: preventDefault() keeps it open, so a parent that binds open never goes stale and "a stray click outside must not close this" needs no prop of its own. On 'cancel' a veto means the satellites stay hidden while the dial is disabled and fan back out when it is enabled again, because open && !disabled and not open alone decides what renders. It does not bubble.
suiOpenvoidonSuiOpen@sui-openFires after the element fans the dial out itself (a launcher press, or an arrow key on the launcher). An external open write does not echo it. It does not bubble.
suiPickSuiSpeedDialItemonSuiPick@sui-pickFires when a satellite is pressed; detail is the item. item.command() also runs.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the launcher.

Types

SuiCloseReason typeWhy a dismissible surface is closing. Detail of the cancelable suiClose. A consumer expresses "a stray backdrop click must not close this" by calling preventDefault() on the reason it does not accept, which is why none of these needs a prop of its own.
No members.
SuiSpeedDialItem interfaceOne satellite action. label is the accessible name and the hover capsule text.
labelstring
disabled?booleanGreys the satellite and blocks its press.
iconstringFont Awesome class, e.g. "fa-regular fa-file-lines".
command?() => voidRuns on press (in addition to the suiPick event).

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also