Taiwan Tile Map (React)
A React tile-grid map of Taiwan's 22 counties and cities that needs no geo data: value-driven choropleth colouring, hover tooltip cards, click callbacks and arrow-key navigation, usable as a visual filter.
This is a WebberUI Pro component
Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.
Like a US-state tile-grid map, this lays out Taiwan's outline with equal-sized rounded squares — the main island runs north to south, the outlying islands sit on the left, and a 5 × 11 grid is the whole picture, with no GeoJSON or projection library to load. Pass data (county code or name → number) and every tile is coloured by interpolating along the value range, while counties without data stay light grey. Hovering or focusing a tile lifts it and floats a card with the name and value; clicking adds an outline plus a check badge and fires a callback, which makes it a natural visual filter for store distribution, epidemic or sales statistics. The 22 codes, full names, short names and default grid positions ship as TAIWAN_REGIONS, and any position can be overridden through layout.
npx shadcn@latest add "https://webberui.com/r/taiwan-tile-map.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<TaiwanTileMap />
Installation
npx shadcn@latest add "https://webberui.com/r/taiwan-tile-map.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/taiwan-tile-map.
Usage
import { TaiwanTileMap, TAIWAN_REGIONS } from "@/components/ui/taiwan-tile-map";
// Keys can be codes (TPE) or county names (臺北市 / 台北市); counties not listed show as "no data"
const storeCounts = { 臺北市: 128, 新北市: 176, TXG: 104, KHH: 98, 澎湖縣: 3 };
<TaiwanTileMap
data={storeCounts}
labels="short"
onSelect={(code, selected) => console.log(code, selected)}
/>
// Multi-select as a filter, with a fixed colour domain so months compare consistently
<TaiwanTileMap
data={storeCounts}
multi
domain={[0, 200]}
colors={["#e0f2fe", "#075985"]}
labels="value"
tooltipFormatter={(region, value) => `${region.name}: ${value ?? 0} stores`}
/>
// The built-in constant is handy for menus, lookup tables or custom positions
TAIWAN_REGIONS.map((r) => r.code); // ["LIE", "KIN", "PEN", "TPE", ...]Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | Record<string, number> | — | Value per county; keys may be codes (TPE) or county names (臺北市 / 台北市). Counties without data render light grey |
colorScale | (t: number) => string | — | Custom colour scale: takes the normalised value 0..1 and returns any CSS colour; takes precedence over colors |
colors | [string, string] | ["#d1fae5", "#047857"] | End colours [lightest, darkest] as hex, interpolated linearly in RGB |
domain | [number, number] | — | Fixed [min, max] for the colour scale; when omitted the data's min / max is used |
labels | "name" | "short" | "value" | "none" | "short" | What to print inside each tile: full name, short name, the value, or nothing |
selected | string | string[] | null | — | Controlled selection: a county code (or name); an array when multi, null for none |
defaultSelected | string | string[] | null | null | Initial selection in uncontrolled mode |
onSelect | (code: string, selected: string[]) => void | — | Fired when a tile is clicked: code is the county just toggled, selected is the full selection after the toggle |
multi | boolean | false | Allow multiple selection (clicking a selected tile again deselects it) |
showLegend | boolean | true | Show the gradient legend below the map (min / max plus a "no data" swatch) |
noDataLabel | string | "無資料" (no data) | Text shown in the tooltip and legend when a county has no value |
tooltipFormatter | (region: TaiwanRegion, value: number | undefined) => React.ReactNode | — | Custom tooltip content; the returned node replaces the default "name + value" |
valueFormatter | (value: number) => string | zh-TW thousands grouping | Number formatting used for value labels, the tooltip and the legend |
layout | Partial<Record<string, readonly [number, number]>> | — | Override individual grid positions: code → [col, row]; unlisted counties keep the defaults |
size | "sm" | "md" | "lg" | "md" | Size preset: controls the map's max width, gap, corner radius and font size |
aria-label | string | "台灣縣市方塊地圖" (Taiwan tile map) | Accessible label of the outermost group |
className | string | — | Forwarded to the outermost container |
TaiwanRegion
Each entry of the TAIWAN_REGIONS constant, and the first argument of tooltipFormatter.
| Field | Type | Default | Description |
|---|---|---|---|
code | string | — | County code (three-letter ISO 3166-2:TW style, e.g. TPE) |
name | string | — | Official full name, e.g. 臺北市 (Taipei City) |
short | string | — | One- or two-character abbreviation, e.g. 北 / 竹市 |
layout | readonly [col: number, row: number] | — | Default grid position; top-left is [0, 0], increasing right and down |
TAIWAN_REGIONS (22 TaiwanRegion entries), resolveTaiwanRegion(key) (look up by code or county name, accepting both 臺 and 台) and the TaiwanTileLabel / TaiwanTileMapSize types are also named exports.
How it works
- Default positions: Lienchiang
[0,1], Kinmen[0,2], Penghu[0,7]; Taipei[3,0], Keelung[4,0]; Taoyuan[2,1], New Taipei[3,1], Yilan[4,1]; Hsinchu City[1,2], Hsinchu County[2,2]; Miaoli[2,3]; Taichung[2,4], Nantou[3,4], Hualien[4,4]; Changhua[1,5]; Yunlin[1,6]; Chiayi City[1,7], Chiayi County[2,7], Taitung[4,7]; Tainan[1,8]; Kaohsiung[1,9]; Pingtung[2,10]. The grid is CSS grid (repeat(5, minmax(0, 1fr))withaspect-square); the column count is derived from the positions, so overridinglayoutre-flows the whole map. - Colouring: each value is normalised to 0..1 (
(value − min) / (max − min), with the range taken from the data ordomain) and then handed tocolorScale, or interpolated in RGB between the twocolors; a single value is treated as the darkest shade. Text inside a tile switches between dark and light based on the tile colour's WCAG relative luminance, falling back to the t value when a custom scale returns something unparseable (such ashsl()). - Tooltip: triggered by hover or keyboard focus, it opens downward on the top two rows and upward elsewhere, and aligns to the edge on the leftmost / rightmost column — no measuring, and it never leaves the map's bounds.
- Entrance animation: tiles fade and scale in staggered by
row, with a slight extra offset bycolwithin a row, so the map appears to unfurl from north to south; whenlayoutis overridden orsizechanges, tile positions glide with a layout animation.
Accessibility
- The grid is a
role="group"with anaria-label(customisable) and, when the legend is shown, anaria-describedbypointing at it; every county is atype="button"whosearia-labelincludes the full name and value (or the no-data text), witharia-pressedreflecting selection - Roving tabindex: the whole map is a single Tab stop; arrow keys move along the same column / row first and jump diagonally to the nearest tile when nothing lies in that direction,
Home/Endgo to the first / last tile,Enter/Spacetoggle selection andEscdismisses the tooltip; the focused tile gets afocus-visiblering - Selection changes are written to a hidden
aria-live="polite"region (e.g. "已選取 臺北市、桃園市" — selected Taipei City, Taoyuan City), and selected tiles get a check badge so selection is not conveyed by colour alone; the tooltip and legend bar arearia-hidden - When the user has "reduce motion" enabled at the system level, the staggered entrance, hover lift and layout animations are disabled and the tooltip simply appears / disappears, while colour and outline feedback stay the same
Solar Terms Timeline (React)
A horizontal timeline of the 24 solar terms for React: locates the current term for any date, counts down to the next one, with seasonal colour bands and phenology / seasonal-food cards; term dates come from the standard 21st-century approximation, getSolarTerms and currentSolarTerm are exported, and almanac values can override the formula.
AI Prompt Input
An AI chat input — auto-growing height, IME-safe Enter to submit, and a submit arrow that flies out.