npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@brownie-js/react

v1.0.4

Published

Lightweight interactive React maps with tile rendering, markers, clustering, routing, and geolocation. Zero mapping library dependencies.

Readme

@brownie-js/react

Lightweight interactive React maps with tile rendering, markers, clustering, routing, and geolocation. Zero mapping library dependencies.

npm version bundle size license

About

Named after Brownie, a good boy who loved exploring.

Brownie Maps is a lightweight React library for rendering interactive tile-based maps. Built on a tiles-first architecture (tile base layer + SVG vectors + HTML overlays) with custom Web Mercator projection math — no Leaflet, no Mapbox, no D3.

Features

  • Tile rendering — OpenStreetMap tiles (or any {z}/{x}/{y} provider) with smooth zoom
  • Zero mapping dependencies — custom Web Mercator projection via @brownie-js/core
  • Markers — clickable, draggable, with custom icons and viewport culling
  • Marker clustering — grid-based clustering with custom render support and category-based color mapping
  • GPU animations — smooth flyTo, marker enter/exit, route ant trail, cluster transitions (60fps, translate3d/opacity only, respects prefers-reduced-motion)
  • OSRM routing — draw road routes between points using the OSRM API
  • Geolocation — track user position with accuracy ring and pulse animation
  • Circles — geographic circles with meter-to-pixel conversion
  • Popups & Tooltips — positioned overlay components, popups with image support
  • Custom overlaysSVGLayer and HTMLLayer for fully custom map layers using a project() render prop
  • Map controlsMapControl positions controls in map corners; built-in ZoomControl and ScaleBar
  • CSS themingMapThemeProvider exposes CSS custom properties for consistent styling
  • Headless hooksuseMarker, usePopup, useCircle, useTooltip for custom render implementations
  • Keyboard accessible — keyboard navigation, Enter/Space activation, ARIA roles
  • Responsive — auto-sizes to container via ResizeObserver
  • TypeScript-first — complete type definitions for all components and hooks

Quick Start

npm install @brownie-js/react
import { GeoMap, TileLayer, Marker } from "@brownie-js/react";

function App() {
  return (
    <GeoMap
      center={[-43.17, -22.91]}
      zoom={12}
      style={{ width: "100%", height: 400 }}
    >
      <TileLayer />
      <Marker coordinates={[-43.17, -22.91]} ariaLabel="Rio de Janeiro" />
    </GeoMap>
  );
}

Components

GeoMap

Root container. Provides map context to all children. Supports pan, zoom (wheel, pinch, double-click), and inertia.

<GeoMap
  center={[-43.17, -22.91]}
  zoom={12}
  minZoom={1}
  maxZoom={18}
  style={{ width: "100%", height: 400 }}
  mapLabel="Interactive map"
  onMoveEnd={({ center, zoom, bounds }) => console.log(center)}
  onZoomChange={(zoom) => console.log(zoom)}
  onClick={({ latlng, pixel }) => console.log(latlng)}
>
  {children}
</GeoMap>

| Prop | Type | Default | Description | | -------------- | ------------------ | ------------------- | ------------------------------------------------------------ | | center | [number, number] | [0, 0] | Initial center [longitude, latitude]. | | zoom | number | 2 | Initial zoom level. | | minZoom | number | 1 | Minimum zoom level. | | maxZoom | number | 18 | Maximum zoom level. | | bounds | { sw, ne } | — | Constrain panning to bounds. | | width | number | auto | Explicit width. Auto-sizes via ResizeObserver when omitted. | | height | number | auto | Explicit height. Auto-sizes via ResizeObserver when omitted. | | className | string | — | CSS class for the container. | | style | CSSProperties | — | Inline styles for the container. | | mapLabel | string | 'Interactive map' | Accessible label (aria-label). | | onMoveEnd | (state) => void | — | Fires after pan/zoom with { center, zoom, bounds }. | | onZoomChange | (zoom) => void | — | Fires during zoom with current level. | | onClick | (event) => void | — | Map click with { latlng, pixel, originalEvent }. |

Imperative handle (via ref): flyTo(options) or flyTo(center, zoom?) (with configurable duration, easing, reduced-motion support), fitBounds(bounds, padding?), getZoom(), getCenter(), getBounds().

TileLayer

Renders map tile images from a tile server.

<TileLayer
  url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
  opacity={1}
  zIndex={0}
/>

| Prop | Type | Default | Description | | --------- | -------- | ------------- | -------------------------------------------------------- | | url | string | OpenStreetMap | Tile URL template with {z}, {x}, {y} placeholders. | | opacity | number | 1 | Layer opacity. | | zIndex | number | 0 | Layer stacking order. |

Marker

Point marker rendered as absolutely positioned HTML. Supports dragging and viewport culling.

<Marker
  coordinates={[-43.17, -22.91]}
  color="#E53935"
  size={32}
  anchor="bottom"
  draggable
  ariaLabel="Rio de Janeiro"
  onClick={(event, data) => console.log(data)}
  onDragEnd={(coords, data) => console.log(coords)}
/>

| Prop | Type | Default | Description | | -------------- | ------------------------- | ----------- | -------------------------------------------------------- | | coordinates | [number, number] | required | [longitude, latitude]. | | icon | ReactNode | — | Custom icon. Replaces default SVG pin. | | color | string | '#E53935' | Default pin color. | | size | number | 32 | Default pin size in pixels. | | anchor | 'center' \| 'bottom' | 'bottom' | Anchor point for positioning. | | draggable | boolean | false | Enable drag interaction. | | opacity | number | 1 | Marker opacity. | | data | Record<string, unknown> | — | Custom data passed to callbacks. | | onClick | (event, data?) => void | — | Click handler. | | onDragEnd | (coords, data?) => void | — | Fires after drag with new coordinates. | | onMouseEnter | (event) => void | — | Mouse enter handler. | | onMouseLeave | (event) => void | — | Mouse leave handler. | | animated | boolean | false | Enable GPU-accelerated enter animation (fade-in + drop). | | ariaLabel | string | — | Accessible name. |

MarkerCluster

Groups nearby markers into clusters at the current zoom level.

<MarkerCluster
  radius={60}
  maxZoom={16}
  renderCluster={(cluster) => <Badge>{cluster.count}</Badge>}
>
  <Marker coordinates={[-43.17, -22.91]} />
  <Marker coordinates={[-43.18, -22.92]} />
</MarkerCluster>

| Prop | Type | Default | Description | | ---------------- | ------------------------ | ------- | ----------------------------------------------------------------------------- | | radius | number | 60 | Cluster grid cell size in pixels. | | maxZoom | number | 16 | Above this zoom, markers are never clustered. | | animated | boolean | false | Enable GPU-accelerated cluster transitions. | | categoryKey | string | — | Marker data key to group by category. | | categoryColors | Record<string, string> | — | Map of category → hex color for cluster indicators. | | renderCluster | (cluster) => ReactNode | — | Custom cluster render function. Receives categories and dominantCategory. | | onClick | (cluster) => void | — | Cluster click handler. |

Route

SVG path between geographic coordinates. Optionally snaps to roads via OSRM.

<Route
  coordinates={[
    [-43.17, -22.91],
    [-46.63, -23.55],
  ]}
  color="#3388ff"
  strokeWidth={2}
  routing
  ariaLabel="Route from Rio to SP"
  onRouteLoaded={({ distance, duration }) => console.log(distance)}
/>

| Prop | Type | Default | Description | | ---------------- | -------------------- | ----------- | ------------------------------------------------- | | coordinates | [number, number][] | required | Waypoints [lon, lat]. | | color | string | '#3388ff' | Stroke color. | | strokeWidth | number | 2 | Stroke width. | | dashArray | string | — | SVG stroke-dasharray (e.g. "5,5"). | | animated | boolean | false | Enable CSS ant trail animation on the route path. | | animationSpeed | number | 2 | Ant trail animation cycle duration in seconds. | | routing | boolean | false | Enable OSRM road routing. | | routingUrl | string | — | Custom OSRM endpoint. | | onRouteLoaded | (data) => void | — | Fires with { distance, duration, geometry }. | | onClick | (event) => void | — | Click handler. | | ariaLabel | string | — | Accessible name. |

Circle

Geographic circle rendered as SVG with meter-to-pixel conversion.

<Circle
  center={[-43.17, -22.91]}
  radius={5000}
  color="#3388ff"
  ariaLabel="5km radius"
/>

| Prop | Type | Default | Description | | ------------- | ------------------ | ------------------------ | ------------------------ | | center | [number, number] | required | [longitude, latitude]. | | radius | number | required | Radius in meters. | | color | string | '#3388ff' | Stroke color. | | fillColor | string | 'rgba(51,136,255,0.2)' | Fill color. | | strokeWidth | number | 2 | Stroke width. | | opacity | number | 1 | Overall opacity. | | onClick | (event) => void | — | Click handler. | | ariaLabel | string | — | Accessible name. |

Popup

Positioned popup overlay at geographic coordinates. Auto-flips when near viewport edge. Supports optional card-style image.

<Popup
  coordinates={[-43.17, -22.91]}
  image={{ src: "/photo.jpg", alt: "Beach view", height: 150 }}
  onClose={() => setOpen(false)}
>
  <p>Hello from Rio!</p>
</Popup>

Tooltip

Lightweight positioned tooltip. Non-interactive (pointerEvents: none).

<Tooltip x={px} y={py} content="Label text" />

Geolocation

Renders user's GPS position as a blue dot with accuracy ring and pulse animation.

<Geolocation watch enableHighAccuracy onError={(err) => console.error(err)} />

Attribution

Auto-renders tile attribution (e.g. OpenStreetMap). Managed internally by GeoMap.

MapControl

Positions child elements as absolute overlays in a map corner.

import { MapControl, ZoomControl } from "@brownie-js/react";
import { ZoomControl, ScaleBar } from "@brownie-js/react/controls";

<GeoMap ...>
  <TileLayer />
  <MapControl position="top-right">
    <ZoomControl />
  </MapControl>
  <MapControl position="bottom-left">
    <ScaleBar />
  </MapControl>
</GeoMap>

| Prop | Type | Default | Description | | ----------- | ----------------------------------------------------------- | -------- | ---------------------------------------- | | position | 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right' | required | Corner to anchor the control. | | className | string | — | CSS class for the control wrapper. | | style | CSSProperties | — | Inline styles. |

SVGLayer

Custom SVG overlay that re-renders on pan/zoom. Receives a project(lon, lat) render prop.

<SVGLayer>
  {(project) => {
    const [x, y] = project(-43.17, -22.91);
    return <circle cx={x} cy={y} r={10} fill="red" />;
  }}
</SVGLayer>

| Prop | Type | Default | Description | | ------------- | ------------------------------------------------------ | ------- | ---------------------------------------- | | children | (project) => ReactNode | required | Render prop receiving project(lon, lat). | | zIndex | number | 1 | SVG stacking order. | | interactive | boolean | false | Enable pointer events on the SVG. | | className | string | — | CSS class for the SVG element. |

HTMLLayer

Custom HTML overlay that re-renders on pan/zoom. Receives a project(lon, lat) render prop.

<HTMLLayer>
  {(project) => {
    const [x, y] = project(-43.17, -22.91);
    return (
      <div style={{ position: "absolute", left: x, top: y }}>
        Custom overlay
      </div>
    );
  }}
</HTMLLayer>

| Prop | Type | Default | Description | | ------------- | ------------------------ | -------- | ------------------------------------------- | | children | (project) => ReactNode | required | Render prop receiving project(lon, lat). | | zIndex | number | 1 | Layer stacking order. | | interactive | boolean | false | Enable pointer events on the HTML layer. | | className | string | — | CSS class for the wrapper div. |

Loader

Full-area loading state placeholder with a map-pin icon and spinner.

<div style={{ width: "100%", height: 400 }}>
  {isLoading ? <Loader ariaLabel="Loading map" /> : <GeoMap ...>...</GeoMap>}
</div>

| Prop | Type | Default | Description | | ----------- | --------------- | --------------- | ------------------------------- | | ariaLabel | string | 'Loading map' | Accessible status label. | | className | string | — | CSS class for the container. | | style | CSSProperties | — | Inline styles for the container.|

Hooks

useMap()

Access the map context. Must be inside <GeoMap>. Does not re-render on pan/zoom.

const { project, invert, stateRef, width, height } = useMap();
const [px, py] = project(-43.17, -22.91);
const [lon, lat] = invert(400, 300);

useMapSubscription()

Like useMap() but subscribes to state changes — re-renders on every pan/zoom.

useMapLayer()

Projection hook for components that need project/invert plus current zoom, center, width, height. Re-renders on pan/zoom.

const { project, invert, zoom, center, width, height } = useMapLayer();

useMarker(options)

Headless marker hook. Returns computed styles, visibility flag, drag handlers, and ARIA props. Use to build fully custom marker components.

const { style, isOutOfView, isDragging, handlers, props } = useMarker({
  coordinates: [-43.17, -22.91],
  anchor: "bottom",
  draggable: true,
  onDragEnd: (coords) => console.log(coords),
});

usePopup(options)

Headless popup hook. Returns positioned style, flip state, visibility, and close handler.

const { style, isVisible, isFlipped, close, props, popupRef } = usePopup({
  coordinates: [-43.17, -22.91],
});

useCircle(options)

Headless circle hook. Returns the SVG center point, radius in pixels, and container style.

const { center, radiusPx, svgProps, containerStyle } = useCircle({
  center: [-43.17, -22.91],
  radius: 5000,
});

useTooltip(options)

Headless tooltip hook. Returns positioned style and ARIA props.

const { style, props } = useTooltip({ x, y });

useMergedRef(...refs)

Utility to merge multiple refs (callback or object) into a single callback ref.

const ref = useMergedRef(localRef, forwardedRef);

useGeolocation(options?)

Browser Geolocation API wrapper. Available from @brownie-js/react/geo.

import { useGeolocation } from "@brownie-js/react/geo";

const { position, error, loading } = useGeolocation({
  enableHighAccuracy: true,
});

useGeolocationDot(options?)

Headless geolocation dot hook — computes dot position, accuracy ring radius, and container style. Available from @brownie-js/react/geo.

import { useGeolocationDot } from "@brownie-js/react/geo";

const { position, dotCenter, accuracyRadiusPx, containerStyle, error, loading } =
  useGeolocationDot({ enableHighAccuracy: true });

useOsrmRoute(waypoints, enabled, url?)

Fetch road routes from an OSRM-compatible API. Includes in-memory caching and AbortController cleanup. Available from @brownie-js/react/route.

import { useOsrmRoute } from "@brownie-js/react/route";

const { data, loading, error } = useOsrmRoute(waypoints, true);

useRouteLayer(options)

Headless route layer hook — returns the SVG path d attribute, SVG container props, and route metadata. Available from @brownie-js/react/route.

import { useRouteLayer } from "@brownie-js/react/route";

const { pathD, svgProps, containerStyle, isLoading, routeData } = useRouteLayer({
  coordinates: [[-43.17, -22.91], [-46.63, -23.55]],
  routing: true,
});

useReverseGeocode(lat, lng)

Reverse geocoding via Nominatim. Available from @brownie-js/react/geo.

import { useReverseGeocode } from "@brownie-js/react/geo";

const { data, loading, error } = useReverseGeocode(-22.91, -43.17);

Sub-exports

The package uses entry-point sub-exports to keep the main bundle small:

| Import path | Exports | | ------------------------------ | --------------------------------------------------------- | | @brownie-js/react | Core components and headless hooks | | @brownie-js/react/controls | ZoomControl, ScaleBar | | @brownie-js/react/cluster | MarkerCluster | | @brownie-js/react/route | Route, useOsrmRoute, useRouteLayer | | @brownie-js/react/geo | Geolocation, useGeolocation, useGeolocationDot, useReverseGeocode | | @brownie-js/react/theme | MapThemeProvider |

import { GeoMap, TileLayer, Marker } from "@brownie-js/react";
import { MarkerCluster } from "@brownie-js/react/cluster";
import { Route, useOsrmRoute } from "@brownie-js/react/route";
import { Geolocation, useGeolocation } from "@brownie-js/react/geo";
import { ZoomControl, ScaleBar } from "@brownie-js/react/controls";
import { MapThemeProvider } from "@brownie-js/react/theme";

Accessibility

  • Map container: role="application" with configurable mapLabel
  • Markers: focusable with role="button", keyboard activation (Enter/Space)
  • Routes: focusable SVG paths with ariaLabel
  • Circles: role="img" with ariaLabel

License

MIT