If you've ever added a map to a React app, you know the drill. Pick a library, wrestle with API keys, fight the wrapper's styling system to make it match your design, then give up and drop down to raw MapLibre or Mapbox anyway. Most map libraries are either too opinionated to customize or too low-level to be a good starting point.
mapcn takes a different approach, and it's the same approach that made shadcn/ui popular for regular components: you don't install a map component as a black-box dependency, you copy the actual source into your project and own it.
What it actually is
mapcn is a collection of accessible, customizable map components for React, built on top of MapLibre GL and styled with Tailwind CSS. It's designed to slot directly into a shadcn/ui project, following the same "copy, don't install" philosophy shadcn made popular.
A few things stood out to me:
- No API key required. It ships with free CARTO basemap tiles by default, so you get a working map immediately, with zero signup friction.
- Theme aware out of the box. The map tiles automatically switch between light and dark styles based on your app's theme.
- You own the code. Since components are copied into your project rather than pulled in as an opaque dependency, you can edit anything, there's no version lock-in, and no fighting an abstraction layer to override one style.
- Not locked to one tile provider. Because it stays close to MapLibre's own style spec, you can swap in tiles from OpenStreetMap, MapTiler, Stadia Maps, Thunderforest, or basically any MapLibre-compatible provider whenever you outgrow the free default.
- Full TypeScript support, and it drops down to the raw MapLibre instance whenever you need more control than the components expose.
Getting started
If you already have Tailwind CSS and shadcn/ui set up, adding the map component is one command:
pnpm dlx shadcn@latest add @mapcn/map
This installs maplibre-gl and adds the map component into your project, the same way any other shadcn component gets added.
Then it's a normal React component:
import { Map, MapControls } from "@/components/ui/map";
import { Card } from "@/components/ui/card";
export function MyMap() {
return (
<Card className="h-[320px] p-0 overflow-hidden">
<Map center={[-74.006, 40.7128]} zoom={11}>
<MapControls />
</Map>
</Card>
);
}
That's a rendered, interactive, theme-aware map with zoom controls, no API key, no config file.
One implementation detail worth knowing: MapLibre parses map tiles in a Web Worker, which ships as a separate file. mapcn loads that worker from unpkg by default, pinned to your installed version, so there's no manual setup. If you're running under a strict Content Security Policy, you'll need to allow script-src 'self' https://unpkg.com and worker-src 'self' blob: (plus whatever your basemap host needs). If you'd rather self-host the worker entirely, you can copy the worker files into your public/ folder and point MapLibreGL.setWorkerUrl() at them instead.
Markers, popups, and tooltips
This is where the "composable" part of mapcn's pitch really shows. Instead of one giant marker prop with a dozen config options, you compose small pieces:
import {
Map,
MapMarker,
MarkerContent,
MarkerPopup,
MarkerTooltip,
} from "@/components/ui/map";
const locations = [
{ id: 1, name: "Empire State Building", lng: -73.9857, lat: 40.7484 },
{ id: 2, name: "Central Park", lng: -73.9654, lat: 40.7829 },
{ id: 3, name: "Times Square", lng: -73.9855, lat: 40.758 },
];
export function MarkersExample() {
return (
<div className="h-[420px] w-full">
<Map center={[-73.98, 40.76]} zoom={12}>
{locations.map((location) => (
<MapMarker key={location.id} longitude={location.lng} latitude={location.lat}>
<MarkerContent>
<div className="bg-primary size-4 rounded-full border-2 border-white shadow-lg" />
</MarkerContent>
<MarkerTooltip>{location.name}</MarkerTooltip>
<MarkerPopup>
<div className="space-y-1">
<p className="text-foreground font-medium">{location.name}</p>
<p className="text-muted-foreground text-xs">
{location.lat.toFixed(4)}, {location.lng.toFixed(4)}
</p>
</div>
</MarkerPopup>
</MapMarker>
))}
</Map>
</div>
);
}
Because MarkerPopup accepts arbitrary JSX, nothing stops you from building a rich popup with an image, a rating, and action buttons using your existing shadcn Button and Card components. It ends up looking like a genuine part of your app's UI instead of a plugin bolted on top of a map.
Markers can also be made draggable with a single draggable prop plus an onDrag handler, which is handy for location pickers or "confirm your address" style flows.
One thing worth flagging: MapMarker renders actual DOM elements, so it's a great fit for anywhere from a handful up to a few hundred markers. If you're plotting thousands of points, the docs point you toward rendering markers as a GeoJSON layer instead, which is far cheaper for large datasets.
Beyond basic markers
The component set goes further than pins on a map. Looking through the docs, mapcn also ships:
- Routes and arcs — for drawing paths and curved connections between points, useful for delivery tracking or "flights between cities" style visualizations.
- GeoJSON support — for rendering larger, more complex geographic datasets as map layers rather than individual DOM markers.
- Clustering — for grouping nearby points so your map doesn't turn into a wall of overlapping pins when the data gets dense.
-
Custom controls — the
MapControlscomponent in the basic example is itself composable and themeable.
Why this matters if you build client sites
For a lot of client work, whatever map library you reach for immediately becomes the thing that visually doesn't match the rest of the site. Store locators, property listings, delivery tracking, service-area maps, they all tend to need a "location finder" screen at some point, and it usually shows up as a default-blue Google Maps embed sitting awkwardly next to a carefully designed page.
Because mapcn is Tailwind-styled and copy-paste by design, a map built with it can actually inherit your design tokens instead of fighting them. And since there's no required API key, it's a genuinely fast way to prototype a location feature before deciding whether a paid tile provider is worth it for a given project.
Where to look next
- mapcn.dev — homepage and live demos
- Documentation — installation and philosophy
- Component docs — Map, Controls, Markers, Popups, Routes, Arcs, GeoJSON, Clusters
- GitHub — source, currently at 11k+ stars
If you're already in the shadcn/ui + Tailwind ecosystem and need a map at some point, this is worth a look before reaching for a heavier, more opinionated library.
We're ArtClick, a web development agency based in Kyoto. We build company websites, WordPress sites, and custom systems — with a focus on sites that are fast, well-designed, and easy to maintain long-term. Learn more at artclickdev.com.

Top comments (0)