Skip to content
microcharts
ReferenceExpressiveCitySkyline

CitySkyline

React city skyline chart — group size as building height and activation as lit windows. Tiny accessible SVG microchart; RSC-safe static or /interactive.

encodes
building height (primary) + lit-window fraction (secondary)
precision
high height / low lit
nodes
2 per building + 1 (n ≤ 8)

CitySkyline answers "how do these groups compare on size, and how activated is each?" — two variables in one row. Height is the primary, precise channel (zero-anchored bars, like a MiniBar); the lit-window fraction is a secondary, low-precision channel you read as "mostly lit, half lit, or dark", not a number. Omit lit everywhere and it's a plain, honest bar row.

CitySkyline
interactive · 5 kB · static · 2.54 kB

Install

Import & usereact/city-skyline
import { CitySkyline } from "@microcharts/react/city-skyline";// teams — real values under “Sample data” below<CitySkyline data={teams} unit="teams" title="Team sizes" />
Sample data
const teams = [  { label: "Platform", value: 46, lit: 0.7 },  { label: "Core", value: 32, lit: 0.5 },  { label: "Web", value: 28, lit: 0.9 },  { label: "API", value: 40, lit: 0.3 },  { label: "Data", value: 18, lit: 0.6 },];
Needs package + stylesheetSet up with AI

Try it

When to use it

  • Good for — team or region size plus an activation read, an org KPI where two variables are the story, or a per-BU comparison with utilization.
  • Avoid for — a single variable (that's MiniBar), precise activation reads, or more than about eight groups.

Sizing

Variants

plain bars (no lit) + values

Edge cases

flat skyline
too short for windows

Four homes

Why this default

Windows come on when lit is present, because the two-variable read is exactly why this beats a MiniBar. Heights are always zero-anchored bars; the lit windows are quantized to the window count and filled bottom-up, so the activation reads as a fill level. There is no roofline variation, no antennas, and nothing encoded in building width — width, roof, and ground are constants, because every mark must earn its place. The secondary channel drops out before the primary: a building too short for any window row is a solid tower, and its lit still shows in the per-building readout on hover or keyboard focus.

Accessibility

The accessible name names the count and the tallest — "3 groups; tallest A at 46." The interactive entry roves the buildings with ←/→ (or hover), announcing each on its own: on the team demo at the top of this page, Platform reads "Platform: 46; 70% lit." — the size precisely, the activation as a percent.

The interactive entry follows the shared interaction contract: arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or Space selects a unit — pinning its readout so it survives blur, until you select it again or press Escape. On touch, a tap pins and a drag scrubs.

Props

PropTypeNotes
data*{ label, value, lit? }[]value = height; lit = 0–1 window fraction.
labelsbooleanCategory labels under the buildings.
groundbooleanThe baseline hairline (default true).
label"none" | "value"Numeral above each building.
unitstringCategory noun for the summary (default 'groups').
bwnumberBuilding width in viewBox units (default 9).
gapnumberGap between buildings in viewBox units (default 3).
fontSizenumberType size of the value numerals, in viewBox units. Defaults from `height`.
animatebooleaninteractiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion.

Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.

Related charts