react-radial-dock
v1.0.1
Published
Customizable, GSAP-animated radial action dock for React.
Maintainers
Readme
react-radial-dock
A customizable, GSAP-animated radial action dock (donut-shaped quick-action menu) for React. Drop-in for Next.js App Router, Vite, and CRA. Bring your own icons.
Install
npm i react-radial-dock gsapgsap is a peer dependency. Then import the CSS once at the root of your app:
import 'react-radial-dock/styles.css';Quick start
'use client';
import RadialDock from 'react-radial-dock';
import 'react-radial-dock/styles.css';
const items = [
{ id: 'a', label: 'Star', icon: '⭐', onSelect: () => console.log('star') },
{ id: 'b', label: 'Mark', icon: '🔖', onSelect: () => console.log('mark') },
{ id: 'c', label: 'Pin', icon: '📍', onSelect: () => console.log('pin') },
];
export default function Page() {
return <RadialDock items={items} />;
}Right-click anywhere on the page to open. Press Esc to close.
Triggers
<RadialDock items={items} triggers={{ rightClick: true, hotkey: 'mod+e' }} />rightClick:true(default) |false|{ ignoreSelectors: string[] }hotkey: e.g.'mod+e'(Cmd on Mac, Ctrl elsewhere),'shift+space',false
Controlled mode
const [open, setOpen] = useState(false);
<RadialDock items={items} open={open} onOpenChange={setOpen} position={{ x: 400, y: 300 }} />;Imperative handle
const ref = useRef<RadialDockHandle>(null);
ref.current?.open(100, 200);
ref.current?.close();
ref.current?.toggle();Theming
CSS variables (e.g. --rrd-slice-fill-hover, --rrd-shadow) drive the look. Override per instance with the theme prop or globally with CSS:
<RadialDock theme={{ sliceFillHover: '#FF4D4F', shadow: '0 12px 40px rgba(255,77,79,0.4)' }} />.my-app { --rrd-slice-fill-hover: var(--brand-blue); }Custom render per slice
{
id: 'star',
onSelect: () => {},
render: ({ hovered }) => <div style={{ transform: hovered ? 'scale(1.2)' : 'scale(1)' }}>⭐</div>,
}Animations
Built-in: spring (default), fade, pop, stagger, iris. Or pass a custom { open, close }:
import gsap from 'gsap';
const flip = {
open: (tl, ctx) => tl.fromTo(ctx.container, { rotateY: 90, opacity: 0 }, { rotateY: 0, opacity: 1, duration: .3 }),
close: (tl, ctx) => tl.to(ctx.container, { opacity: 0, duration: .18 }),
};
<RadialDock items={items} animation={flip} />Geometry
| Prop | Default | Notes |
| ------------- | ------- | ----------------------------------- |
| outerRadius | 110 | min 60 |
| innerRadius | 55 | must be ≤ outerRadius − 20 |
| iconRadius | 'auto' | resolves to (outer + inner) / 2 |
| iconSize | 32 | |
| sliceGap | 2 | px on the icon-radius arc |
| startAngle | 0 | degrees, 0 = top |
| direction | 'clockwise' | |
Accessibility
role="menu"witharia-label(default"Quick actions").- Hidden
<span role="menuitem">per slice, indexed viaaria-activedescendant. - Keyboard:
ArrowLeft/ArrowRight/Tab/Shift+Tabcycle slices,Enterselects,Esccloses. - Disabled items:
aria-disabled, skipped in cycle. - Respects
prefers-reduced-motion: reduce(override withreducedMotion="never").
Browser support
- Chrome, Edge, Safari ≥ 14, Firefox ≥ latest, Safari iOS 14+.
backdrop-filterfalls back to a solid disk on older browsers.
License
MIT © Rupam Shil
