Creative Tim UICreative Tim UI

Shadcn Speed Dial

PreviousNext

A floating action button that expands to reveal multiple action buttons, perfect for quick access to common actions in a compact interface.

"use client"

import * as React from "react"
import { Home, LayoutDashboard, Plus, Settings } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function SpeedDialDemo() {
  const [open, setOpen] = React.useState(false)

  return (
    <TooltipProvider>
      <Tooltip open={open} onOpenChange={setOpen}>
        <TooltipTrigger asChild>
          <Button
            size="icon"
            className="rounded-full"
            onClick={() => setOpen(!open)}
          >
            <Plus
              className={cn(
                "h-[18px] w-[18px] transition-transform",
                open && "rotate-45"
              )}
            />
          </Button>
        </TooltipTrigger>
        <TooltipContent
          side="left"
          sideOffset={8}
          className="flex flex-col gap-2 border-0 bg-transparent p-0 shadow-none [&>svg]:hidden"
        >
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <Home className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent
                side="left"
                sideOffset={8}
                className="[&>svg]:hidden"
              >
                Home
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <Settings className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent
                side="left"
                sideOffset={8}
                className="[&>svg]:hidden"
              >
                Settings
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <LayoutDashboard className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent
                side="left"
                sideOffset={8}
                className="[&>svg]:hidden"
              >
                Dashboard
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}

Installation

pnpm dlx @creative-tim/ui@latest add tooltip button

Usage

import SpeedDialDemo from "@/components/speed-dial-demo"
<SpeedDialDemo />

Examples

Speed Dial Custom Icon

A speed dial with a custom icon (mail) as the main trigger button, demonstrating how to use different icons for the primary action button.

"use client"

import * as React from "react"
import { Home, LayoutDashboard, Mail, Settings } from "lucide-react"

import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function SpeedDialCustomIcon() {
  const [open, setOpen] = React.useState(false)

  return (
    <TooltipProvider>
      <Tooltip open={open} onOpenChange={setOpen}>
        <TooltipTrigger asChild>
          <Button
            size="icon"
            className="rounded-full"
            onClick={() => setOpen(!open)}
          >
            <Mail className="h-[18px] w-[18px]" />
          </Button>
        </TooltipTrigger>
        <TooltipContent
          side="left"
          sideOffset={8}
          className="flex flex-col gap-2 border-0 bg-transparent p-0 shadow-none [&>svg]:hidden"
        >
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <Home className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent
                side="left"
                sideOffset={8}
                className="[&>svg]:hidden"
              >
                Home
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <Settings className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent
                side="left"
                sideOffset={8}
                className="[&>svg]:hidden"
              >
                Settings
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <LayoutDashboard className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent
                side="left"
                sideOffset={8}
                className="[&>svg]:hidden"
              >
                Dashboard
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}

Speed Dial Horizontal

A speed dial with a horizontal layout where action buttons are arranged side by side instead of vertically stacked, ideal for wider screen spaces.

"use client"

import * as React from "react"
import { Home, LayoutDashboard, Plus, Settings } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function SpeedDialHorizontal() {
  const [open, setOpen] = React.useState(false)

  return (
    <TooltipProvider>
      <Tooltip open={open} onOpenChange={setOpen}>
        <TooltipTrigger asChild>
          <Button
            size="icon"
            className="rounded-full"
            onClick={() => setOpen(!open)}
          >
            <Plus
              className={cn(
                "h-[18px] w-[18px] transition-transform",
                open && "rotate-45"
              )}
            />
          </Button>
        </TooltipTrigger>
        <TooltipContent
          side="right"
          sideOffset={8}
          className="flex gap-2 border-0 bg-transparent p-0 shadow-none [&>svg]:hidden"
        >
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <Home className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent sideOffset={8} className="[&>svg]:hidden">
                Home
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <Settings className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent sideOffset={8} className="[&>svg]:hidden">
                Settings
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full"
                >
                  <LayoutDashboard className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
              <TooltipContent sideOffset={8} className="[&>svg]:hidden">
                Dashboard
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}

Speed Dial Placement

A demonstration of speed dial placement options showing how speed dials can be positioned in different corners of a container (top-left, top-right, bottom-left, bottom-right) with appropriate tooltip positioning.

"use client"

import * as React from "react"
import { Home, LayoutDashboard, Plus, Settings } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function SpeedDialPlacement() {
  const [open1, setOpen1] = React.useState(false)
  const [open2, setOpen2] = React.useState(false)
  const [open3, setOpen3] = React.useState(false)
  const [open4, setOpen4] = React.useState(false)

  return (
    <div className="relative h-80 w-full">
      <div className="absolute top-0 left-0">
        <TooltipProvider>
          <Tooltip open={open1} onOpenChange={setOpen1}>
            <TooltipTrigger asChild>
              <Button
                size="icon"
                className="rounded-full"
                onClick={() => setOpen1(!open1)}
              >
                <Plus
                  className={cn(
                    "h-[18px] w-[18px] transition-transform",
                    open1 && "rotate-45"
                  )}
                />
              </Button>
            </TooltipTrigger>
            <TooltipContent
              side="bottom"
              sideOffset={8}
              className="flex flex-col gap-2 border-0 bg-transparent p-0 shadow-none [&>svg]:hidden"
            >
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <Home className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="left"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Home
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <Settings className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="left"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Settings
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <LayoutDashboard className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="left"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Dashboard
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
            </TooltipContent>
          </Tooltip>
        </TooltipProvider>
      </div>
      <div className="absolute top-0 right-0">
        <TooltipProvider>
          <Tooltip open={open2} onOpenChange={setOpen2}>
            <TooltipTrigger asChild>
              <Button
                size="icon"
                className="rounded-full"
                onClick={() => setOpen2(!open2)}
              >
                <Plus
                  className={cn(
                    "h-[18px] w-[18px] transition-transform",
                    open2 && "rotate-45"
                  )}
                />
              </Button>
            </TooltipTrigger>
            <TooltipContent
              side="left"
              sideOffset={8}
              className="flex gap-2 border-0 bg-transparent p-0 shadow-none [&>svg]:hidden"
            >
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <Home className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="top"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Home
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <Settings className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="top"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Settings
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <LayoutDashboard className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="top"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Dashboard
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
            </TooltipContent>
          </Tooltip>
        </TooltipProvider>
      </div>
      <div className="absolute bottom-0 left-0">
        <TooltipProvider>
          <Tooltip open={open3} onOpenChange={setOpen3}>
            <TooltipTrigger asChild>
              <Button
                size="icon"
                className="rounded-full"
                onClick={() => setOpen3(!open3)}
              >
                <Plus
                  className={cn(
                    "h-[18px] w-[18px] transition-transform",
                    open3 && "rotate-45"
                  )}
                />
              </Button>
            </TooltipTrigger>
            <TooltipContent
              side="right"
              sideOffset={8}
              className="flex gap-2 border-0 bg-transparent p-0 shadow-none [&>svg]:hidden"
            >
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <Home className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent sideOffset={8} className="[&>svg]:hidden">
                    Home
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <Settings className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent sideOffset={8} className="[&>svg]:hidden">
                    Settings
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <LayoutDashboard className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent sideOffset={8} className="[&>svg]:hidden">
                    Dashboard
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
            </TooltipContent>
          </Tooltip>
        </TooltipProvider>
      </div>
      <div className="absolute right-0 bottom-0">
        <TooltipProvider>
          <Tooltip open={open4} onOpenChange={setOpen4}>
            <TooltipTrigger asChild>
              <Button
                size="icon"
                className="rounded-full"
                onClick={() => setOpen4(!open4)}
              >
                <Plus
                  className={cn(
                    "h-[18px] w-[18px] transition-transform",
                    open4 && "rotate-45"
                  )}
                />
              </Button>
            </TooltipTrigger>
            <TooltipContent
              side="top"
              sideOffset={8}
              className="flex flex-col gap-2 border-0 bg-transparent p-0 shadow-none [&>svg]:hidden"
            >
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <Home className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="left"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Home
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <Settings className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="left"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Settings
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <Button
                      size="icon"
                      variant="secondary"
                      className="rounded-full"
                    >
                      <LayoutDashboard className="h-[18px] w-[18px]" />
                    </Button>
                  </TooltipTrigger>
                  <TooltipContent
                    side="left"
                    sideOffset={8}
                    className="[&>svg]:hidden"
                  >
                    Dashboard
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
            </TooltipContent>
          </Tooltip>
        </TooltipProvider>
      </div>
    </div>
  )
}

Speed Dial with Custom Styles

A speed dial with custom styling including rounded borders, background colors, and padding, demonstrating how to customize the appearance to match your design system.

"use client"

import * as React from "react"
import { Home, LayoutDashboard, Plus, Settings } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function SpeedDialWithCustomStyles() {
  const [open, setOpen] = React.useState(false)

  return (
    <TooltipProvider>
      <Tooltip open={open} onOpenChange={setOpen}>
        <TooltipTrigger asChild>
          <Button
            size="icon"
            variant="secondary"
            className="rounded-full"
            onClick={() => setOpen(!open)}
          >
            <Plus
              className={cn(
                "h-[18px] w-[18px] transition-transform",
                open && "rotate-45"
              )}
            />
          </Button>
        </TooltipTrigger>
        <TooltipContent
          side="left"
          sideOffset={8}
          className="bg-background flex flex-col gap-2 rounded-full border p-1 shadow-none [&>svg]:hidden"
        >
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full p-3"
                >
                  <Home className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full p-3"
                >
                  <Settings className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
            </Tooltip>
          </TooltipProvider>
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  size="icon"
                  variant="secondary"
                  className="rounded-full p-3"
                >
                  <LayoutDashboard className="h-[18px] w-[18px]" />
                </Button>
              </TooltipTrigger>
            </Tooltip>
          </TooltipProvider>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}

Speed Dial with Label

A speed dial with always-visible tooltip labels on the action buttons, providing immediate context without requiring hover interactions.

"use client"

import * as React from "react"
import { Home, LayoutDashboard, Plus, Settings } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export function SpeedDialWithLabel() {
  const [open, setOpen] = React.useState(false)

  return (
    <TooltipProvider>
      <Tooltip open={open} onOpenChange={setOpen}>
        <TooltipTrigger asChild>
          <Button
            size="icon"
            className="rounded-full"
            onClick={() => setOpen(!open)}
          >
            <Plus
              className={cn(
                "h-[18px] w-[18px] transition-transform duration-200",
                open && "rotate-45"
              )}
            />
          </Button>
        </TooltipTrigger>
        <TooltipContent
          side="left"
          sideOffset={8}
          className="flex flex-col gap-3 border-0 bg-transparent p-0 shadow-none [&>svg]:hidden"
        >
          <div className="animate-in fade-in-0 slide-in-from-right-2 flex items-center gap-3 transition-all duration-200">
            <Button
              size="icon"
              variant="secondary"
              className="shrink-0 rounded-full"
            >
              <Home className="h-[18px] w-[18px]" />
            </Button>
            <span className="bg-background text-foreground rounded-md border px-3 py-1.5 text-sm font-medium whitespace-nowrap shadow-sm">
              Home
            </span>
          </div>
          <div className="animate-in fade-in-0 slide-in-from-right-2 flex items-center gap-3 transition-all delay-75 duration-200">
            <Button
              size="icon"
              variant="secondary"
              className="shrink-0 rounded-full"
            >
              <Settings className="h-[18px] w-[18px]" />
            </Button>
            <span className="bg-background text-foreground rounded-md border px-3 py-1.5 text-sm font-medium whitespace-nowrap shadow-sm">
              Settings
            </span>
          </div>
          <div className="animate-in fade-in-0 slide-in-from-right-2 flex items-center gap-3 transition-all delay-150 duration-200">
            <Button
              size="icon"
              variant="secondary"
              className="shrink-0 rounded-full"
            >
              <LayoutDashboard className="h-[18px] w-[18px]" />
            </Button>
            <span className="bg-background text-foreground rounded-md border px-3 py-1.5 text-sm font-medium whitespace-nowrap shadow-sm">
              Dashboard
            </span>
          </div>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}

Best Practices

  • Use speed dials for quick access to frequently used actions
  • Keep the number of action buttons to 3-5 for optimal usability
  • Position speed dials in corners or fixed positions to avoid interfering with content
  • Use clear, recognizable icons for action buttons
  • Provide tooltips or labels for better accessibility
  • Consider using horizontal layout for wider screens
  • Use animations to indicate state changes (open/closed)
  • Ensure speed dials don't obstruct important content
  • Use custom styles to match your design system
  • Consider mobile responsiveness when positioning speed dials

Speed dial components provide an elegant way to organize multiple actions in a compact floating button interface. They're ideal for dashboards, mobile apps, and any interface where space is limited but quick access to actions is important.