Skip to main content
How to Style a Custom Branded Map (Dark Mode, Colors, No Watermarks)
Tutorials

How to Style a Custom Branded Map (Dark Mode, Colors, No Watermarks)

Style a fully branded map in 2026: change background, roads, and water colors, add dark mode toggle, remove watermarks, and match your design system exactly.

Brent van der HeidenLast updated 9 min read
#custom map style#dark mode map#branded map#maps api#map customization#mapbox style

Every map you've ever seen on a competitor's app uses the same default style. The generic beige roads, the mid-teal water, the cluttered POI icons that fight for attention with your own UI, these are the universal defaults, designed to offend nobody and delight nobody. They communicate one thing clearly: this product is using a map library, not owning its map experience.

Custom map styling changes that. A dark mode map on a logistics dashboard, a brand-green road network on a real estate app, a clean minimalist style with your logo where the attribution watermark used to be, these are details that elevate a tool into a product.

This tutorial walks through the Mapbox Style Specification as implemented by MapAtlas, from the structure of the style JSON down to a complete light/dark toggle in React. You don't need GIS experience. You need to be comfortable with JSON and JavaScript.

If you haven't yet set up a basic map, start with How to Add Interactive Maps to Your Website first, then come back here for styling.

Why Default Map Styles Look Generic

A tile provider's default style is designed to be universally legible for the widest possible audience, in the widest possible context. It prioritizes informational density over aesthetic opinion. Roads need to be visible. Labels need to contrast. POI icons need to be recognizable.

None of those goals align with your product's goals. Your app probably cares about a small subset of what's on the map, delivery routes, property locations, healthcare facilities, and everything else is visual noise.

Custom styling lets you suppress what you don't need, emphasize what you do, and ensure the map reads as part of your product rather than a third-party widget dropped in.

Understanding the Style Specification

A MapAtlas style is a JSON document with five key sections:

{
  "version": 8,
  "name": "My Brand Style",
  "sources": { ... },
  "glyphs": "https://cdn.mapmetrics-atlas.net/basemaps-assets/fonts/{fontstack}/{range}.pbf",
  "sprite": "https://cdn.mapmetrics-atlas.net/basemaps-assets/sprites/v4/light",
  "layers": [ ... ]
}
  • sources, where the map data comes from (MapAtlas vector tile endpoints)
  • glyphs, where to load font files for label rendering
  • sprite, where to load icon images for POI markers
  • layers, the ordered list of visual layers, each with paint and layout properties

The layers array is where you spend most of your time. Each layer specifies a data type (fill, line, symbol, circle, fill-extrusion), which source features to draw, and how to paint them.

Here's a minimal but complete style that renders a vector tile base:

{
  "version": 8,
  "name": "Brand Base",
  "sources": {
    "mapatlas": {
      "type": "vector",
      "tiles": ["https://gateway.mapmetrics-atlas.net/planet20251013/{z}/{x}/{y}.mvt?token=YOUR_API_KEY"],
      "minzoom": 0,
      "maxzoom": 14
    }
  },
  "glyphs": "https://cdn.mapmetrics-atlas.net/basemaps-assets/fonts/{fontstack}/{range}.pbf",
  "sprite": "https://cdn.mapmetrics-atlas.net/basemaps-assets/sprites/v4/light",
  "layers": [
    {
      "id": "background",
      "type": "background",
      "paint": { "background-color": "#f5f0eb" }
    }
  ]
}

This renders a blank canvas in your chosen background color. From here you add layers for water, landuse, roads, buildings, and labels.

Dark Mode: Changing the Core Colors

A dark mode map is not simply an inverted raster image. It's a completely different set of paint properties applied to the same vector geometry. This is one of the core reasons vector tiles exist, one tile, infinite visual interpretations.

The key colors to update for a dark theme:

LayerLight modeDark mode
Background (land)#f5f0eb#1a1a2e
Water#a8d5e5#0d3b66
Urban landuse#e8e0d5#16213e
Parks/greenspace#c8e6c9#1b4332
Roads (major)#ffffff#3a3a5c
Roads (minor)#efefef#2d2d4a
Road labels#333333#cccccc

Here's how to apply this in a style layer:

{
  "id": "water",
  "type": "fill",
  "source": "mapatlas",
  "source-layer": "water",
  "paint": {
    "fill-color": "#0d3b66",
    "fill-opacity": 1
  }
}
{
  "id": "road-major",
  "type": "line",
  "source": "mapatlas",
  "source-layer": "transportation",
  "filter": ["==", "class", "primary"],
  "layout": {
    "line-join": "round",
    "line-cap": "round"
  },
  "paint": {
    "line-color": "#3a3a5c",
    "line-width": ["interpolate", ["linear"], ["zoom"], 10, 1, 16, 4]
  }
}

The interpolate expression in line-width scales the road width based on zoom level, narrow at low zoom, wider as you zoom in. This is data-driven styling without any additional network requests.

Applying Brand Colors

Most design systems define a primary color (say, #97C70A), a secondary color, and a neutral palette. The map should use these same values, not approximate them.

Target these layers for brand color application:

Roads as brand elements. If your brand color is vibrant enough, use it for major roads or highlighted routes:

{
  "id": "road-highlighted",
  "type": "line",
  "source": "mapatlas",
  "source-layer": "transportation",
  "filter": ["==", "class", "primary"],
  "paint": {
    "line-color": "#97C70A",
    "line-width": 3,
    "line-opacity": 0.8
  }
}

Custom POI markers. Replace the default sprite icons with your own by hosting a custom sprite sheet and updating the sprite URL. Then reference your icons in symbol layers:

{
  "id": "brand-poi",
  "type": "symbol",
  "source": "mapatlas",
  "source-layer": "poi",
  "layout": {
    "icon-image": "brand-pin",
    "icon-size": 1.2,
    "text-field": ["get", "name"],
    "text-font": ["Inter Bold", "Noto Sans Regular"],
    "text-size": 12,
    "text-offset": [0, 1.5],
    "text-anchor": "top"
  },
  "paint": {
    "text-color": "#97C70A",
    "text-halo-color": "#1a1a2e",
    "text-halo-width": 1
  }
}

Custom Fonts for Map Labels

The default map label fonts are generic. If your brand uses Inter, Roboto, or a custom typeface, you can use it for map labels too.

MapAtlas serves glyphs from its own glyph endpoint, which supports the standard Mapbox glyph format. To use a custom font:

  1. Generate .pbf glyph files from your typeface using a tool like fontnik.
  2. Host the glyph files on a CDN or your own server.
  3. Update the glyphs field in your style JSON:
{
  "glyphs": "https://your-cdn.com/fonts/{fontstack}/{range}.pbf"
}
  1. Reference the font in any symbol layer's text-font property:
"text-font": ["Inter Bold", "Noto Sans Regular"]

Always provide a fallback font (Noto Sans covers Unicode characters your primary font may not include). The renderer uses the first font in the array that contains a glyph for each character.

Removing Watermarks and Attribution

MapAtlas allows full white-label deployment. You can remove the MapAtlas logo from the map entirely, while keeping the OpenStreetMap credit the data licence requires.

In the SDK, attribution is handled by a control you can disable:

const map = new mapmetricsgl.Map({
  container: 'map',
  style: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/style.json&token=YOUR_API_KEY',
  center: [4.9041, 52.3676],
  zoom: 12,
  attributionControl: false,  // Remove the default attribution control
});

If you want to keep attribution but style it to match your UI, hide the default control and add your own:

map.addControl(
  new mapmetricsgl.AttributionControl({
    customAttribution: 'Map data © OpenStreetMap contributors',
    compact: true,
  }),
  'bottom-left'
);

This replaces the branding with a minimal, legally compliant attribution that respects OpenStreetMap's licence while matching your UI, so you keep full control of how the map is branded.

Light/Dark Theme Toggle in React

Here's a complete React component that manages a map with a light/dark toggle, using CSS custom properties to keep the theme in sync with the rest of your UI:

import { useEffect, useRef, useState } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

const STYLES = {
  light: 'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/style.json&token=YOUR_API_KEY',
  dark:  'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_DARK_STYLE_ID/style.json&token=YOUR_API_KEY',
};

export function BrandedMap({ center = [4.9041, 52.3676], zoom = 12 }) {
  const containerRef = useRef(null);
  const mapRef       = useRef(null);
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    const map = new mapmetricsgl.Map({
      container:        containerRef.current,
      style:            STYLES.light,
      center,
      zoom,
      attributionControl: false,
    });

    map.addControl(
      new mapmetricsgl.AttributionControl({ compact: true }),
      'bottom-left'
    );

    mapRef.current = map;
    return () => map.remove();
  }, []);

  useEffect(() => {
    if (mapRef.current) {
      mapRef.current.setStyle(STYLES[theme]);
    }
  }, [theme]);

  return (
    <div style={{ position: 'relative' }}>
      <div ref={containerRef} style={{ width: '100%', height: '500px' }} />
      <button
        onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}
        style={{
          position: 'absolute',
          top: 12,
          right: 12,
          padding: '8px 16px',
          background: theme === 'dark' ? '#ffffff' : '#1a1a2e',
          color:      theme === 'dark' ? '#1a1a2e' : '#ffffff',
          border: 'none',
          borderRadius: 6,
          cursor: 'pointer',
          zIndex: 1,
        }}
      >
        {theme === 'light' ? 'Dark mode' : 'Light mode'}
      </button>
    </div>
  );
}

The setStyle call re-renders the map with the new color scheme. Tile data that's already in cache is not re-fetched, only the visual interpretation changes.

Hosting a Custom Style JSON

For full control, host your style JSON as a static file on your CDN or S3 bucket. This lets you:

  • Version-control your style alongside your application code.
  • Update the visual design without redeploying the application (just update the hosted JSON).
  • Use different styles for different environments (preview vs. production).

Load a hosted style at runtime:

const map = new mapmetricsgl.Map({
  container: 'map',
  style: 'https://your-cdn.com/styles/brand-dark.json',
  center: [4.9041, 52.3676],
  zoom: 12,
});

The only constraint is CORS: the style JSON must be served with Access-Control-Allow-Origin: * (or your specific domain) so the browser can fetch it cross-origin.

Style Development Workflow

The fastest way to build a custom style is iterative, see changes in real time without rewriting code.

  1. Start with a MapAtlas base style JSON as your foundation. Create a style in the MapMetrics portal under Styles, copy its Style URL, and fetch that URL to download the JSON.
  2. Edit the JSON in your editor, watching which layer affects which visual element.
  3. Reload the map with your modified style JSON to see the result.
  4. Once stable, commit the style JSON to your repository and host it on your CDN.

For deeper customization, check the Map Visualization & Styling documentation for the full layer reference, paint property types, and expression syntax.

If you're coming from Mapbox and wondering what changed, see Mapbox vs. MapAtlas: Which Maps API Is Right for Your EU Project? for a side-by-side comparison of features and pricing.

And for the architectural underpinning of why vector tiles make all of this possible, Vector Tiles vs. Raster Tiles explains the rendering pipeline in detail.

Summary

Custom map styling is not a luxury, it's the difference between a map that feels like a third-party widget and one that feels like it was built for your product. With MapAtlas:

  • The style specification is standard JSON compatible with Mapbox GL JS tooling.
  • Dark mode is a style JSON swap, not a new tile set or server configuration.
  • Brand colors apply to roads, labels, and POI icons through paint properties.
  • Custom fonts load from any glyph endpoint you control.
  • Watermarks and attribution are fully configurable, white-label is supported on all paid plans.
  • The entire style can be toggled at runtime without page reload.

Start building your branded map today. Sign up for a free MapAtlas API key and load the base style JSON as your starting point. The first custom style takes about 30 minutes to get right, subsequent iterations take minutes.

Frequently Asked Questions

Can I remove the MapAtlas watermark from my map?

Yes. MapAtlas allows full white-label maps: you can remove the MapAtlas logo entirely and replace it with your own branding, keeping only the small OpenStreetMap data credit the open licence requires.

Is the MapAtlas style format compatible with Mapbox GL JS styles?

Yes. MapAtlas uses the Mapbox Style Specification, the same JSON format used by Mapbox GL JS and MapLibre GL. Any existing Mapbox style JSON works with MapAtlas with only the source URLs updated.

Can I toggle between light and dark map styles without reloading the page?

Yes. Call map.setStyle() with the new style URL or object. The map re-renders with the new visual configuration without reloading tile data that's already cached. Combined with CSS custom properties, this creates a seamless theme-aware map component.

Found this useful? Share it.

About the author

Brent van der Heiden

Written by

Brent van der Heiden

Co-Founder & CEO at MapAtlas

Brent built MapAtlas out of a conviction that developers deserve location APIs with fair pricing and genuine end-user privacy. Before that he co-founded MapMetrics, a community mapping and navigation app. He writes about geospatial infrastructure, map data, AI search visibility, and how location data powers the products people rely on every day.

View all articles →
Back to Blog