Skip to content

Icon Cloud

Icons laid out on a slowly turning sphere, built with CSS 3D transforms and no canvas. Each icon can be a link, a button or a plain image, and the list keeps one fixed order for the keyboard.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

Optional, and kept apart.

Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.

import { IconCloud, BellIcon, CalendarIcon, ChartIcon, ClockIcon, FileIcon, HomeIcon, ImageIcon, InboxIcon, SearchIcon, SettingsIcon, StarIcon, TagIcon, UsersIcon } from "@rdloom/react";

const icons = [
  ["Notifications", BellIcon],
  ["Calendar", CalendarIcon],
  ["Reports", ChartIcon],
  ["Schedule", ClockIcon],
  ["Files", FileIcon],
  ["Home", HomeIcon],
  ["Images", ImageIcon],
  ["Inbox", InboxIcon],
  ["Search", SearchIcon],
  ["Settings", SettingsIcon],
  ["Favourites", StarIcon],
  ["Tags", TagIcon],
  ["People", UsersIcon],
] as const;

export default function IconCloudBasicExample() {
  return (
    <div className="flex w-full justify-center py-4">
      <IconCloud
        size={300}
        iconSize={36}
        label="Sample icons"
        items={icons.map(([label, Icon]) => ({ label, icon: <Icon className="size-6 text-[var(--rd-color-text-default)]" /> }))}
      />
    </div>
  );
}

Installation

npx rdloom add icon-cloud

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/icon-cloud.json

Works, but without upgrade tracking, and you import the motion CSS yourself.

Usage

import { IconCloud, BellIcon, CalendarIcon, ChartIcon, ClockIcon, FileIcon, HomeIcon, ImageIcon, InboxIcon, SearchIcon, SettingsIcon, StarIcon, TagIcon, UsersIcon } from "@rdloom/react";

<div className="flex w-full justify-center py-4">
  <IconCloud
    size={300}
    iconSize={36}
    label="Sample icons"
    items={icons.map(([label, Icon]) => ({ label, icon: <Icon className="size-6 text-[var(--rd-color-text-default)]" /> }))}
  />
</div>
import { IconCloud } from "@rdloom/react";

const pages = ["Docs", "Guides", "Blog", "Pricing", "Status", "Team", "Jobs", "Help"];

export default function IconCloudLinksExample() {
  return (
    <div className="flex w-full justify-center py-4">
      <IconCloud
        size={300}
        iconSize={48}
        duration={60}
        label="Sample pages"
        items={pages.map((page) => ({
          label: page,
          href: "#",
          icon: (
            <span className="flex size-12 items-center justify-center rounded-full border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)] text-[11px] text-[var(--rd-color-text-default)]">
              {page}
            </span>
          ),
        }))}
      />
    </div>
  );
}

Still

import { IconCloud, BellIcon, ChartIcon, HomeIcon, InboxIcon, SettingsIcon, StarIcon, UsersIcon } from "@rdloom/react";

const icons = [
  ["Notifications", BellIcon],
  ["Reports", ChartIcon],
  ["Home", HomeIcon],
  ["Inbox", InboxIcon],
  ["Settings", SettingsIcon],
  ["Favourites", StarIcon],
  ["People", UsersIcon],
] as const;

// isPaused keeps the sphere still, for pages that do not want any movement.
export default function IconCloudStillExample() {
  return (
    <div className="flex w-full justify-center py-4">
      <IconCloud
        isPaused
        size={260}
        iconSize={36}
        label="Sample icons, still"
        items={icons.map(([label, Icon]) => ({ label, icon: <Icon className="size-6 text-[var(--rd-color-text-default)]" /> }))}
      />
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
itemsrequired

The icons. label is the accessible name. An item with href is a link, one with onPress is a button, and any other is a plain icon.

Array<{ id?: string; label: string; icon: ReactNode; href?: string; onPress?: () => void }>none
size

Width and height of the sphere in pixels.

number320
iconSize

Width and height of each icon in pixels.

number40
duration

Seconds for one full turn.

number48
label

Accessible name of the list of icons.

string"Icons"
isPaused

Stops the turning. Moving content that lasts more than five seconds should be stoppable.

booleanfalse

Accessibility

Role list, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab: moves to each link or button in the order of the items array
  • Enter: follows a link or presses a button; Space presses a button

Screen readers announce

  • Announced as a list with the given label; each link, button or image is read in order with its label

What your code must do

  • The icons are real list items in a fixed DOM order that never follows the rotation, so keyboard order is stable
  • Each link and button has an accessible name from label; plain icons are images with that label
  • The turning pauses while the pointer is over the cloud or focus is inside it, and with isPaused or the rdm-still class (WCAG 2.2.2)
  • Visitors who ask for less motion (prefers-reduced-motion) get a still sphere
  • Icons are kept facing the viewer while the sphere turns, so they are never mirrored or edge-on
  • Focus shows a visible ring on the icon in every colour mode
  • Does not flash: nothing changes more than three times in a second (WCAG 2.3.1)

Guidelines

Use it when

  • Showing the range of tools or technologies a product supports
  • A decorative hero graphic whose icons may also link somewhere

Avoid it when

  • Navigation, because moving targets are hard to use
  • Icons that need a visible text caption

Don't

  • Icons that are only recognisable by colour
  • Dozens of items, which crowd the sphere and overlap

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-focus-ring
  • --rd-radius-control