Map Marker Cluster
- Stable
- WCAG 2.2 evidence
- RTL
A number on a map that stands for several nearby places: a white disc with a
segmented ring and the count. The map is not part of the library: position the marker yourself, centred on the cluster
(translate(-50%, -50%)). It is plain (an image named by aria-label, such as "12 places") unless you give it onPress (a button, for example to zoom in
or list the places) or href (a link). Interactive markers are React Aria buttons and links: Enter and Space press, keyboard
focus shows the focus ring, and the 36 px minimum size meets the target size (WCAG 2.1.1, 2.4.7, 2.5.8, 4.1.2). The marker's
type is the count in text, never the ring colours (1.4.1). Consumer duties: write the name with the number first ("12 places"); announce changes when the map re-clusters while the user
zooms (4.1.3); keep the 24 px target spacing between neighbouring markers (2.5.8).
import { MapMarkerCluster } from "@nexera-ui/react";- 8
- examples
- 14
- props
- 2
- live controls
- 1
- platform
- 6
- WCAG criteria
- 0
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { MapMarkerCluster } from "@nexera-ui/react";
<MapMarkerCluster />Examples 7
The same examples as Storybook, rendered live. Open Code to copy one.
As button
A button that zooms into the cluster: Tab to it, Enter or Space presses; the name starts with the visible count.
import { useState } from "react";
import { MapMarkerCluster } from "@nexera-ui/react";
export function AsButton() {
function Zoom() {
const [zoomed, setZoomed] = useState(false);
return (
<div className="flex flex-col items-start gap-3">
<MapMarkerCluster
count={12}
size="md"
aria-label="12 places, zoom in"
onPress={() => {
setZoomed(true);
}}
/>
<p className="m-0 text-body-small text-secondary" aria-live="polite">
{zoomed ? "Zoomed in: showing 12 places" : "Press the marker to zoom in"}
</p>
</div>
);
}
return <Zoom />;
}
As link
A link to a list of the places.
import { MapMarkerCluster } from "@nexera-ui/react";
<MapMarkerCluster count={12} aria-label="12 places, show list" size="md" href="#places" />;
Weighted segments
Segment weights (decorative): here a cluster that is mostly the first kind of place. The count and name stay the source of truth.
import { MapMarkerCluster } from "@nexera-ui/react";
export function WeightedSegments() {
return (
<div className="flex flex-wrap items-center gap-6">
<MapMarkerCluster count={20} size="lg" aria-label="20 places" segments={[10, 5, 3, 2]} />
<MapMarkerCluster count={20} size="lg" aria-label="20 places, all one kind" segments={[1]} />
<MapMarkerCluster count={20} size="lg" aria-label="20 places, two kinds" segments={[1, 1]} />
</div>
);
}
Long count
Large counts are never clipped: abbreviate them yourself ("99+", "1.2k") and keep the exact number in the name.
import { MapMarkerCluster } from "@nexera-ui/react";
export function LongCount() {
return (
<div className="flex flex-wrap items-center gap-6">
<MapMarkerCluster count="99+" size="sm" aria-label="124 places" />
<MapMarkerCluster count="1.2k" size="md" aria-label="1,248 places" />
<MapMarkerCluster count="12.5k" size="lg" aria-label="12,480 places" />
</div>
);
}
On map surfaces
On map surfaces (the map-* tokens, light and dark): the surface-coloured base and shadow keep the marker readable.
import { MapMarkerCluster } from "@nexera-ui/react";
export function OnMapSurfaces() {
return (
<div className="flex flex-wrap gap-4">
{["bg-map-land", "bg-map-water", "bg-map-park", "bg-map-building"].map((surface) => (
<div
key={surface}
className={`flex size-28 items-center justify-center rounded-md ${surface}`}
>
<MapMarkerCluster count={8} size="md" aria-label="8 places" onPress={() => undefined} />
</div>
))}
</div>
);
}
Right to left
Right-to-left: a symmetric disc, so nothing mirrors; the count keeps its digits.
import { MapMarkerCluster } from "@nexera-ui/react";
export function RightToLeft() {
return (
<div dir="rtl" lang="ar" className="flex flex-wrap items-center gap-6">
<MapMarkerCluster count="١٢" size="md" aria-label="١٢ مكانًا" />
<MapMarkerCluster
count={12}
size="md"
aria-label="12 مكانًا، تكبير"
onPress={() => undefined}
/>
</div>
);
}
Disabled
Disabled: dimmed, out of the tab order.
import { MapMarkerCluster } from "@nexera-ui/react";
<MapMarkerCluster
count={8}
aria-label="8 places, unavailable"
size="md"
onPress={() => undefined}
isDisabled
/>;
Props 14
Press "Try it" on a card to load that prop into the playground.
14 props shown
count*Nexerastring | numberNumber of places the cluster stands for. Show a short count: the number is never clipped, so abbreviate large ones ("99+", "1.2k"). Keep the exact number in the accessible name.
sizeNexera"sm" | "md" | "lg"Diameter and type size: 36 px with Component/Badge text, 48 px with Body/Strong, 60 px with Heading/H4.
segmentsNexerareadonly number[]Relative weights of the four ring segments, in the Figma colours chart/1, chart/2, chart/5, chart/3 (for example the
share of each kind of place in the cluster). The ring is decorative: it never replaces text, so describe the mix in the
accessible name or next to the map if it matters (WCAG 1.4.1). Weights of zero or less are ignored; without usable weights the
four segments are equal, as drawn in Figma.
idNexerastringDOM id.
aria-describedbyNexerastringId(s) of the element(s) that describe the marker (for example a summary of what the cluster holds).
classNameNexerastringExtra classes, merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style (typically the position on the map).
aria-labelNexerastringAccessible name, for example "12 places" or "12 places, zoom in" (WCAG 4.1.2). It should start with the visible count (2.5.3). Translate it.
aria-labelledbyNexerastringId(s) of the element(s) that name the cluster; wins over aria-label.
Id(s) of the element(s) that name the cluster.
onPressNexera((event: PressEvent) => void) | ((event: PressEvent) => void)Called when the marker is pressed (pointer, Enter or Space). Makes the marker a button. Also called when the link is pressed.
hrefNexerastringMakes the marker a link to this URL.
targetNexerastringLink target.
relNexerastringLink relationship.
isDisabledNexerabooleanDisables the button. Disables the link.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
5 direct · 1 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · supporting test
- 1.4.1Use of ColorLevel A · tested directly
- 2.1.1KeyboardLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · tested directly
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-hovered
<MapMarkerCluster className="data-disabled:opacity-90 shadow-sm" />Used in blocks
No block uses MapMarkerCluster yet.
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.