React-Planet: The Practical Guide to Circular Orbital Navigation in React

React-Planet: The Practical Guide to Circular Orbital Navigation in React






React-Planet: Build Circular Navigation Menus Fast







React-Planet: The Practical Guide to Circular Orbital Navigation in React

Published: June 2025  ·  12 min read  · 
Topics: React circular menu, react-planet tutorial, orbital UI, React navigation component

Most navigation patterns in React apps follow the same tired script — a horizontal navbar up top, a hamburger icon on mobile, maybe a sidebar if someone’s feeling adventurous. It works. Nobody gets fired for a navbar. But sometimes a product calls for something that’s actually interesting to use, something that feels spatial and intentional. That’s exactly the gap that react-planet fills.

React-planet is a lightweight React component library that lets you build circular, orbital navigation menus — think satellite items orbiting a central hub, opening and closing with spring-physics animations. It’s genuinely fun to interact with, surprisingly easy to integrate, and flexible enough that you won’t spend three days fighting its API just to change a color. This guide covers everything: installation, configuration, customization, animations, and real-world usage patterns, with enough code to get you shipping today.

Quick context: react-planet is built on top of react-spring for physics-based animations, which means the motion feels organic rather than robotic. That detail matters more than it might seem once you see it in action.

What Is React-Planet and Why Should You Care

At its core, react-planet is a React navigation component that renders a central element surrounded by satellite child elements arranged in a circular or arc pattern. The satellite items expand outward from the center when the menu opens — hence the orbital metaphor — and collapse back when it closes. The library handles all the positional mathematics and animation timing; you just supply the content and configuration props.

The use cases are broader than they might appear at first glance. Speed-dial floating action buttons, contextual radial menus triggered on right-click or long press, creative onboarding UI, game HUDs, data visualization dashboards with interactive nodes — anywhere that standard linear menus feel spatially wrong, React circular navigation menus built with react-planet tend to feel right. The library doesn’t force a specific visual style, either. It manages geometry and motion; the aesthetics are entirely yours.

Compared to rolling your own circular layout with raw CSS transforms and requestAnimationFrame, react-planet saves a significant amount of pain. Calculating equidistant positions on a circle, managing animation states per-child, handling open/close transitions without layout thrash — this is all the kind of work that seems straightforward until you’re three hours deep and your items are clipping each other at 45-degree angles. React-planet abstracts all of that cleanly.

React-Planet Installation and Initial Setup

Getting react-planet into a project is refreshingly straightforward. The package lives on npm, has no peer dependencies beyond React itself and react-spring, and works in any modern React environment — Create React App, Vite, Next.js, Remix, whatever your stack happens to be. Start with the install command:

# npm
npm install react-planet

# yarn
yarn add react-planet

# pnpm
pnpm add react-planet

Once installed, you import the Planet component and its optional Satellite wrapper from the package. The Planet component is the center node. Everything you pass as children becomes a satellite item. Here is the minimal working example — the absolute floor of a functional React planet menu:

import React, { useState } from 'react';
import { Planet } from 'react-planet';

export default function RadialMenu() {
  const [open, setOpen] = useState(false);

  return (
    <Planet
      centerContent={
        <button
          onClick={() => setOpen(!open)}
          style={{
            width: 56,
            height: 56,
            borderRadius: '50%',
            background: '#4f46e5',
            color: '#fff',
            border: 'none',
            cursor: 'pointer',
            fontSize: 24,
          }}
        >
          ☰
        </button>
      }
      open={open}
      orbitRadius={90}
    >
      <button style={{ borderRadius: '50%', width: 44, height: 44 }}>🏠</button>
      <button style={{ borderRadius: '50%', width: 44, height: 44 }}>📁</button>
      <button style={{ borderRadius: '50%', width: 44, height: 44 }}>⚙️</button>
      <button style={{ borderRadius: '50%', width: 44, height: 44 }}>📧</button>
    </Planet>
  );
}

That is a fully functional orbital navigation component. Four satellites, one center trigger, spring-animated open/close state managed by a single boolean. The open prop is controlled externally, which is intentional — react-planet follows the controlled component pattern, meaning your parent component owns the state and decides when the menu opens or closes. This makes integration with router-based navigation, keyboard accessibility, and custom open/close logic straightforward to implement without hacking internals.

One practical note for react-planet setup: the Planet component renders with position: relative by default, so it plays nicely inside flex or grid containers without unexpected overflow issues. If you’re building a floating action button variant, you’ll want to wrap it in a fixed-positioned container and position it at the bottom-right — the library itself is agnostic about document flow positioning.

Understanding the Core Props: Configuration That Actually Matters

React-planet’s API surface is intentionally compact. You won’t spend half a day reading a prop table only to discover that the feature you need isn’t in there. The most important props are centerContent, open, orbitRadius, rotation, mass, tension, and friction. Understanding what each one does — and why it’s designed that way — will save you from empirical trial-and-error later.

orbitRadius (number, default ~80px) controls how far satellite items travel from the center when the menu opens. This is the single biggest factor in how the menu feels spatially. A tight radius works for small, icon-only satellites in compact UI. A generous radius — 120px or more — works better when satellites have labels or when there are more than five of them, preventing overlap. rotation (number, degrees) sets the starting angle of the first satellite. A value of 180 fans items downward from the center; 0 starts at the right (3 o’clock position); -90 starts straight up. Combined with numAngleSections, which defaults to the number of children, you can constrain items to an arc rather than a full circle.

The physics props — mass, tension, and friction — come directly from react-spring’s spring configuration and control the character of the open/close animation. Higher tension produces snappier, faster movement. Higher friction reduces wobble and overshoot. Lower mass makes items feel lighter and quicker. The default values produce a pleasant bouncy feel, which is usually exactly what you want for an interactive orbital menu. If you’re embedding the component in a professional dashboard where bounciness reads as unprofessional, nudging tension up to 200 and friction to 30 will produce a crisp, no-nonsense animation.

Additional props worth knowing: dragablePlanet (boolean) makes the entire orbital system draggable around the viewport, which is genuinely cool for certain use cases. bounce enables a bounce effect on open. hideOrbit (boolean) controls whether the faint orbit circle guide renders — hide it in production, show it during development so you can verify radius alignment visually. And autoClose will close the menu when a satellite is clicked, which is almost always what you want for navigation menus.

React-Planet Customization: Making It Actually Look Like Yours

The base component renders no visual chrome of its own beyond positioning. There’s no default color scheme, no forced icon set, no baked-in typography. This means react-planet customization is essentially unlimited: you’re styling regular React elements, not fighting a component’s internal stylesheet. The center button, each satellite, and any wrapper you add are all plain DOM elements from CSS’s perspective.

For the center trigger element, a circular button with a subtle box shadow and a hover scale transform is the most common pattern. For satellites, consistency in size and shape matters a lot — if some are 40px circles and others are 60px squares, the orbital layout will look unbalanced. A useful practical pattern is defining a shared satelliteStyle object and spreading it across each child, then applying item-specific accent colors on top. Here’s a more polished version demonstrating this approach:

import React, { useState } from 'react';
import { Planet } from 'react-planet';

const satelliteStyle = {
  width: 48,
  height: 48,
  borderRadius: '50%',
  border: 'none',
  cursor: 'pointer',
  fontSize: 20,
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
  boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
  transition: 'transform 0.15s ease',
};

const satellites = [
  { icon: '🏠', bg: '#4f46e5', label: 'Home',     onClick: () => console.log('Home')     },
  { icon: '📁', bg: '#0891b2', label: 'Files',    onClick: () => console.log('Files')    },
  { icon: '⚙️', bg: '#059669', label: 'Settings', onClick: () => console.log('Settings') },
  { icon: '📧', bg: '#dc2626', label: 'Messages', onClick: () => console.log('Messages') },
  { icon: '👤', bg: '#7c3aed', label: 'Profile',  onClick: () => console.log('Profile')  },
];

export default function StyledOrbitalMenu() {
  const [open, setOpen] = useState(false);

  return (
    <Planet
      centerContent={
        <button
          onClick={() => setOpen(prev => !prev)}
          style={{
            ...satelliteStyle,
            width: 60,
            height: 60,
            background: open ? '#1e1b4b' : '#4f46e5',
            color: '#fff',
            fontSize: 26,
          }}
          aria-label="Toggle navigation menu"
          aria-expanded={open}
        >
          {open ? '✕' : '✦'}
        </button>
      }
      open={open}
      orbitRadius={110}
      rotation={210}
      mass={1}
      tension={200}
      friction={18}
      onClose={() => setOpen(false)}
    >
      {satellites.map(({ icon, bg, label, onClick }) => (
        <button
          key={label}
          onClick={() => { onClick(); setOpen(false); }}
          aria-label={label}
          style={{ ...satelliteStyle, background: bg, color: '#fff' }}
          title={label}
        >
          {icon}
        </button>
      ))}
    </Planet>
  );
}

Notice the aria-label and aria-expanded attributes on the center button. Circular menus get a bad reputation for accessibility, and frankly most implementations deserve it — the geometry looks great and the a11y is an afterthought. By labeling the trigger and each satellite, and by managing focus programmatically when the menu opens (a useEffect that focuses the first satellite when open becomes true is enough), you can make a React circular UI that’s actually navigable by keyboard and screen reader.

For theming at scale — say, a design system where the orbital menu appears in multiple contexts — the cleanest approach is to wrap Planet in your own component that accepts a theme prop and maps it to the relevant spring and sizing values. This keeps the react-planet API out of your feature components and makes restyling a one-file change.

React-Planet Animations: Physics, Timing, and Motion Design

The animation story in react-planet is where the library separates itself from DIY implementations or simpler alternatives. Because it uses react-spring under the hood, the motion is physics-based rather than duration-based. This distinction matters: a duration-based animation finishes in a fixed time regardless of interruption, which leads to jarring snaps when a user clicks the button rapidly. A physics-based spring continues naturally from its current velocity when interrupted, producing motion that feels connected to user intent.

The three animation props you’ll tune most often are mass, tension, and friction. Think of them as describing a physical spring: mass is the weight attached to the end of the spring (heavier = slower, more inertia), tension is the stiffness of the spring coil (higher = faster, snappier), and friction is the damping force that prevents infinite oscillation (lower = more bounce, higher = more deadening). A useful set of presets:

  • Playful / gamified UI: mass=1, tension=120, friction=10 — strong bounce, energetic feel
  • Standard / neutral: mass=1, tension=170, friction=20 — library defaults, works for most cases
  • Professional / enterprise: mass=1, tension=250, friction=32 — fast, minimal overshoot, businesslike
  • Slow / dramatic: mass=2, tension=80, friction=12 — deliberate, theatrical, good for onboarding

React-planet also exposes a bounceOnOpen prop that adds a secondary bounce animation specifically on the open transition, independent of the spring physics settings. It’s a subtle touch that makes the menu feel more alive without requiring manual spring tuning. For react-planet animations that stagger — where satellites appear one after another rather than all at once — you currently need to add a custom wrapper that applies incremental animationDelay via inline styles on each child, since staggered entry isn’t a built-in prop. It’s a reasonable gap; a wrapper utility component that accepts a staggerDelay prop and applies it is about fifteen lines of code.

One thing worth noting: because spring animations don’t have fixed durations, avoid coupling them to timing-dependent logic. Don’t write code like “after the menu opens, do X” based on a hardcoded timeout. Instead, react-spring exposes an onRest callback that fires when the animation settles. React-planet passes this through, so you can attach logic that should execute after the open or close animation completes without any setTimeout guesswork.

Real-World Usage: Floating Menus, Router Integration, and Production Patterns

The most common production use case for React circular navigation menus is as a floating action button cluster — fixed in the corner of the viewport, providing quick access to frequently used actions without permanently consuming screen real estate. Implementing this with react-planet requires only a fixed-position wrapper around the Planet component:

// FloatingOrbitalMenu.jsx
import React, { useState } from 'react';
import { Planet } from 'react-planet';
import { useNavigate } from 'react-router-dom';

export default function FloatingOrbitalMenu() {
  const [open, setOpen] = useState(false);
  const navigate = useNavigate();

  const navItems = [
    { icon: '🏠', path: '/',        label: 'Home'     },
    { icon: '📊', path: '/reports', label: 'Reports'  },
    { icon: '⚙️', path: '/settings',label: 'Settings' },
  ];

  return (
    <div
      style={{
        position: 'fixed',
        bottom: 32,
        right: 32,
        zIndex: 9999,
      }}
    >
      <Planet
        centerContent={
          <button
            onClick={() => setOpen(p => !p)}
            style={{
              width: 56, height: 56, borderRadius: '50%',
              background: '#4f46e5', color: '#fff',
              border: 'none', cursor: 'pointer', fontSize: 22,
              boxShadow: '0 6px 20px rgba(79,70,229,0.4)',
            }}
            aria-label="Open navigation"
            aria-expanded={open}
          >
            {open ? '✕' : '✦'}
          </button>
        }
        open={open}
        orbitRadius={90}
        rotation={180}
        onClose={() => setOpen(false)}
        autoClose
        mass={1}
        tension={200}
        friction={22}
      >
        {navItems.map(({ icon, path, label }) => (
          <button
            key={path}
            onClick={() => { navigate(path); setOpen(false); }}
            aria-label={label}
            title={label}
            style={{
              width: 46, height: 46, borderRadius: '50%',
              background: '#1e1b4b', color: '#fff',
              border: 'none', cursor: 'pointer', fontSize: 20,
              boxShadow: '0 3px 10px rgba(0,0,0,0.2)',
            }}
          >
            {icon}
          </button>
        ))}
      </Planet>
    </div>
  );
}

Notice rotation={180} — this fans the satellite items upward and to the left from the bottom-right anchor, which is exactly the spatial behavior you’d expect from a corner-anchored React floating menu. A rotation of 90 would fan items leftward in a semicircle; 270 fans them upward. Combining rotation with numAngleSections set to a value larger than the actual child count creates an arc menu rather than a full orbit, which is often more natural for corner or edge anchoring.

For React Router integration specifically, the pattern above is the most direct approach — useNavigate in the satellite click handlers, autoClose on the Planet to dismiss after navigation. If you’re working with Next.js and its Link component, pass the Link directly as each satellite child, wrapping it in a styled div or button as needed. React-planet’s children are rendered as-is, so any valid React element works as a satellite without any special treatment.

One gotcha worth calling out explicitly: react-planet applies absolute positioning to satellite elements during the open animation, which means the parent container needs either position: relative or position: fixed to establish a stacking context. Without it, satellites will position relative to the nearest positioned ancestor, which may not be what you expect. This trips up most developers exactly once. The fix is always the same: check the computed position of the Planet wrapper’s parent.

Advanced Patterns: Nested Orbits, Contextual Menus, and Accessibility

One of the more interesting advanced applications is nested orbital menus — a secondary Planet component inside one of the satellite positions of a primary Planet. This creates a hierarchical radial menu where clicking a first-level satellite opens a second orbit of related sub-actions. The implementation is straightforward since react-planet’s children are just React elements: pass a full Planet component as one of the children of the outer Planet, managing its open state independently. Orbit radii should be smaller for the inner level — around 60px — to keep the spatial hierarchy readable.

Contextual radial menus — menus that appear at the cursor position on right-click or long press — are another high-value pattern for certain application types (canvas tools, maps, game interfaces). The approach here is to render the Planet component at position: fixed with top and left driven by mouse coordinates captured in a contextmenu event handler, combined with a useClickOutside hook that closes the menu on the next click. React-planet has no built-in contextual triggering, but because it’s a controlled component, wiring this up externally takes about 20 lines of hook code. The result is a genuinely delightful interaction — a radial picker materializing at the cursor with spring physics is the kind of UI moment that users actually mention in feedback.

On the accessibility front: circular menus are not inherently inaccessible, but they require deliberate implementation. Beyond the aria-label and aria-expanded attributes mentioned earlier, you should implement keyboard navigation — specifically, arrow keys to cycle through satellite items when the menu is open, Escape to close it, and Enter/Space to activate the focused satellite. None of this is provided by react-planet (it manages geometry, not focus), so it lives in a custom hook. A useOrbitalMenuKeyboard hook that listens to keydown events and manages a focusedIndex state is the standard pattern. Combined with a useEffect that calls .focus() on the corresponding satellite ref when focusedIndex changes, you get fully keyboard-navigable orbital navigation.

When to Use React-Planet (and When Not To)

React-planet earns its place in a React project when the navigation or action set is small (3–8 items), spatially oriented UI is appropriate for the context, and animation quality is a priority. Product dashboards with a core action cluster, mobile-first apps where thumb-reach matters (a bottom-right orbital menu is ergonomically natural), creative tools, and any interface where differentiation from standard navigation patterns is a feature rather than a quirk — these are all strong fits.

The cases where react-planet is the wrong tool are equally clear. Large navigation sets — anything above eight items — become visually cluttered in an orbit layout. If your navigation structure is hierarchical with more than two levels, a standard sidebar or mega-menu will serve users better. If accessibility is a hard constraint and your team doesn’t have bandwidth for the keyboard navigation implementation described above, a conventional component is the pragmatic choice. And if your users are unfamiliar with radial menu patterns (which is still most enterprise software users), the discoverability problem is real — a menu that’s invisible until tapped is only as good as the user’s willingness to tap the center button.

Compared to alternatives like react-radial-menu or building from scratch with CSS transforms and framer-motion, react-planet offers the best combination of maintenance cost, animation quality, and API simplicity. It’s a focused library that does one thing well rather than a sprawling utility with twenty configuration modes. For a React team that wants an orbital/circular UI component they can ship in an afternoon and maintain indefinitely, react-planet is the honest recommendation.

Frequently Asked Questions

How do I install and configure react-planet in a React project?

Run npm install react-planet (or the yarn/pnpm equivalent). Import the Planet component from react-planet, pass your trigger element as the centerContent prop, and place satellite items as direct children. Control the open/closed state with a boolean state variable passed to the open prop. Set orbitRadius to define how far satellites spread from the center. That’s the complete minimum configuration — everything else is optional refinement.

How do I customize the orbit radius and animation in react-planet?

Orbit radius is controlled by the orbitRadius prop (a number in pixels). Animation feel is driven by mass, tension, and friction — the react-spring physics config. Higher tension = snappier. Higher friction = less bounce. You can also use rotation (degrees) to set the starting angle and numAngleSections to spread items over an arc rather than a full circle. The bounceOnOpen boolean adds a secondary bounce effect on open without changing the base spring config.

How do I handle click events on react-planet satellite items?

Satellite items are plain React elements — attach onClick handlers directly to them as you would any button or link. The Planet component exposes an onClose prop that fires when the menu should close (e.g., click outside), and the autoClose boolean will automatically trigger onClose after any satellite is clicked. For navigation, call your router’s navigate function inside the satellite’s onClick and set your open state to false in the same handler.

Semantic Keyword Core

🔵 Primary — Core Terms
react-planet
React circular menu
React planet menu
React orbital navigation
React circular navigation menu
React circular UI
🟢 Secondary — Functional / Transactional
react-planet installation
react-planet setup
react-planet getting started
react-planet tutorial
react-planet example
react-planet customization
react-planet animations
React navigation component
React floating menu
🟡 LSI / Semantic Adjacents
radial menu React
satellite menu React
circular button layout
orbit animation React
react-spring orbital
speed dial menu React
circular action menu
React FAB menu
interactive radial UI
React spring physics animation
contextual radial menu React
🟣 Long-Tail / Voice Search
how to install react-planet
how to make a circular menu in React
react-planet orbitRadius customization
react orbital navigation component tutorial
circular floating action button React
react-planet with React Router
how to handle click events in react-planet


Share:

Leave your comments