React Simple Maps

<-Docs
Styling

Guides

Styling

Every React Simple Maps component renders a plain SVG element, adds one rsm- class to it, and spreads any prop it does not recognise straight onto that element. That gives you four ways to style a map, in rough order of how much you need:

The classes are fixed:

<svg class="rsm-svg">
  <g class="rsm-zoomable-group">
    <path class="rsm-graticule" />
    <path class="rsm-sphere" />
    <g class="rsm-geographies">
      <path class="rsm-geography" />
      <path class="rsm-geography" />
      <path class="rsm-geography" />
      ...
    </g>
    <g class="rsm-marker"></g>
    <g class="rsm-marker"></g>
    <g class="rsm-marker"></g>
    ...
    <g class="rsm-annotation"></g>
    <g class="rsm-annotation"></g>
    <g class="rsm-annotation"></g>
    ...
    <path class="rsm-line" />
    <path class="rsm-line" />
    <path class="rsm-line" />
    ...
  </g>
</svg>

Your className is added to the built-in one rather than replacing it, so <Geography className="state" /> renders class="rsm-geography state".

Attribute styling

SVG presentation attributes passed as props are passed on to the element:

<Geography
  geography={geo}
  fill="#EAEAEC"
  stroke="#FFF"
  strokeWidth={0.5}
  strokeLinejoin="round"
/>

Some components have default props you may want to override — Line defaults to stroke="currentcolor" and strokeWidth={3}, Graticule and Sphere have a transparent fill and currentcolor stroke.

<div style={{ color: "#B0B7C3" }}>
  <ComposableMap>
    <Graticule /> {/* strokes in #B0B7C3 */}
    <Sphere />
  </ComposableMap>
</div>

Attributes are the right tool for values that come from data and never change on interaction. They cannot express :hover or :focus — for that, use CSS.

The style prop

style also passes straight through, and takes precedence over presentation attributes:

<Geography geography={geo} style={{ fill: "#0066ff", cursor: "pointer" }} />

Note this is a plain React style object. Some older React Simple Maps examples show a style={{ default: …, hover: …, pressed: … }} shape — that was a v3 API and does not work in v5. The equivalent now is CSS classes or CSS variables.

CSS styling

For anything shared across many features, or any interaction state, style the rsm- classes in a stylesheet. This keeps per-feature React props to a minimum, which matters on maps with hundreds of paths.

.rsm-geography {
  fill: #eaeaec;
  outline: none;
  transition: fill 100ms ease;
}
 
.rsm-geography:hover {
  fill: #ff5533;
}
 
.rsm-geography:active {
  fill: #e03e20;
}
<Geographies geography={geoUrl}>
  {({ geographies }) =>
    geographies.map((geo) => <Geography key={geo.rsmKey} geography={geo} />)
  }
</Geographies>

Note the CSS uses hyphenated stroke-width, where the React prop is strokeWidth.

Focus states

Geography renders with tabIndex={0}, so every feature is keyboard focusable and will show the browser's default focus ring. Rather than removing it, replace it with something visible:

.rsm-geography {
  outline: none;
}
 
.rsm-geography:focus-visible {
  stroke: #000000;
  stroke-width: 2;
}

If a map is decorative and should not be in the tab order at all, pass tabIndex={-1}.

Scoping to one map

The rsm- classes are global. With more than one map on a page, scope your rules through a wrapper or the map's own className:

<ComposableMap className="world-map">…</ComposableMap>
.world-map .rsm-geography {
  fill: #dfe3e8;
}

CSS variable styling

Attributes give you per-geography color but no hover. CSS gives you hover but the same color everywhere. Choropleths may need both: each geography colored by its own data value, and a hover state that fits that color.

CSS custom properties bridge the two. Set the per-geography values in JS via style, and consume them in a stylesheet that owns the interaction states:

import { scaleQuantize } from "d3-scale"
 
const colorScale = scaleQuantize()
  .domain([0, 100])
  .range(["#d4e8f7", "#a8d0ee", "#7cb8e5", "#5090c9", "#2468ad"])
 
const hoverColor => () => ...
 
<Geographies geography={geoUrl}>
  {({ geographies }) =>
    geographies.map((geo) => {
      const value = data[geo.id]
      const fill = value == null ? "#F0F0F0" : colorScale(value)
 
      return (
        <Geography
          key={geo.rsmKey}
          geography={geo}
          style={{
            "--fill": fill,
            "--fill-hover": hoverColor(fill),
          }}
        />
      )
    })
  }
</Geographies>
.rsm-geography {
  fill: var(--fill, #f0f0f0);
  stroke: #fff;
  stroke-width: 0.5;
  outline: none;
  transition: fill 150ms ease;
}
 
.rsm-geography:hover {
  fill: var(--fill-hover, #ff5533);
}
 
.rsm-geography:focus-visible {
  stroke: var(--fill-hover, #0066ff);
  stroke-width: 2;
}

The variables carry the data; the stylesheet owns every state. Adding a pressed or selected state later is a CSS change, not a React one. Always give var() a fallback so geographies with no data still render.

Bonus: Deriving the hover color

The point of --fill-hover is that it fits the generated color rather than being one shared accent. The package already depends on d3-color, so you can derive it:

import { color } from "d3-color"
 
const hoverColor = (fill) => color(fill).brighter(0.4).formatHex()

brighter() alone breaks down at the extremes — it cannot brighten pure white, and on a dark palette a fixed step can blow out. Choosing the direction based on lightness is more robust:

import { hcl } from "d3-color"
 
function hoverColor(fill) {
  const c = hcl(fill)
  c.l += c.l > 60 ? -12 : 14 // darken light fills, lighten dark ones
  return c.formatHex()
}

Working in HCL keeps hue and chroma fixed and moves only perceived lightness, so the hover state reads as the same colour rather than a different one.

Setting variables higher up

Variables inherit, so shared values belong on an ancestor rather than repeated on every path:

<ComposableMap style={{ "--stroke": "#FFF", "--fill-empty": "#F0F0F0" }}>
.rsm-geography {
  fill: var(--fill, var(--fill-empty));
  stroke: var(--stroke);
}

Only the values exclusive to a geography need to be set on the Geography itself.

TypeScript

React.CSSProperties does not include custom properties, so TypeScript rejects "--fill" in a style object. Cast them explicitly:

style={{ "--fill": fill } as React.CSSProperties}

Or, to keep it checked, declare the properties you use:

interface MapVars extends React.CSSProperties {
  "--fill"?: string
  "--fill-hover"?: string
}
 
const style: MapVars = { "--fill": fill, "--fill-hover": hoverColor(fill) }

CSS variables with Tailwind

You can easily access CSS variables in utility classes from Tailwind. If you are already using Tailwind for your project, this is usually the easiest approach.

<Geography
  geography={...}
  style={{ "--fill": "#000000", "--fill-hover": "#333333" }}
  className="fill-(--fill) hover:fill-(--fill-hover)"
/>