Skip to content
WANCRAFT

A.94Buttons & Controls

Speed Dial

A floating action button that fans out into labelled actions with staggered springs, as a stack or an arc.

NewReactMotionTailwindintermediate

Overview

Speed Dial turns one floating action button into a short menu of secondary actions. Pressing it rotates the plus into a close cross and springs the actions out one after another — straight up, down, left or right, or along a quarter-circle arc that curves towards the inside of its corner. Labels sit beside each action so nothing depends on guessing icons. Under the hood it is the WAI-ARIA menu button pattern: the trigger reports aria-expanded, actions are menu items reachable with the arrow keys, Escape closes and returns focus, and a press anywhere outside dismisses it. Pin it to the viewport with position="fixed" or to a container with "absolute".

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/speed-dial.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/speed-dial.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/speed-dial.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { SpeedDial, type SpeedDialAction } from "@/components/ui/speed-dial";

export function SpeedDialExample() {
  const actions: SpeedDialAction[] = [
    { id: "note", label: "New note", icon: <span>N</span>, onSelect: () => console.log("note") },
    { id: "upload", label: "Upload", icon: <span>U</span>, onSelect: () => console.log("upload") },
    { id: "share", label: "Share", icon: <span>S</span>, onSelect: () => console.log("share") },
  ];

  return (
    <SpeedDial actions={actions} label="Create" />
  );
}

03Props

PropTypeDefaultDescription
actionsSpeedDialAction[]—{ id: string; label: string; icon: ReactNode; onSelect?: () => void; disabled?: boolean }. The first action sits closest to the button.
direction"up" | "down" | "left" | "right""up"Which way the actions open.
layout"stack" | "arc""stack"Straight stack, or a 90° arc from direction towards the inside of the corner.
position"fixed" | "absolute" | "static""fixed"Pin to the viewport, to the nearest positioned parent, or keep in flow.
corner"bottom-right" | "bottom-left" | "top-right" | "top-left""bottom-right"Corner for fixed / absolute placement; also decides the label side and arc sweep.
offsetnumber24Distance from the corner in px.
showLabelsbooleantrueVisible labels beside actions (horizontal stacks: on hover / focus). Labels stay accessible names either way.
gapnumber12Space between actions in px.
staggernumber40Delay between actions when opening, in ms. Closing runs in reverse at half the delay.
spring{ stiffness: number; damping: number; mass: number }{ stiffness: 480, damping: 30, mass: 0.8 }Spring for the actions and the main icon.
rotationnumber45Degrees the main icon turns when open.
iconReactNodeplusMain button icon.
accentstring"#FF7448"Main button fill and focus ring colour.
labelstring"Quick actions"Accessible name of the main button; also labels the menu.
open / defaultOpenboolean— / falseControlled or initial open state.
onOpenChange(open: boolean) => void—Called when the menu opens or closes.
onAction(action: SpeedDialAction) => void—Called after any action is chosen.
...propsHTMLAttributes<HTMLDivElement>—Forwarded to the positioned root element.

04Features

  • 01Four directions, straight stack or quarter-circle arc
  • 02Staggered spring entrance; closing plays in reverse
  • 03Main icon rotates (plus → close by default)
  • 04Labels beside each action, radiating outward on the arc
  • 05Fixed, absolute or static positioning with a corner and offset
  • 06Controlled or uncontrolled open state, click-outside dismissal
motion · springmotion · staggerinput · clickinput · keyboardinput · focus

05Accessibility

  • Menu button pattern: the trigger has aria-haspopup="menu", aria-expanded and aria-controls; actions are role="menuitem" inside role="menu".
  • Opening by click, Enter, Space or an arrow key moves focus to the first action (or the last, with the arrow pointing back).
  • Arrow keys move between actions and wrap (the key pointing away from the button goes outward); Home / End jump; disabled actions are skipped.
  • Escape closes and returns focus to the trigger; Tab closes and moves on; pressing outside closes without stealing focus.
  • Each label is the text content of its menu item, so it is the accessible name even when labels are hidden.
  • With prefers-reduced-motion the actions fade in place with no stagger, spring or rotation travel.

06Limitations

  • Labels in horizontal stacks appear on hover or focus only, because side-by-side labels would overlap.
  • Keep it to about six actions; beyond that a regular menu is easier to scan.