Choropleth Map
betaRegions 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
steps
select
Chosen: Brazil
loading
no shape
error
The map could not load.
The map could not load.
empty
No regions to show
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. 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
| Prop | Type | Default | What it does |
|---|---|---|---|
| shape | ChoroplethShape | - | 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. |
| values | Record<string, number | null> | - | Each region's value, by region id. |
| steps | number[] | the quartiles of values | Where each shade starts, lowest first. Three numbers make four shades. |
| valueFormat | Intl.NumberFormatOptions | - | How values read, in the readout and the legend, like { style: 'percent' }. |
| selected | string | null | - | The region outlined as chosen. |
| onRegionSelect | (id: string) => void | - | Called on click, Enter, or Space. Makes each region a button with aria-pressed. |
| noDataText | string | "No data" | What a region with no value reads. |
| size | "sm" | "md" | "lg" | "md" | Height. The map keeps its own shape inside it. |
| loading | boolean | - | Shows the regions as placeholders, or a placeholder box when there is no shape yet. |
| error | ReactNode | - | Shows this message in place of the map. Pass onRetry for a retry button. |
| emptyText | ReactNode | "No regions to show" | Shown when the shape has no regions. |
| title | ReactNode | - | Names the map. Pass this, aria-label, or aria-labelledby. |
| locale | string | "en-US" | The locale for numbers. The default keeps the server and the browser the same. |
| className | string | - | Extra classes for the figure. '[--choropleth-color:var(--chart-3)]' sets the hue. It defaults to --chart-1. |
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}
/>