SpeedDial

SpeedDial is a floating action button with a popup menu displaying a set of actions.

basic-demo

Usage#

import { PlusIcon } from '@primereact/icons';
import { SpeedDial } from '@primereact/ui/speeddial';
import { Pencil } from '@primeicons/react/pencil';
<SpeedDial.Root direction="up">
    <SpeedDial.Trigger>
        <PlusIcon />
    </SpeedDial.Trigger>
    <SpeedDial.List>
        <SpeedDial.Item>
            <SpeedDial.Action>
                <Pencil />
            </SpeedDial.Action>
        </SpeedDial.Item>
    </SpeedDial.List>
</SpeedDial.Root>

Examples#

Linear#

When type is set to linear (default), items are displayed in a line based on the direction prop. The direction can be up, down, left, or right.

linear-demo
import { useSpeedDialProps } from '@primereact/types/shared/speeddial';
import { SpeedDial } from '@primereact/ui/speeddial';
import { Plus } from '@primeicons/react/plus';
import { Pencil } from '@primeicons/react/pencil';
import { Refresh } from '@primeicons/react/refresh';
import { Trash } from '@primeicons/react/trash';
import { Upload } from '@primeicons/react/upload';
import { ExternalLink } from '@primeicons/react/external-link';

const directions = [
    { direction: 'up', style: { position: 'absolute', left: 'calc(50% - 2rem)', bottom: 0 } },
    { direction: 'down', style: { position: 'absolute', left: 'calc(50% - 2rem)', top: 0 } },
    { direction: 'left', style: { position: 'absolute', top: 'calc(50% - 2rem)', right: 0 } },
    { direction: 'right', style: { position: 'absolute', top: 'calc(50% - 2rem)', left: 0 } }
];

export default function LinearDemo() {
    const items = [
        { icon: Pencil, label: 'Edit' },
        { icon: Refresh, label: 'Refresh' },
        { icon: Trash, label: 'Delete' },
        { icon: Upload, label: 'Upload' },
        { icon: ExternalLink, label: 'External' }
    ];

    return (
        <div>
            <div style={{ position: 'relative', height: '500px' }}>
                {directions.map((item, index) => (
                    <SpeedDial.Root
                        key={index}
                        direction={item.direction as useSpeedDialProps['direction']}
                        style={item.style as React.CSSProperties}
                    >
                        <SpeedDial.Trigger className="transition-transform duration-200 data-open:rotate-45">
                            <Plus />
                        </SpeedDial.Trigger>
                        <SpeedDial.List>
                            {items.map((action) => {
                                const Icon = action.icon;

                                return (
                                    <SpeedDial.Item key={action.label}>
                                        <SpeedDial.Action>
                                            <Icon />
                                        </SpeedDial.Action>
                                    </SpeedDial.Item>
                                );
                            })}
                        </SpeedDial.List>
                    </SpeedDial.Root>
                ))}
            </div>
        </div>
    );
}

Circle#

Items can be displayed around the button when type is set to circle. Additional radius property defines the radius of the circle.

circle-demo
import { SpeedDial } from '@primereact/ui/speeddial';
import { Plus } from '@primeicons/react/plus';
import { Pencil } from '@primeicons/react/pencil';
import { Refresh } from '@primeicons/react/refresh';
import { Trash } from '@primeicons/react/trash';
import { Upload } from '@primeicons/react/upload';
import { ExternalLink } from '@primeicons/react/external-link';
import { Cog } from '@primeicons/react/cog';
import { User } from '@primeicons/react/user';
import { Heart } from '@primeicons/react/heart';

export default function CircleDemo() {
    const items = [
        { icon: Pencil, label: 'Edit' },
        { icon: Refresh, label: 'Refresh' },
        { icon: Trash, label: 'Delete' },
        { icon: Upload, label: 'Upload' },
        { icon: ExternalLink, label: 'External' },
        { icon: Cog, label: 'Settings' },
        { icon: User, label: 'User' },
        { icon: Heart, label: 'Favorite' }
    ];

    return (
        <div>
            <div className="flex items-center justify-center" style={{ position: 'relative', height: '500px' }}>
                <SpeedDial.Root type="circle" radius={80} style={{ position: 'absolute' }}>
                    <SpeedDial.Trigger severity="warn" className="transition-transform duration-200 data-open:rotate-45">
                        <Plus />
                    </SpeedDial.Trigger>
                    <SpeedDial.List>
                        {items.map((action) => {
                            const Icon = action.icon;

                            return (
                                <SpeedDial.Item key={action.label}>
                                    <SpeedDial.Action>
                                        <Icon />
                                    </SpeedDial.Action>
                                </SpeedDial.Item>
                            );
                        })}
                    </SpeedDial.List>
                </SpeedDial.Root>
            </div>
        </div>
    );
}

Semi Circle#

When type is defined as semi-circle, items are displayed in a half-circle around the button.

semicircle-demo
import { useSpeedDialProps } from '@primereact/types/shared/speeddial';
import { SpeedDial } from '@primereact/ui/speeddial';
import { Plus } from '@primeicons/react/plus';
import { Pencil } from '@primeicons/react/pencil';
import { Refresh } from '@primeicons/react/refresh';
import { Trash } from '@primeicons/react/trash';
import { Upload } from '@primeicons/react/upload';
import { ExternalLink } from '@primeicons/react/external-link';

const directions = [
    { direction: 'up', style: { position: 'absolute', left: 'calc(50% - 2rem)', bottom: 0 } },
    { direction: 'down', style: { position: 'absolute', left: 'calc(50% - 2rem)', top: 0 } },
    { direction: 'left', style: { position: 'absolute', top: 'calc(50% - 2rem)', right: 0 } },
    { direction: 'right', style: { position: 'absolute', top: 'calc(50% - 2rem)', left: 0 } }
];

export default function SemiCircleDemo() {
    const items = [
        { icon: Pencil, label: 'Edit' },
        { icon: Refresh, label: 'Refresh' },
        { icon: Trash, label: 'Delete' },
        { icon: Upload, label: 'Upload' },
        { icon: ExternalLink, label: 'External' }
    ];

    return (
        <div>
            <div style={{ position: 'relative', height: '500px' }}>
                {directions.map((item) => (
                    <SpeedDial.Root
                        key={item.direction}
                        radius={80}
                        type="semi-circle"
                        direction={item.direction as useSpeedDialProps['direction']}
                        style={item.style as React.CSSProperties}
                    >
                        <SpeedDial.Trigger severity="success" className="transition-transform duration-200 data-open:rotate-45">
                            <Plus />
                        </SpeedDial.Trigger>
                        <SpeedDial.List>
                            {items.map((action) => {
                                const Icon = action.icon;

                                return (
                                    <SpeedDial.Item key={action.label}>
                                        <SpeedDial.Action>
                                            <Icon />
                                        </SpeedDial.Action>
                                    </SpeedDial.Item>
                                );
                            })}
                        </SpeedDial.List>
                    </SpeedDial.Root>
                ))}
            </div>
        </div>
    );
}

Quarter Circle#

Setting type as quarter-circle displays the items at one of four corners of a button based on the direction.

quartercircle-demo
import { useSpeedDialProps } from '@primereact/types/shared/speeddial';
import { SpeedDial } from '@primereact/ui/speeddial';
import { Plus } from '@primeicons/react/plus';
import { Pencil } from '@primeicons/react/pencil';
import { Refresh } from '@primeicons/react/refresh';
import { Trash } from '@primeicons/react/trash';
import { Upload } from '@primeicons/react/upload';
import { ExternalLink } from '@primeicons/react/external-link';

const directions = [
    { direction: 'up-left', style: { position: 'absolute', right: 0, bottom: 0 } },
    { direction: 'up-right', style: { position: 'absolute', left: 0, bottom: 0 } },
    { direction: 'down-left', style: { position: 'absolute', right: 0, top: 0 } },
    { direction: 'down-right', style: { position: 'absolute', left: 0, top: 0 } }
];

export default function QuarterCircleDemo() {
    const items = [
        { icon: Pencil, label: 'Edit' },
        { icon: Refresh, label: 'Refresh' },
        { icon: Trash, label: 'Delete' },
        { icon: Upload, label: 'Upload' },
        { icon: ExternalLink, label: 'External' }
    ];

    return (
        <div>
            <div style={{ position: 'relative', height: '500px' }}>
                {directions.map((item) => (
                    <SpeedDial.Root
                        key={item.direction}
                        radius={120}
                        type="quarter-circle"
                        direction={item.direction as useSpeedDialProps['direction']}
                        style={item.style as React.CSSProperties}
                    >
                        <SpeedDial.Trigger className="transition-transform duration-200 data-open:rotate-45">
                            <Plus />
                        </SpeedDial.Trigger>
                        <SpeedDial.List>
                            {items.map((action) => {
                                const Icon = action.icon;

                                return (
                                    <SpeedDial.Item key={action.label}>
                                        <SpeedDial.Action>
                                            <Icon />
                                        </SpeedDial.Action>
                                    </SpeedDial.Item>
                                );
                            })}
                        </SpeedDial.List>
                    </SpeedDial.Root>
                ))}
            </div>
        </div>
    );
}

Transition Delay#

The transitionDelay property specifies the delay in milliseconds between each action item's appearance animation.

transitiondelay-demo
import { SpeedDial } from '@primereact/ui/speeddial';
import { Plus } from '@primeicons/react/plus';
import { Pencil } from '@primeicons/react/pencil';
import { Refresh } from '@primeicons/react/refresh';
import { Trash } from '@primeicons/react/trash';
import { Upload } from '@primeicons/react/upload';
import { ExternalLink } from '@primeicons/react/external-link';

export default function TransitionDelayDemo() {
    const items = [
        { icon: Pencil, label: 'Edit' },
        { icon: Refresh, label: 'Refresh' },
        { icon: Trash, label: 'Delete' },
        { icon: Upload, label: 'Upload' },
        { icon: ExternalLink, label: 'External' }
    ];

    return (
        <div>
            <div className="flex justify-between" style={{ position: 'relative', height: '240px' }}>
                <SpeedDial.Root direction="up" transitionDelay={0} style={{ position: 'absolute', left: 0, bottom: 0 }}>
                    <SpeedDial.Trigger severity="secondary" className="transition-transform duration-200 data-open:rotate-45">
                        <Plus />
                    </SpeedDial.Trigger>
                    <SpeedDial.List>
                        {items.map((action) => {
                            const Icon = action.icon;

                            return (
                                <SpeedDial.Item key={action.label}>
                                    <SpeedDial.Action>
                                        <Icon />
                                    </SpeedDial.Action>
                                </SpeedDial.Item>
                            );
                        })}
                    </SpeedDial.List>
                </SpeedDial.Root>

                <SpeedDial.Root direction="up" transitionDelay={80} style={{ position: 'absolute', left: 'calc(50% - 2rem)', bottom: 0 }}>
                    <SpeedDial.Trigger severity="success" className="transition-transform duration-200 data-open:rotate-45">
                        <Plus />
                    </SpeedDial.Trigger>
                    <SpeedDial.List>
                        {items.map((action) => {
                            const Icon = action.icon;

                            return (
                                <SpeedDial.Item key={action.label}>
                                    <SpeedDial.Action>
                                        <Icon />
                                    </SpeedDial.Action>
                                </SpeedDial.Item>
                            );
                        })}
                    </SpeedDial.List>
                </SpeedDial.Root>

                <SpeedDial.Root direction="up" transitionDelay={150} style={{ position: 'absolute', right: 0, bottom: 0 }}>
                    <SpeedDial.Trigger severity="info" className="transition-transform duration-200 data-open:rotate-45">
                        <Plus />
                    </SpeedDial.Trigger>
                    <SpeedDial.List>
                        {items.map((action) => {
                            const Icon = action.icon;

                            return (
                                <SpeedDial.Item key={action.label}>
                                    <SpeedDial.Action>
                                        <Icon />
                                    </SpeedDial.Action>
                                </SpeedDial.Item>
                            );
                        })}
                    </SpeedDial.List>
                </SpeedDial.Root>
            </div>
        </div>
    );
}

Template#

SpeedDial supports full customization of the button and action items using render props or custom components.

template-demo
'use client';
import { SpeedDialRootVisibleChangeEvent } from '@primereact/types/shared/speeddial';
import { SpeedDial } from '@primereact/ui/speeddial';
import * as React from 'react';
import { Plus } from '@primeicons/react/plus';
import { Heart } from '@primeicons/react/heart';
import { ShareAlt } from '@primeicons/react/share-alt';
import { Print } from '@primeicons/react/print';
import { Save } from '@primeicons/react/save';
import { Copy } from '@primeicons/react/copy';

export default function TemplateDemo() {
    const [visible, setVisible] = React.useState(false);

    const items = [
        { icon: Heart, label: 'Like' },
        { icon: ShareAlt, label: 'Share' },
        { icon: Print, label: 'Print' },
        { icon: Save, label: 'Save' },
        { icon: Copy, label: 'Copy' }
    ];

    return (
        <div className="flex justify-center" style={{ position: 'relative', height: '450px' }}>
            <SpeedDial.Root
                direction="down"
                transitionDelay={50}
                visible={visible}
                onVisibleChange={(e: SpeedDialRootVisibleChangeEvent) => setVisible(e.value as boolean)}
                style={{ position: 'absolute', top: 0 }}
            >
                <SpeedDial.Trigger severity="warn" className="w-12 h-12 transition-transform duration-200 data-open:rotate-45">
                    <Plus className="w-4! h-4!" />
                </SpeedDial.Trigger>
                <SpeedDial.List className="gap-4 mt-4">
                    {items.map((item, index) => (
                        <SpeedDial.Item key={index}>
                            <div
                                className="flex gap-3 cursor-pointer rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
                                onClick={() => setVisible(false)}
                            >
                                <span className="flex items-center justify-center w-24 px-6 py-2 bg-surface-0 dark:bg-surface-900 border border-surface-200 dark:border-surface-700 rounded-lg text-surface-500 dark:text-surface-400 font-medium">
                                    {item.label}
                                </span>
                                <span className="flex items-center justify-center w-12 h-12 rounded-full bg-surface-0 dark:bg-surface-900 border border-surface-200 dark:border-surface-700 text-surface-500 dark:text-surface-400">
                                    {React.createElement(item.icon, { className: 'text-xl' })}
                                </span>
                            </div>
                        </SpeedDial.Item>
                    ))}
                </SpeedDial.List>
            </SpeedDial.Root>
        </div>
    );
}

Tooltip#

SpeedDial can be combined with Tooltip component to display labels for action items.

tooltip-demo
import { SpeedDial } from '@primereact/ui/speeddial';
import { Tooltip } from '@primereact/ui/tooltip';
import { Plus } from '@primeicons/react/plus';
import { Pencil } from '@primeicons/react/pencil';
import { Refresh } from '@primeicons/react/refresh';
import { Trash } from '@primeicons/react/trash';
import { Upload } from '@primeicons/react/upload';
import { ExternalLink } from '@primeicons/react/external-link';

export default function TooltipDemo() {
    const items = [
        { icon: Pencil, label: 'Add' },
        { icon: Refresh, label: 'Update' },
        { icon: Trash, label: 'Delete' },
        { icon: Upload, label: 'Upload' },
        { icon: ExternalLink, label: 'External' }
    ];

    return (
        <div>
            <div style={{ position: 'relative', height: '350px' }}>
                <SpeedDial.Root direction="up" style={{ position: 'absolute', right: 0, bottom: 0 }}>
                    <SpeedDial.Trigger severity="help" className="transition-transform duration-200 data-open:rotate-45">
                        <Plus />
                    </SpeedDial.Trigger>
                    <SpeedDial.List>
                        <Tooltip.Group>
                            {items.map((action) => {
                                const Icon = action.icon;

                                return (
                                    <Tooltip.Root key={action.label} side="left">
                                        <Tooltip.Trigger as={SpeedDial.Item}>
                                            <SpeedDial.Action>
                                                <Icon />
                                            </SpeedDial.Action>
                                        </Tooltip.Trigger>
                                        <Tooltip.Portal>
                                            <Tooltip.Content>
                                                <p>{action.label}</p>
                                                <Tooltip.Arrow />
                                            </Tooltip.Content>
                                        </Tooltip.Portal>
                                    </Tooltip.Root>
                                );
                            })}
                        </Tooltip.Group>
                    </SpeedDial.List>
                </SpeedDial.Root>
                <SpeedDial.Root direction="up" style={{ position: 'absolute', left: 0, bottom: 0 }}>
                    <SpeedDial.Trigger severity="danger" className="transition-transform duration-200 data-open:rotate-45">
                        <Plus />
                    </SpeedDial.Trigger>
                    <SpeedDial.List>
                        <Tooltip.Group>
                            {items.map((action) => {
                                const Icon = action.icon;

                                return (
                                    <Tooltip.Root key={action.label} side="right">
                                        <Tooltip.Trigger as={SpeedDial.Item}>
                                            <SpeedDial.Action>
                                                <Icon />
                                            </SpeedDial.Action>
                                        </Tooltip.Trigger>
                                        <Tooltip.Portal>
                                            <Tooltip.Content>
                                                <p>{action.label}</p>
                                                <Tooltip.Arrow />
                                            </Tooltip.Content>
                                        </Tooltip.Portal>
                                    </Tooltip.Root>
                                );
                            })}
                        </Tooltip.Group>
                    </SpeedDial.List>
                </SpeedDial.Root>
            </div>
        </div>
    );
}

Mask#

SpeedDial can be combined with Motion component to display a mask overlay when opened.

mask-demo
'use client';
import { Motion } from '@primereact/core/motion';
import { SpeedDialRootVisibleChangeEvent } from '@primereact/types/shared/speeddial';
import { SpeedDial } from '@primereact/ui/speeddial';
import * as React from 'react';
import { Plus } from '@primeicons/react/plus';
import { Pencil } from '@primeicons/react/pencil';
import { Refresh } from '@primeicons/react/refresh';
import { Trash } from '@primeicons/react/trash';
import { Upload } from '@primeicons/react/upload';
import { ExternalLink } from '@primeicons/react/external-link';

export default function MaskDemo() {
    const [visible, setVisible] = React.useState(false);

    const items = [
        { icon: Pencil, label: 'Edit' },
        { icon: Refresh, label: 'Refresh' },
        { icon: Trash, label: 'Delete' },
        { icon: Upload, label: 'Upload' },
        { icon: ExternalLink, label: 'External' }
    ];

    return (
        <>
            <div style={{ position: 'relative', height: '360px' }}>
                <Motion
                    visible={visible}
                    name="p-overlay-mask"
                    className="absolute inset-0 bg-black/50 rounded-md"
                    onClick={() => setVisible(false)}
                />
                <SpeedDial.Root
                    direction="up"
                    visible={visible}
                    onVisibleChange={(e: SpeedDialRootVisibleChangeEvent) => setVisible(e.value as boolean)}
                    style={{ position: 'absolute', right: '1rem', bottom: '1rem' }}
                >
                    <SpeedDial.Trigger className="transition-transform duration-200 data-open:rotate-45">
                        <Plus />
                    </SpeedDial.Trigger>
                    <SpeedDial.List>
                        {items.map((action) => {
                            const Icon = action.icon;

                            return (
                                <SpeedDial.Item key={action.label}>
                                    <SpeedDial.Action>
                                        <Icon />
                                    </SpeedDial.Action>
                                </SpeedDial.Item>
                            );
                        })}
                    </SpeedDial.List>
                </SpeedDial.Root>
            </div>
        </>
    );
}

Accessibility#

Screen Reader#

SpeedDial component renders a native button element that implicitly includes any passed prop. Text to describe the button can be defined with the aria-labelledby or aria-label props. Addititonally the button includes aria-haspopup, aria-expanded for states along with aria-controls to define the relation between the popup and the button. can be added to implement custom key handlers.

The popup overlay uses menu role on the list and each action item has a menuitem role with an aria-label as the menuitem label. The id of the menu refers to the aria-controls of the button.

Keyboard Support#

KeyFunction
enterToggles the visibility of the menu.
spaceToggles the visibility of the menu.
down arrowOpens the menu and moves focus to the first item.
up arrowOpens the menu and moves focus to the last item.
right arrowOpens the menu and moves focus to the last item.
left arrowOpens the menu and moves focus to the first item.
escapeCloses the menu.
KeyFunction
enterActives the menuitem, closes the menu and sets focus on the menu button.
spaceActives the menuitem, closes the menu and sets focus on the menu button.
escapeCloses the menu and sets focus on the menu button.
arrow keysNavigates between the menu items.
homeMoves focus to the first item.
endMoves focus to the last item.