A flat world map drawn as dots on a canvas, with optional markers and arcs that pulse between marker pairs. Still when off-screen, paused or for reduced motion.
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 { DottedMap } from "@rdloom/react";
export default function DottedMapBasicExample() {
return (
<div className="flex w-full justify-center">
<div className="w-full max-w-xl">
<DottedMap label="World map" description="A map of the world drawn in small dots." />
</div>
</div>
);
}Installation
npx rdloom add dotted-mapCopies 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/dotted-map.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { DottedMap } from "@rdloom/react";
<div className="flex w-full justify-center">
<div className="w-full max-w-xl">
<DottedMap label="World map" description="A map of the world drawn in small dots." />
</div>
</div>Connections
import { DottedMap, type WorldConnection, type WorldMarker } from "@rdloom/react";
const places: WorldMarker[] = [
{ lat: 37.8, lng: -122.4, label: "San Francisco" },
{ lat: 51.5, lng: -0.1, label: "London" },
{ lat: 19.1, lng: 72.9, label: "Mumbai" },
{ lat: 35.7, lng: 139.7, label: "Tokyo" },
];
const routes: WorldConnection[] = [
{ from: "San Francisco", to: "London" },
{ from: "London", to: "Mumbai" },
{ from: "Mumbai", to: "Tokyo" },
];
export default function DottedMapConnectionsExample() {
return (
<div className="flex w-full justify-center">
<div className="w-full max-w-xl">
<DottedMap
label="Data centre links"
description="Four cities linked in a chain by pulsing arcs."
markers={places}
connections={routes}
/>
</div>
</div>
);
}Still
import { useState } from "react";
import { Button, DottedMap } from "@rdloom/react";
// Defined once, outside the component, so a re-render does not restart the map.
const places = [
{ lat: -23.5, lng: -46.6, label: "Sao Paulo" },
{ lat: 48.9, lng: 2.4, label: "Paris" },
];
const routes = [{ from: "Sao Paulo", to: "Paris" }];
export default function DottedMapStillExample() {
const [paused, setPaused] = useState(false);
return (
<div className="flex w-full flex-col items-center gap-3">
<div className="w-full max-w-xl">
<DottedMap
label="Routes"
description="Two cities joined by an arc."
markers={places}
connections={routes}
dotSpacing={10}
isPaused={paused}
/>
</div>
<Button variant="secondary" onPress={() => setPaused((p) => !p)}>
{paused ? "Play the arcs" : "Hold the map still"}
</Button>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
labelAccessible name of the whole graphic. | string | "World map" |
descriptionA sentence for screen readers that says what the graphic shows. It is visually hidden. | string | none |
markersPlaces to mark as { lat, lng, label }. Each one is also listed, with its label, for screen readers. | WorldMarker[] | none |
dotColorColour of the land dots, any CSS colour. Defaults to the muted text colour. | string | none |
markerColorColour of the markers, any CSS colour. Defaults to the primary action colour. | string | none |
isPausedStops the movement and shows one still frame. | boolean | false |
connectionsArcs drawn between two markers as { from, to }, each the label of a marker. A pulse travels along each arc. Pairs whose labels match no marker are skipped. They are also listed for screen readers. | WorldConnection[] | none |
dotSpacingDistance between dots in pixels. Larger is coarser and cheaper. | number | 8 |
Accessibility
Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
Not interactive.
Screen readers announce
- The canvas is hidden from assistive technology
- The label, description, a list of the places and a list of the connections are read as text
What your code must do
- The canvas is decoration and aria-hidden; the root is a labelled group with a visually hidden description and a real list of the places
- Stands still for visitors who ask for less motion (prefers-reduced-motion), inside an rdm-still container and with isPaused; one still frame is drawn
- Moving for more than five seconds, so it can be paused with isPaused or the rdm-still container (WCAG 2.2.2)
- Does not flash: nothing changes more than three times in a second (WCAG 2.3.1)
- Paused while off-screen, and its animation frame is cancelled when it unmounts
- Hidden in forced-colors (high contrast) mode, where the place list is still there for assistive technology
- Connections are listed as text, so the routes are not shown by the animation alone
Guidelines
Use it when
- Showing where offices, regions or routes are in a hero or section
- A quiet decorative map with a few named places
Avoid it when
- Precise geography or street level detail
- Many dozens of arcs at once
Don't
- Leaving out label and description
- Arcs whose meaning is only in the animation
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-text-muted--rd-color-action-primary