Skip to content
txbrendan/ui

Choropleth Map

beta

Regions shaded by value in one hue, in steps, with hatching for regions with no data, a legend that filters, and arrow keys that move from region to region.

Preview

values

Members by state

steps

Turnout by prefecture

select

Clubs by country

Chosen: Brazil

loading

no shape

error

The map could not load.

The map could not load.

empty

No regions to show

No regions to show

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/choropleth-map

Use it for

  • A value per country, state, or province, where where it is matters, like members by state or sales by country.
  • Pass shape, a map already projected: { width, height, regions: [{ id, name, d, c }] }, with a title or aria-label.
  • Pass values by region id, like { 'US-TX': 120 }. A region without a number is hatched and reads 'No data'.
  • Pass steps to set where each shade starts, like [10, 50, 100] for four shades. By default the shades split the values into quarters.
  • Pass onRegionSelect to make each region a button, and selected to outline the chosen one.
  • This site builds its shapes into /maps: world.json for countries, and admin1/<ID>.json for each country's regions. Copy the ones you need into your app, or build your own in the same shape.

Avoid it for

  • Exact numbers people must compare. Use BarChart or BreakdownList beside the map.
  • Values that only follow area or population. Show a rate, like per 1,000 people, so big regions do not win by size.
  • Streets, zooming, or panning. This map draws fixed shapes, not tiles.

Props

  • shape

    The map, already projected: { width, height, regions, outline?, frames?, dots? }. Each region is { id, name, d, c, dot? }, where d is an SVG path and c a point inside it.

    ChoroplethShape

  • values

    Each region's value, by region id.

    Record<string, number | null>

  • steps

    Where each shade starts, lowest first. Three numbers make four shades.

    number[] · default the quartiles of values

  • valueFormat

    How values read, in the readout and the legend, like { style: 'percent' }.

    Intl.NumberFormatOptions

  • selected

    The region outlined as chosen.

    string | null

  • onRegionSelect

    Called on click, Enter, or Space. Makes each region a button with aria-pressed.

    (id: string) => void

  • noDataText

    What a region with no value reads.

    string · default "No data"

  • size

    Height. The map keeps its own shape inside it.

    "sm" | "md" | "lg" · default "md"

  • loading

    Shows the regions as placeholders, or a placeholder box when there is no shape yet.

    boolean

  • error

    Shows this message in place of the map. Pass onRetry for a retry button.

    ReactNode

  • emptyText

    Shown when the shape has no regions.

    ReactNode · default "No regions to show"

  • title

    Names the map. Pass this, aria-label, or aria-labelledby.

    ReactNode

  • locale

    The locale for numbers. The default keeps the server and the browser the same.

    string · default "en-US"

  • className

    Extra classes for the figure. '[--choropleth-color:var(--chart-3)]' sets the hue. It defaults to --chart-1.

    string

Accessibility

  • The map is a group named by its title. Each region is named by its name and value, like 'Texas: 120'.
  • The map is one Tab stop. Arrow keys move to the nearest region that way, and Home and End go to the first and last by name.
  • With onRegionSelect, each region is a button with aria-pressed, and Enter or Space chooses it.
  • No data is a hatch, not only a lighter shade, so it reads apart from low values without color.
  • The legend steps are buttons. Hovering or focusing one shows only its regions. Pressing one keeps it on, with aria-pressed.
  • In forced colors, the shades become steps of the text color, the hatch stays, and the focused region is outlined in Highlight.

Examples

Members by state

import usa from "./maps/admin1/USA.json";

<ChoroplethMap title="Members by state" shape={usa} values={{ "US-TX": 120, "US-CA": 210 }} />

Set steps and a format

<ChoroplethMap
  aria-label="Turnout by prefecture"
  shape={japan}
  values={turnout}
  steps={[0.5, 0.55, 0.6]}
  valueFormat={{ style: "percent" }}
/>

Choose a country

<ChoroplethMap
  title="Clubs by country"
  shape={world}
  values={clubs}
  selected={country}
  onRegionSelect={setCountry}
/>