---
title: "Customize annotation pins and colors | Geosplats sdk"
description: "Style annotations with colors, icons, numbers, and custom popup placement."
---

> For AI agents: a documentation index is available at [/llms.txt](https://docs.maptiler.com/llms.txt). Markdown versions of all pages are available by appending .md to any URL.

# Customize annotation pins and colors

This example shows how to customize the appearance of annotations on a 3D Gaussian splat model using custom colors, pin shapes, numbered step badges, and prioritized popup placement.

### NPM module setup

```bash
npm install --save @maptiler/geosplats
```

#### main.js

```js
import { SplatModel, Map, config } from "@maptiler/geosplats";
import "@maptiler/geosplats/dist/maptiler-geosplats.css";

config.apiKey = "YOUR_MAPTILER_API_KEY_HERE";

const map = new Map({
  apiKey: config.apiKey,
  container: "map",
  center: [-80.27822, 25.74077],
  zoom: 17.5,
  pitch: 50,
  bearing: -15,
});

let splatModel;
map.on("load", () => {
  splatModel = new SplatModel({
    model: "YOUR_MAPTILER_SPLAT_MODEL_ID_HERE",
    revealParams: { enabled: true },
  });
  map.addSplatModel(splatModel);
});

map.on("splatmodel:load", () => {
  // 1. Single character label creates a round circular badge (ideal for numbered tours)
  splatModel.addAnnotation({
    pos: { x: -0.06, z: -0.26 },
    label: "1",
    color: "#3174ff",
    popup: "<h3>Tour Stop 1</h3><p>Main hotel lobby and reception.</p>",
  });

  splatModel.addAnnotation({
    pos: { x: -0.4, z: -0.2 },
    label: "2",
    color: "#2bb673",
    popup: "<h3>Tour Stop 2</h3><p>Outdoor heated swimming pool and terrace.</p>",
  });

  // 2. Icon-only round pin: an emoji without label text
  splatModel.addAnnotation({
    pos: { x: 0.1, z: 0.2 },
    icon: "🚘",
    color: [77, 77, 77],
    popup: "<h3>Valet Parking</h3><p>Underground secure parking garage.</p>",
  });

  // 3. Hex color and customized popup placement preference
  splatModel.addAnnotation({
    pos: { x: -0.6, z: 0.5 },
    icon: "⛳",
    label: "Golf Course",
    color: "#e69439",
    popupPlacement: ["top", "bottom", "right", "left"],
    popup: "<h3>Golf Course</h3><p>18-hole championship green.</p>",
  });

  // 4. Color defined as an RGB object
  splatModel.addAnnotation({
    pos: { x: 0.35, z: -0.25 },
    icon: "💆",
    label: "Luxury Spa",
    color: { r: 180, g: 60, b: 180 },
    popupPlacement: ["left", "right"],
    popup: "<h3>Luxury Spa</h3><p>Full wellness, massage, and sauna treatments.</p>",
  });

  // 5. Named color
  splatModel.addAnnotation({
    pos: { x: 0.6, z: 0.46 },
    icon: "🎾",
    label: "Tennis Club",
    color: "red",
    popup: "<h3>Tennis Club</h3><p>10 floodlit championship courts.</p>",
  });
});
```

#### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex" />
    <title>Customize annotation pins and colors</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div id="map"></div>
    <script type="module" src="./main.js"></script>
  </body>
</html>
```

#### style.css

```css
body {
  font-family: Inter, sans-serif;
  margin: 0;
  overflow: hidden;
}

#map {
  width: 100%;
  height: 100vh;
}
```

### Basic JavaScript setup

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Customize annotation pins and colors</title>
    <link
      rel="stylesheet"
      href="https://cdn.maptiler.com/maptiler-geosplats/v1.2.1/maptiler-geosplats.css"
    />
    <style>
      body {
        font-family: Inter, sans-serif;
        margin: 0;
        overflow: hidden;
      }

      #map {
        width: 100%;
        height: 100vh;
      }
    </style>
  </head>

  <body>
    <div id="map"></div>
    <script type="module">
      import {
        SplatModel,
        Map,
        config,
      } from "https://cdn.maptiler.com/maptiler-geosplats/v1.2.1/maptiler-geosplats.mjs";

      config.apiKey = "YOUR_MAPTILER_API_KEY_HERE";

      const map = new Map({
        apiKey: config.apiKey,
        container: "map",
        center: [-80.27822, 25.74077],
        zoom: 17.5,
        pitch: 50,
        bearing: -15,
      });

      let splatModel;
      map.on("load", () => {
        splatModel = new SplatModel({
          model: "YOUR_MAPTILER_SPLAT_MODEL_ID_HERE",
          revealParams: { enabled: true },
        });
        map.addSplatModel(splatModel);
      });

      map.on("splatmodel:load", () => {
        // 1. Single character label creates a round circular badge (ideal for numbered tours)
        splatModel.addAnnotation({
          pos: { x: -0.06, z: -0.26 },
          label: "1",
          color: "#3174ff",
          popup: "<h3>Tour Stop 1</h3><p>Main hotel lobby and reception.</p>",
        });

        splatModel.addAnnotation({
          pos: { x: -0.4, z: -0.2 },
          label: "2",
          color: "#2bb673",
          popup: "<h3>Tour Stop 2</h3><p>Outdoor heated swimming pool and terrace.</p>",
        });

        // 2. Icon-only round pin: an emoji without label text
        splatModel.addAnnotation({
          pos: { x: 0.1, z: 0.2 },
          icon: "🚘",
          color: [77, 77, 77],
          popup: "<h3>Valet Parking</h3><p>Underground secure parking garage.</p>",
        });

        // 3. Hex color and customized popup placement preference
        splatModel.addAnnotation({
          pos: { x: -0.6, z: 0.5 },
          icon: "⛳",
          label: "Golf Course",
          color: "#e69439",
          popupPlacement: ["top", "bottom", "right", "left"],
          popup: "<h3>Golf Course</h3><p>18-hole championship green.</p>",
        });

        // 4. Color defined as an RGB object
        splatModel.addAnnotation({
          pos: { x: 0.35, z: -0.25 },
          icon: "💆",
          label: "Luxury Spa",
          color: { r: 180, g: 60, b: 180 },
          popupPlacement: ["left", "right"],
          popup: "<h3>Luxury Spa</h3><p>Full wellness, massage, and sauna treatments.</p>",
        });

        // 5. Named color
        splatModel.addAnnotation({
          pos: { x: 0.6, z: 0.46 },
          icon: "🎾",
          label: "Tennis Club",
          color: "red",
          popup: "<h3>Tennis Club</h3><p>10 floodlit championship courts.</p>",
        });
      });
    </script>
  </body>
</html>
```

## Pin geometry and sizing

The pin automatically adjusts its shape depending on the length of its content:

-   **Single character or emoji**: Produces a circular pin. This layout is ideal for numbered tour sequences (`"1"`, `"2"`, `"3"`) or standalone icon symbols (`"🚘"`, `"⛳"`).
-   **Longer text**: From three characters onward, the pin renders as a rounded rectangular badge with an arrow pointer centered on the coordinate.
-   **Active state collapsing**: When a pin with a popup is clicked, it collapses into a compact circular pin displaying only its icon, preventing visual overlap with the popup card.

```js
// Circular numbered tour pin
splatModel.addAnnotation({
  pos: { x: -0.06, z: -0.26 },
  label: "1",
  color: "#3174ff",
});

// Icon-only circular pin
splatModel.addAnnotation({
  pos: { x: 0.1, z: 0.2 },
  icon: "🚘",
  color: [77, 77, 77],
});
```

## Color formats and categorization

The `color` property configures the pin background fill color (the white label text and white border remain consistent). It accepts:

-   **Hexadecimal strings**: `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA` (e.g. `"#e69439"`).
-   **RGB arrays**: `[r, g, b]` or `[r, g, b, a]` with components from `0` to `255` (and alpha from `0` to `1`).
-   **RGB objects**: `{ r, g, b, a? }` with numerical values.
-   **Supported named colors**: `"black"`, `"white"`, `"red"`, `"green"`, `"blue"`, `"yellow"`, `"magenta"`, `"cyan"`, and `"transparent"`.

```js
const CATEGORY_COLORS = {
  leisure: "#2bb673",
  dining: "#e69439",
  services: "#3174ff",
};

splatModel.addAnnotation({
  pos: { x: -0.4, z: -0.2 },
  icon: "🏖️",
  label: "Pool",
  color: CATEGORY_COLORS.leisure,
});
```

## Controlling popup placement

Use `popupPlacement` to define an ordered list of preferred sides where the popup can open relative to the pin:

-   Available values: `"top"`, `"bottom"`, `"left"`, and `"right"`.
-   Every frame, the SDK selects the first side with sufficient screen clearance on the map.
-   If none of the sides have sufficient space, the side with the most available room is chosen.

```js
// Prefer opening above or below, falling back to sides
splatModel.addAnnotation({
  pos: { x: -0.6, z: 0.5 },
  label: "Golf Course",
  popup: "<h3>Golf Course</h3><p>Championship green.</p>",
  popupPlacement: ["top", "bottom", "right", "left"],
});
```

## Learn more

Check out the [MapTiler GeoSplats SDK reference](https://docs.maptiler.com/geosplats-sdk/api) for full documentation on `AnnotationOptions`, `AnnotationPopupPlacement`, and `Color`.

## Related examples

### [Add annotations](https://docs.maptiler.com/geosplats-sdk/examples/add-annotations/)

Examples

Add interactive labelled pins and popups to a splat model.

### [Model vs geographic positioning](https://docs.maptiler.com/geosplats-sdk/examples/annotation-positioning/)

Examples

Place annotations using model-relative bounding box coordinates or geographic coordinates.

### [Rich HTML popups](https://docs.maptiler.com/geosplats-sdk/examples/rich-popup-annotations/)

Examples

Display custom HTML cards in annotation popups with images, tables, and custom widths.

### [Capture events](https://docs.maptiler.com/geosplats-sdk/examples/events/)

Examples

Learn how to capture both global map events and specific splats events like load, update, mouseover, and click.
