- Accordion
- Alert
- Avatar
- Badge
- Breadcrumb
- Button
- Button Group
- Calendarnew
- Card
- Carousel
- Checkbox
- Chip
- Collapse
- Dialog
- Drawer
- Footer
- Gallery
- Icon Button
- Image
- Input
- List
- Mapnew
- Menu
- Navbar
- Pagination
- Popover
- Progress
- Radio
- Rating
- Select
- Sidebar
- Skeletonnew
- Slider
- Software Purchase Card
- Sonner / Toastnew
- Speed Dial
- Spinner
- Stepper
- Switch
- Table
- Tabs
- Textarea
- Timeline
- Tooltip
- Typography
- Video
Open Source & Attribution: This component uses MapLibre GL JS, OpenFreeMap tiles, OpenMapTiles schema, and OpenStreetMap data. Attribution is automatically displayed on the map. Read more →
import { Map } from "@/components/ui/map"
export function MapDemo() {
return (
<div className="h-[400px] w-full overflow-hidden rounded-lg border">
<Map center={[-122.4194, 37.7749]} zoom={12} />
</div>
)
}
Installation
pnpm dlx @creative-tim/ui@latest add map
Usage
import { Map } from "@/components/ui/map"<Map center={[-122.4194, 37.7749]} zoom={12} />Examples
Map with Controls
Interactive map with zoom, compass, locate, and fullscreen controls positioned in the corner.
"use client"
import { Map, MapControls } from "@/components/ui/map"
export function MapWithControls() {
const handleLocate = (coords: { lng: number; lat: number }) => {
console.log("User location:", coords)
// Custom behavior: could show a marker, save to state, etc.
}
return (
<div className="h-[400px] w-full overflow-hidden rounded-lg border">
<Map center={[-122.4194, 37.7749]} zoom={12}>
<MapControls
position="bottom-right"
showZoom
showCompass
showLocate
showFullscreen
onLocate={handleLocate}
/>
</Map>
</div>
)
}
Multiple Markers
Display multiple location markers with interactive tooltips on hover and popups on click showing detailed information.
import {
Map,
MapMarker,
MarkerContent,
MarkerPopup,
MarkerTooltip,
} from "@/components/ui/map"
export function MapMarkers() {
// San Francisco landmarks
const landmarks = [
{
id: 1,
name: "Golden Gate Bridge",
longitude: -122.4783,
latitude: 37.8199,
},
{
id: 2,
name: "Alcatraz Island",
longitude: -122.423,
latitude: 37.8267,
},
{
id: 3,
name: "Ferry Building",
longitude: -122.3933,
latitude: 37.7956,
},
{
id: 4,
name: "Coit Tower",
longitude: -122.4058,
latitude: 37.8024,
},
]
return (
<div className="h-[400px] w-full overflow-hidden rounded-lg border">
<Map center={[-122.4194, 37.7749]} zoom={12}>
{landmarks.map((landmark) => (
<MapMarker
key={landmark.id}
longitude={landmark.longitude}
latitude={landmark.latitude}
>
<MarkerContent>
<div className="bg-primary border-background ring-primary/20 size-3.5 rounded-full border-2 shadow-md ring-2" />
</MarkerContent>
<MarkerTooltip>
<div className="font-medium">{landmark.name}</div>
</MarkerTooltip>
<MarkerPopup>
<div className="space-y-1.5">
<p className="text-foreground font-semibold">{landmark.name}</p>
<p className="text-muted-foreground text-xs">
Coordinates: {landmark.latitude.toFixed(4)},{" "}
{landmark.longitude.toFixed(4)}
</p>
</div>
</MarkerPopup>
</MapMarker>
))}
</Map>
</div>
)
}
Note: This component includes CSS overrides for MapLibre's default popup and tooltip styles to ensure proper theme integration. The styles are automatically injected when the Map component is imported.
Marker with Popup
Markers with interactive popups that display detailed information when clicked.
import { Button } from "@/components/ui/button"
import { Map, MapMarker, MarkerContent, MarkerPopup } from "@/components/ui/map"
export function MapMarkerPopup() {
return (
<div className="h-[400px] w-full overflow-hidden rounded-lg border">
<Map center={[-122.4783, 37.8199]} zoom={13}>
<MapMarker longitude={-122.4783} latitude={37.8199}>
<MarkerContent>
<div className="bg-primary border-background ring-primary/20 size-3.5 rounded-full border-2 shadow-md ring-2" />
</MarkerContent>
<MarkerPopup closeButton>
<div className="space-y-2">
<h3 className="font-semibold">Golden Gate Bridge</h3>
<p className="text-muted-foreground text-xs">
Iconic suspension bridge spanning the Golden Gate strait
</p>
<Button size="sm" className="w-full">
Get Directions
</Button>
</div>
</MarkerPopup>
</MapMarker>
</Map>
</div>
)
}
Note: This component includes CSS overrides for MapLibre's default popup styles to ensure proper theme integration.
Marker with Tooltip
Markers with tooltips that appear on hover for quick information display.
import {
Map,
MapMarker,
MarkerContent,
MarkerTooltip,
} from "@/components/ui/map"
export function MapMarkerTooltip() {
const landmarks = [
{
name: "Golden Gate Bridge",
longitude: -122.4783,
latitude: 37.8199,
},
{
name: "Alcatraz Island",
longitude: -122.423,
latitude: 37.8267,
},
{
name: "Ferry Building",
longitude: -122.3933,
latitude: 37.7956,
},
]
return (
<div className="h-[400px] w-full overflow-hidden rounded-lg border">
<Map center={[-122.4194, 37.7749]} zoom={12}>
{landmarks.map((landmark) => (
<MapMarker
key={landmark.name}
longitude={landmark.longitude}
latitude={landmark.latitude}
>
<MarkerContent>
<div className="bg-primary border-background ring-primary/20 size-5 rounded-full border-2 shadow-md ring-2" />
</MarkerContent>
<MarkerTooltip>{landmark.name}</MarkerTooltip>
</MapMarker>
))}
</Map>
</div>
)
}
Note: This component includes CSS overrides for MapLibre's default tooltip styles to ensure proper theme integration.
Custom Map Style
Customize the visual appearance of the map with your own color scheme and branding using custom style JSON files.
import type { StyleSpecification } from "maplibre-gl"
import { Map } from "@/components/ui/map"
// Custom light style with soft teal/mint theme using OpenFreeMap tiles
// OpenFreeMap: MIT License - https://github.com/hyperknot/openfreemap/blob/main/LICENSE.md
const customLightStyle: StyleSpecification = {
version: 8,
name: "Custom Light Style",
sources: {
openmaptiles: {
type: "vector",
url: "https://tiles.openfreemap.org/planet",
attribution:
'© <a href="https://openfreemap.org">OpenFreeMap</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
},
},
glyphs: "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
layers: [
{
id: "background",
type: "background",
paint: { "background-color": "#f5f9f8" },
},
{
id: "water",
type: "fill",
source: "openmaptiles",
"source-layer": "water",
paint: { "fill-color": "#c5e3df" },
},
{
id: "landcover",
type: "fill",
source: "openmaptiles",
"source-layer": "landcover",
paint: { "fill-color": "#e8f5e9", "fill-opacity": 0.5 },
},
{
id: "roads-minor",
type: "line",
source: "openmaptiles",
"source-layer": "transportation",
filter: ["in", "class", "minor", "service", "street"],
paint: {
"line-color": "#90a4ae",
"line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.5, 18, 4],
},
},
{
id: "roads-major",
type: "line",
source: "openmaptiles",
"source-layer": "transportation",
filter: ["in", "class", "primary", "secondary", "tertiary", "trunk"],
paint: {
"line-color": "#4db6ac",
"line-width": ["interpolate", ["linear"], ["zoom"], 8, 1, 18, 8],
},
},
{
id: "roads-highway",
type: "line",
source: "openmaptiles",
"source-layer": "transportation",
filter: ["==", "class", "motorway"],
paint: {
"line-color": "#26a69a",
"line-width": ["interpolate", ["linear"], ["zoom"], 6, 1, 18, 12],
},
},
{
id: "buildings",
type: "fill",
source: "openmaptiles",
"source-layer": "building",
paint: { "fill-color": "#cfd8dc", "fill-opacity": 0.6 },
},
],
}
// Custom dark style with soft teal/slate theme
const customDarkStyle: StyleSpecification = {
version: 8,
name: "Custom Dark Style",
sources: {
openmaptiles: {
type: "vector",
url: "https://tiles.openfreemap.org/planet",
attribution:
'© <a href="https://openfreemap.org">OpenFreeMap</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
},
},
glyphs: "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
layers: [
{
id: "background",
type: "background",
paint: { "background-color": "#1e272e" },
},
{
id: "water",
type: "fill",
source: "openmaptiles",
"source-layer": "water",
paint: { "fill-color": "#2c3e50" },
},
{
id: "landcover",
type: "fill",
source: "openmaptiles",
"source-layer": "landcover",
paint: { "fill-color": "#2d3e36", "fill-opacity": 0.5 },
},
{
id: "roads-minor",
type: "line",
source: "openmaptiles",
"source-layer": "transportation",
filter: ["in", "class", "minor", "service", "street"],
paint: {
"line-color": "#455a64",
"line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.5, 18, 4],
},
},
{
id: "roads-major",
type: "line",
source: "openmaptiles",
"source-layer": "transportation",
filter: ["in", "class", "primary", "secondary", "tertiary", "trunk"],
paint: {
"line-color": "#4db6ac",
"line-width": ["interpolate", ["linear"], ["zoom"], 8, 1, 18, 8],
},
},
{
id: "roads-highway",
type: "line",
source: "openmaptiles",
"source-layer": "transportation",
filter: ["==", "class", "motorway"],
paint: {
"line-color": "#80cbc4",
"line-width": ["interpolate", ["linear"], ["zoom"], 6, 1, 18, 12],
},
},
{
id: "buildings",
type: "fill",
source: "openmaptiles",
"source-layer": "building",
paint: { "fill-color": "#37474f", "fill-opacity": 0.7 },
},
],
}
export function MapCustomStyle() {
return (
<div className="h-[400px] w-full overflow-hidden rounded-lg border">
<Map
center={[-122.4194, 37.7749]}
zoom={12}
styles={{
light: customLightStyle,
dark: customDarkStyle,
}}
/>
</div>
)
}
Custom Styling: You can create your own map style JSON files to match your brand colors. Place them in your public directory and reference them via the styles prop. Use Maputnik to visually edit map styles, or customize colors in the JSON directly.
Draggable Marker
Interactive marker that users can drag to update location with custom callbacks.
Current Position
Lng: -122.4194, Lat: 37.7749
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"
import { Map, MapMarker, MarkerContent } from "@/components/ui/map"
export function MapMarkerDraggable() {
const [coords, setCoords] = React.useState<[number, number]>([
-122.4194, 37.7749,
])
const [isDragging, setIsDragging] = React.useState(false)
const handleDragEnd = (lngLat: { lng: number; lat: number }) => {
setCoords([lngLat.lng, lngLat.lat])
setIsDragging(false)
console.log("Marker moved to:", lngLat)
}
const resetPosition = () => {
setCoords([-122.4194, 37.7749])
}
return (
<div className="space-y-3">
<div className="h-[400px] w-full overflow-hidden rounded-lg border">
<Map center={coords} zoom={12}>
<MapMarker
longitude={coords[0]}
latitude={coords[1]}
draggable
onDragStart={() => setIsDragging(true)}
onDragEnd={handleDragEnd}
>
<MarkerContent>
<div
className={`bg-primary size-5 rounded-lg border-2 border-white shadow-lg transition-transform ${
isDragging ? "scale-125" : ""
}`}
/>
</MarkerContent>
</MapMarker>
</Map>
</div>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="space-y-1">
<p className="text-sm font-medium">Current Position</p>
<p className="text-muted-foreground font-mono text-xs">
Lng: {coords[0].toFixed(4)}, Lat: {coords[1].toFixed(4)}
</p>
</div>
<Button size="sm" variant="outline" onClick={resetPosition}>
Reset Position
</Button>
</div>
</div>
)
}
Props
Map (Root Component)
| Prop | Type | Default | Description |
|---|---|---|---|
center | [number, number] | [-122.4194, 37.7749] (SF) | Map center coordinates [longitude, latitude] |
zoom | number | 12 | Initial zoom level (0-22) |
minZoom | number | 0 | Minimum zoom level allowed |
maxZoom | number | 22 | Maximum zoom level allowed |
styles | object | OpenFreeMap | Custom tile styles for light/dark themes |
className | string | - | Additional CSS classes |
children | ReactNode | - | Child components (markers, controls, etc.) |
MapMarker
| Prop | Type | Default | Description |
|---|---|---|---|
longitude | number | - | Marker longitude coordinate (required) |
latitude | number | - | Marker latitude coordinate (required) |
draggable | boolean | false | Whether the marker can be dragged |
onClick | () => void | - | Called when marker is clicked |
onDragStart | (lngLat: LngLat) => void | - | Called when drag starts |
onDrag | (lngLat: LngLat) => void | - | Called continuously during drag |
onDragEnd | (lngLat: LngLat) => void | - | Called when drag ends |
children | ReactNode | - | Marker content components |
MarkerContent
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | Blue dot | Custom marker appearance |
className | string | - | Additional CSS classes |
MarkerPopup
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Popup content |
className | string | - | Additional CSS classes |
closeButton | boolean | false | Show close button in popup |
closeOnClick | boolean | true | Close popup when clicking outside |
MarkerTooltip
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Tooltip content |
className | string | - | Additional CSS classes |
MarkerLabel
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Label content |
className | string | - | Additional CSS classes |
position | "top" | "bottom" | "left" | "right" | "top" | Label position relative to marker |
MapControls
| Prop | Type | Default | Description |
|---|---|---|---|
position | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "bottom-right" | Controls position on map |
showZoom | boolean | true | Show zoom in/out buttons |
showCompass | boolean | false | Show compass/reset north button |
showLocate | boolean | false | Show geolocation button |
showFullscreen | boolean | false | Show fullscreen toggle button |
onLocate | (coords: { lng: number; lat: number }) => void | - | Called when user location is found |
className | string | - | Additional CSS classes |
MapPopup (Standalone)
| Prop | Type | Default | Description |
|---|---|---|---|
longitude | number | - | Popup longitude (required) |
latitude | number | - | Popup latitude (required) |
children | ReactNode | - | Popup content |
className | string | - | Additional CSS classes |
closeButton | boolean | true | Show close button |
onClose | () => void | - | Called when popup is closed |
MapRoute
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | - | Unique route identifier (required) |
coordinates | [number, number][] | - | Array of [lng, lat] coordinates |
color | string | "#3b82f6" | Route line color |
width | number | 3 | Route line width in pixels |
opacity | number | 1 | Route line opacity (0-1) |
dashArray | number[] | - | Dash pattern for dashed lines |
interactive | boolean | true | Enable click/hover interactions |
onClick | () => void | - | Called when route is clicked |
useMap Hook
Returns the map instance and loading state for advanced use cases.
const { map, isLoaded } = useMap()| Return Value | Type | Description |
|---|---|---|
map | maplibregl.Map | null | MapLibre GL map instance |
isLoaded | boolean | Whether the map has finished loading |
Features
- Zero Configuration: Works immediately with free OpenFreeMap tiles, no API keys required
- Theme Support: Automatically switches between light and dark map tiles based on your theme
- Composable: Build complex map interfaces by combining simple components
- TypeScript: Full type safety with comprehensive TypeScript definitions
- Accessible: ARIA labels, keyboard navigation, and screen reader support
- Performant: Efficient rendering with MapLibre GL's WebGL-based engine
- Customizable: Easily customize colors, styles, and behavior to match your design
Best Practices
- Use San Francisco coordinates
[-122.4194, 37.7749]as the default or choose appropriate defaults for your use case - Keep zoom levels between 10-15 for city-level views, 15-18 for neighborhood views
- Use
MarkerTooltipfor quick information,MarkerPopupfor detailed interactions - Limit the number of markers rendered at once (use clustering for large datasets)
- Provide custom
onLocatehandlers to save user location or show notifications - Use
draggablemarkers for location pickers and address selection - Add
MapControlsto improve user navigation and orientation - Consider loading states while map initializes
- Test map behavior on both desktop and mobile devices
- Ensure map containers have explicit height (
h-[400px]or similar)
Advanced Usage
Custom Map Styles
Provide custom map tile styles for complete visual control over your maps. Supports separate styles for light and dark themes.
<Map
center={[-122.4194, 37.7749]}
zoom={12}
styles={{
light: "/map-styles/custom-light-style.json",
dark: "/map-styles/custom-dark-style.json",
}}
/>The map will automatically switch between light and dark styles based on your theme. You can use:
- Free OpenFreeMap styles:
positron,liberty,bright,dark - Your own custom style JSON files
Custom Marker Styling
<MapMarker longitude={lng} latitude={lat}>
<MarkerContent>
<div className="bg-primary text-primary-foreground rounded-full p-2">
<RestaurantIcon className="h-4 w-4" />
</div>
</MarkerContent>
</MapMarker>Programmatic Map Control
const { map } = useMap()
useEffect(() => {
if (map) {
map.flyTo({ center: [lng, lat], zoom: 14 })
}
}, [map, lng, lat])Drawing Routes
<MapRoute
id="route-1"
coordinates={[
[-122.4194, 37.7749],
[-122.4783, 37.8199],
[-122.4230, 37.8267],
]}
color="hsl(var(--primary))"
width={4}
/>The map component provides a powerful and flexible way to integrate interactive maps into your application. It's perfect for store locators, delivery tracking, real estate listings, travel planning, and any interface that requires geospatial visualization.
Copyrights and Licenses
This component is built with open-source technologies that require attribution:
- MapLibre GL JS - The rendering engine powering these maps. Licensed under BSD-3-Clause, a permissive open-source license that allows free use in commercial projects.
- OpenFreeMap - Provides the free vector map tiles used by default. OpenFreeMap is released under the MIT License and serves map tiles without requiring API keys or registration.
- OpenMapTiles - The vector tile schema used by OpenFreeMap. Code is licensed under BSD-3-Clause, design under CC-BY 4.0.
- OpenStreetMap - The underlying map data is sourced from OpenStreetMap contributors, licensed under ODbL. Attribution to OpenStreetMap is required when using their data.
The map component automatically displays the required attributions in the bottom corner of the map. If you create custom styles, ensure you include proper attribution for your tile and data sources.
On This Page
InstallationUsageExamplesMap with ControlsMultiple MarkersMarker with PopupMarker with TooltipCustom Map StyleDraggable MarkerPropsMap (Root Component)MapMarkerMarkerContentMarkerPopupMarkerTooltipMarkerLabelMapControlsMapPopup (Standalone)MapRouteuseMap HookFeaturesBest PracticesAdvanced UsageCustom Map StylesCustom Marker StylingProgrammatic Map ControlDrawing RoutesCopyrights and Licenses