Brazil map & locations
A clickable national map of the 27 federative units + a states-and-cities dataset — with no paid or external API. The geometry is a simplified, bundled IBGE GeoJSON (rendered as SVG), and the locations list mirrors utils/locations from tempest-fastapi-sdk.
Import from the tempest-react-sdk/br subpath
This module bundles data (~5600 city names + UF geometry). To keep it off the bundle of apps that don't use it, it lives in a separate subpath — import from tempest-react-sdk/br, not the root. The map geometry still loads lazily (only when BrazilMap mounts).
import { BrazilMap, citiesByUf } from "tempest-react-sdk/br";
When to use
- A clickable Brazil map to pick a state (dashboards, regional filters).
- Choropleth: tint states by a metric (sales, users, coverage).
- A cascading State → City selector in forms.
- Query states/cities/regions offline (
citiesByUf,ufChoices, ...).
Part 1 — Locations data
Start with the data: pure, network-free functions available immediately.
import {
listStates,
getState,
citiesByUf,
statesByRegion,
ufChoices,
isValidUf,
normalizeUf,
} from "tempest-react-sdk/br";
listStates().length; // 27 (sorted by name)
getState("sp");
// { uf: "SP", name: "São Paulo", region: "Sudeste", cities: [...] }
citiesByUf("RJ"); // ["Angra dos Reis", "Aperibé", ..., "Rio de Janeiro", ...]
citiesByUf("XX"); // [] — an invalid UF returns an empty list (never throws)
statesByRegion("Sul").map((s) => s.uf); // ["PR", "RS", "SC"]
normalizeUf(" rj "); // "RJ"
normalizeUf("zz"); // null
isValidUf("mg"); // true
Empty collections are not errors
citiesByUf of an unknown UF returns [], it does not throw. Same convention as the backend: "no matches" is a valid result.
Feed a <Select> / <Combobox>
ufChoices() and cityChoices(uf) already return { value, label }:
import { Select } from "tempest-react-sdk";
import { ufChoices } from "tempest-react-sdk/br";
<Select label="State" placeholder="Select" options={ufChoices()} />;
Part 2 — State → City selector
BrazilStateCitySelect chains two selects: picking a state filters the cities of that UF. The city resets when the state changes.
import { BrazilStateCitySelect } from "tempest-react-sdk/br";
export function AddressForm() {
return (
<BrazilStateCitySelect
onChange={({ uf, city }) => console.log(uf, city)}
stateLabel="State"
cityLabel="City"
/>
);
}
defaultUf/defaultCity— initial values (uncontrolled).onChange({ uf, city })— fires on every change;uf/cityarenullwhen empty.layout="column"stacks the selects (default is side by side).disabledlocks both.
City enables only after a state
The city select is disabled until a state is chosen — there is nothing to list before that.
Part 3 — The BrazilMap
SVG map of the 27 UFs, with auto-fit, acronym labels and per-state clicking. No external tiles.
Clickable map
import { useState } from "react";
import { BrazilMap, type UF } from "tempest-react-sdk/br";
export function MapPicker() {
const [uf, setUf] = useState<UF | null>(null);
return (
<>
<BrazilMap selected={uf} onSelect={setUf} height={440} />
{uf && <p>Selected: {uf}</p>}
</>
);
}
onSelect(uf)fires on click (and on Enter/Space — states are focusable whenonSelectis set).selectedaccepts a single UF or a list — handy for multi-select.- Each state has an
aria-labelwith the name — accessible by default.
Hover tooltip
By default (showTooltip, default true) a floating tooltip appears on hover: name, acronym, region and city count — plus the choropleth value when values is set (e.g. São Paulo (SP) · Sudeste · 645 cidades). Pass showTooltip={false} to disable, or renderTooltip={(data) => ...} to customize the content (data = { uf, name, value? }).
<BrazilMap
renderTooltip={({ uf, name, value }) => (
<><strong>{name}</strong> — {value ?? "no data"}</>
)}
/>
Choropleth (tint by metric)
Pass values (one number per UF) and each state is tinted linearly between minColor and maxColor:
import { BrazilMap } from "tempest-react-sdk/br";
const sales = { SP: 1200, MG: 640, RJ: 580, BA: 410, RS: 390 };
<BrazilMap values={sales} minColor="#e0f2fe" maxColor="#0369a1" showLabels={false} />;
States without a value use the base surface color.
Map + cities (full recipe)
The case that motivated the module: click the map and list the state's cities.
import { useState } from "react";
import {
BrazilMap,
BrazilStateCitySelect,
getState,
type UF,
} from "tempest-react-sdk/br";
export function NationalMap() {
const [uf, setUf] = useState<UF | null>(null);
const state = uf ? getState(uf) : null;
return (
<div>
<BrazilMap selected={uf} onSelect={setUf} height={440} />
{state && (
<section>
<h3>
{state.name} — {state.cities.length} cities
</h3>
<BrazilStateCitySelect
key={uf}
defaultUf={uf!}
onChange={({ city }) => console.log("city:", city)}
/>
</section>
)}
</div>
);
}
Reset on state change
The key={uf} remounts the selector when the UF changes from the map, ensuring the city resets.
BrazilMap props
(see also BrazilStateMap for the municipality level.)
| Prop | Type | Default | Description |
|---|---|---|---|
selected |
UF \| UF[] \| null |
— | Highlighted UF(s). |
onSelect |
(uf: UF) => void |
— | Click/keyboard on a state. |
values |
Partial<Record<UF, number>> |
— | Per-UF metric → choropleth. |
minColor / maxColor |
string |
primary tints | Choropleth scale ends. |
height |
number |
440 |
Viewport height in px. |
padding |
number |
12 |
Inner padding in px. |
showLabels |
boolean |
true |
Acronym at each UF centroid. |
showTooltip |
boolean |
true |
Floating hover tooltip (name + region + city count + value). |
renderTooltip |
(data) => ReactNode |
— | Custom tooltip content ({ uf, name, value? }). |
label |
string |
"Mapa do Brasil por estado" |
Accessible region label. |
Part 4 — State submap (BrazilStateMap)
A submap of one state with all its municipalities clickable. Municipal geometry is split per UF and loaded lazily — opening the SP map fetches only SP's chunk (~40-70 KB gzip), never the country's ~2 MB.
import { useState } from "react";
import { BrazilStateMap, type Municipality } from "tempest-react-sdk/br";
export function SPMunicipalities() {
const [city, setCity] = useState<Municipality | null>(null);
return (
<>
<BrazilStateMap uf="SP" selected={city?.name} onSelect={setCity} height={420} />
{city && <p>{city.name} — IBGE {city.id}</p>}
</>
);
}
uf(required) — the state to draw.onSelect({ id, name })— fires on a municipality click (id= 7-digit IBGE code).selected— matches byidorname; accepts a list.values— choropleth by municipality (key =idorname).showLabels—falseby default: a state has hundreds of municipalities and labels overlap.showTooltip(defaulttrue) — floating hover tooltip with name + IBGE code (+ choropleth value when present).renderTooltip={(data) => ...}customizes it (data={ id, name, value? }).
National → state drill-down (recipe)
Combine both maps: clicking the national map switches the submap's state.
import { useState } from "react";
import { BrazilMap, BrazilStateMap, type Municipality, type UF } from "tempest-react-sdk/br";
export function DrillDown() {
const [uf, setUf] = useState<UF>("SP");
const [city, setCity] = useState<Municipality | null>(null);
return (
<div style={{ display: "flex", gap: 16 }}>
<BrazilMap
selected={uf}
onSelect={(u) => {
setUf(u);
setCity(null);
}}
height={320}
/>
<BrazilStateMap uf={uf} selected={city?.name} onSelect={setCity} height={320} />
</div>
);
}
Municipal choropleth
import { BrazilStateMap } from "tempest-react-sdk/br";
<BrazilStateMap
uf="RJ"
values={{ "Rio de Janeiro": 100, Niterói: 40, "Duque de Caxias": 55 }}
/>;
Map names vs. names dataset
Municipality names on the map come from the IBGE GeoJSON; citiesByUf comes from the names dataset. They are nearly identical, but spelling/accents may differ in rare cases. To match values, prefer the IBGE code (id) when you have it.
Direct municipal-geometry access
import { loadStateMunicipalities } from "tempest-react-sdk/br";
const sp = await loadStateMunicipalities("SP");
sp?.features.length; // 644
Part 5 — Offline geocoding
Convert between name/coordinate and municipality, offline. Uses a compact centroid index (~97 KB gzip) loaded lazily — no network calls, no API key.
import {
reverseGeocode,
nearestMunicipality,
geocodeMunicipality,
municipalityCentroid,
stateCentroid,
} from "tempest-react-sdk/br";
// Coordinate → the municipality that CONTAINS it (point-in-polygon, exact):
await reverseGeocode({ latitude: -23.5505, longitude: -46.6333 });
// { id: "3550308", name: "São Paulo", uf: "SP" }
// Coordinate → nearest-centroid municipality (fast, approximate, no geometry):
await nearestMunicipality({ latitude: -23.55, longitude: -46.63 });
// { id, name, uf: "SP", latitude, longitude, distanceKm }
// Name → coordinate (homonyms may exist across states):
await geocodeMunicipality("Bonito"); // several
await geocodeMunicipality("São Paulo", "SP"); // filtered by UF
// Centroids:
await municipalityCentroid("3550308"); // { id, name, uf, latitude, longitude }
await stateCentroid("SP"); // { latitude, longitude }
reverseGeocode vs nearestMunicipality
reverseGeocodedoes point-in-polygon → returns the municipality that actually contains the point. Loads one state's geometry (lazy per-UF chunk). Pass{ uf }if you know it, to skip candidate-state detection.nearestMunicipalitycompares centroids only → fast and geometry-free, but near borders / in large municipalities it can pick a neighbor.
Recipe: "where am I?" (GPS → municipality)
Combine with usePositionTracker from the Geolocation module:
import { useEffect, useState } from "react";
import { usePositionTracker } from "tempest-react-sdk";
import { reverseGeocode, type ReverseGeocodeResult } from "tempest-react-sdk/br";
export function WhereAmI() {
const { lastPoint } = usePositionTracker({ autoStart: true });
const [place, setPlace] = useState<ReverseGeocodeResult | null>(null);
useEffect(() => {
if (lastPoint) reverseGeocode(lastPoint).then(setPlace);
}, [lastPoint]);
return <p>{place ? `You are in ${place.name} — ${place.uf}` : "Locating…"}</p>;
}
Accuracy
The geometry is simplified (~2 km). Points within ~1-2 km of a border may resolve to the neighboring municipality; points offshore / outside the territory fall back to the nearest centroid.
Part 6 — Markers, color scales and legend
Markers (pins)
BrazilMap, BrazilStateMap and TrajectoryMap (the Geolocation module) accept markers — { latitude, longitude } points plotted over the map, with label (tooltip), color, radius and id. onMarkerClick(marker, index) on click.
import { BrazilMap, type GeoMarker } from "tempest-react-sdk/br";
const capitals: GeoMarker[] = [
{ id: "sp", latitude: -23.55, longitude: -46.63, label: "São Paulo", color: "#e11d48" },
{ id: "rj", latitude: -22.91, longitude: -43.17, label: "Rio de Janeiro" },
];
<BrazilMap markers={capitals} onMarkerClick={(m) => console.log(m.label)} />;
Pins from geocoding
Combine with geocoding (Part 5): municipalityCentroid(id) or geocodeMunicipality(name) give the coordinates to turn into markers.
Color scales + legend
For a choropleth beyond the 2-color ramp, pass colorScale (from sequentialScale/quantizeScale/thresholdScale) and pair it with <MapLegend>. Built-in colorblind-safe palettes: SEQUENTIAL_BLUES, SEQUENTIAL_GREENS, SEQUENTIAL_VIRIDIS, DIVERGING_RDBU.
import {
BrazilMap,
MapLegend,
sequentialScale,
SEQUENTIAL_VIRIDIS,
} from "tempest-react-sdk/br";
const sales = { SP: 1200, MG: 640, RJ: 580, BA: 410, RS: 390 };
const scale = sequentialScale(0, 1200, SEQUENTIAL_VIRIDIS);
<div>
<BrazilMap values={sales} colorScale={scale} showLabels={false} />
<MapLegend title="Sales (R$ k)" min={0} max={1200} palette={SEQUENTIAL_VIRIDIS} />
</div>;
sequentialScale(min, max, palette)— continuous gradient.quantizeScale(min, max, palette)—palette.lengthequal bands.thresholdScale(thresholds, palette)— bands by cutoff (palettehasthresholds.length + 1colors).<MapLegend>— continuous gradient (min/max/palette+format) or discrete bands (items={[{ color, label }]}).
Brand palette
The palettes are public standards (ColorBrewer/Viridis). Swap for any ordered list of your brand's hex colors — the scale builders accept any string[].
Part 7 — Zoom, color-by-region and municipality search
Pan & zoom
zoomable enables wheel-zoom (anchored at the cursor) + drag-pan on BrazilMap/BrazilStateMap. Double-click resets; a Reset button appears while zoomed/panned.
<BrazilStateMap uf="MG" zoomable />
Color by region
colorByRegion tints each state by its macro-region (categorical), overriding values/colorScale. Pair it with a discrete legend via regionLegendItems().
import { BrazilMap, MapLegend, regionLegendItems } from "tempest-react-sdk/br";
<div>
<BrazilMap colorByRegion showLabels={false} />
<MapLegend title="Region" items={regionLegendItems()} />
</div>;
REGION_COLORS exposes the region → color map (swap for your brand palette).
Municipality search (autocomplete)
MunicipalitySearch is an offline autocomplete (uses searchMunicipalities, debounced). Wire onSelect to a BrazilStateMap's selected to highlight it on the map.
import { useState } from "react";
import { MunicipalitySearch, BrazilStateMap, type UF } from "tempest-react-sdk/br";
export function SearchOnMap() {
const [uf] = useState<UF>("SP");
const [city, setCity] = useState<string | null>(null);
return (
<>
<MunicipalitySearch uf={uf} label="Municipality" onSelect={(m) => setCity(m.name)} />
<BrazilStateMap uf={uf} selected={city} zoomable />
</>
);
}
ufrestricts the search to one state; without it, it searches the whole country (the result shows the UF).onSelect(m)receives{ id, name, uf, latitude, longitude }— enough to also center/mark it.
About the geometry
- Source: IBGE UF boundaries (public domain), simplified with Douglas-Peucker (~2 km tolerance) and rounded to 3 decimals.
- Size: ~119 KB raw / ~36 KB gzip, in a separate chunk loaded lazily by
BrazilMap. - Accuracy: adequate for a clickable overview map, not for precise geographic analysis or area computation.
Municipality: use BrazilStateMap
BrazilMap draws states. For the municipality level, BrazilStateMap draws every municipality of a state — the municipal geometry (~2 MB total) is split per UF and loaded lazily, one chunk per state, so it never lands in a single bundle.
Advanced geometry access
Need the GeoJSON for a custom render? Load it lazily:
import { loadBrUfGeoJson } from "tempest-react-sdk/br";
const collection = await loadBrUfGeoJson();
collection.features.length; // 27
Recap
- Data:
listStates,getState,citiesByUf,statesByRegion,ufChoices,cityChoices,isValidUf,normalizeUf,isValidCity— offline, mirroring the FastAPI SDK'sutils/locations. - Selector:
BrazilStateCitySelectchains State → City. - National map:
BrazilMaprenders the 27 UFs in SVG — clickable (onSelect), highlightable (selected) and choropleth (values). No external tiles; bundled, lazy geometry. - State submap:
BrazilStateMapdraws every municipality of a UF — clickable, choropleth, per-state geometry loaded lazily.loadStateMunicipalities(uf)exposes the raw geometry. - Import always from the
tempest-react-sdk/brsubpath.
See also
- Geolocation — lat/lon capture, trajectory and tile-free
TrajectoryMap - Forms BR — CPF/CNPJ/CEP and
useViaCEP - Components: Data entry —
Select,Combobox