Skip to main content
Api

<ScriptGoogleMapsOverlayView>

Renders Vue slot content at a map latitude and longitude using Google's OverlayView. Unlike InfoWindow, it leaves the HTML structure and styling to you.

v-model:openboolean

Marker Anchoring

When nested inside a ScriptGoogleMapsMarker, the overlay inherits the marker's position and follows it while it is dragged.

<template>
  <ScriptGoogleMaps api-key="your-api-key">
    <ScriptGoogleMapsMarker
      :position="{ lat: -34.397, lng: 150.644 }"
      :options="{ gmpDraggable: true }"
    >
      <ScriptGoogleMapsOverlayView
        anchor="bottom-center"
        :offset="{ x: 0, y: -50 }"
      >
        <div class="custom-tooltip">
          Custom Vue content
        </div>
      </ScriptGoogleMapsOverlayView>
    </ScriptGoogleMapsMarker>
  </ScriptGoogleMaps>
</template>

Controlled vs Uncontrolled Open State

The overlay supports two open-state patterns: uncontrolled (component-managed) and controlled (parent-managed via v-model:open).

Uncontrolled. The component owns its open state. The overlay opens by default; pass :default-open="false" to start closed. This is the simplest pattern when you don't need to react to state changes from the parent.

<template>
  <!-- Opens immediately on mount (default) -->
  <ScriptGoogleMapsOverlayView :position="{ lat: -34.397, lng: 150.644 }">
    <div>Always-open label</div>
  </ScriptGoogleMapsOverlayView>

  <!-- Remains closed; use v-model:open when it needs to toggle -->
  <ScriptGoogleMapsOverlayView
    :position="{ lat: -34.397, lng: 150.644 }"
    :default-open="false"
  >
    <div>Initially hidden</div>
  </ScriptGoogleMapsOverlayView>
</template>

Controlled. Bind v-model:open to a parent ref. The parent owns the state and the overlay reflects it. The overlay updates the bound ref when something internal flips it (for example, the marker-cluster auto-hide behavior).

<script setup lang="ts">
const open = ref(true)
</script>

<template>
  <ScriptGoogleMapsOverlayView v-model:open="open" :position="{ lat: -34.397, lng: 150.644 }">
    <div>Controlled by parent</div>
  </ScriptGoogleMapsOverlayView>
</template>

When v-model:open is bound, defaultOpen has no effect; pass an initial value to the bound ref instead.

An uncontrolled overlay that starts with defaultOpen: false has no public method for opening it later. Bind v-model:open for any overlay that needs to toggle.

Using v-model:open keeps the overlay mounted, toggling visibility via CSS. This avoids remount cost and preserves internal state.

<script setup lang="ts">
const open = ref(false)
</script>

<template>
  <ScriptGoogleMaps api-key="your-api-key">
    <ScriptGoogleMapsMarker
      :position="{ lat: -34.397, lng: 150.644 }"
      @click="open = !open"
    >
      <ScriptGoogleMapsOverlayView
        v-model:open="open"
        anchor="bottom-center"
        :offset="{ x: 0, y: -50 }"
      >
        <div class="custom-popup">
          <button @click.stop="open = false">
            ×
          </button>
          <p>Any Vue content here</p>
        </div>
      </ScriptGoogleMapsOverlayView>
    </ScriptGoogleMapsMarker>
  </ScriptGoogleMaps>
</template>

For simple cases where remounting is acceptable, v-if also works:

<ScriptGoogleMapsMarker
  :position="{ lat: -34.397, lng: 150.644 }"
  @click="open = true"
>
  <ScriptGoogleMapsOverlayView v-if="open">
    <MyPopup @close="open = false" />
  </ScriptGoogleMapsOverlayView>
</ScriptGoogleMapsMarker>

Persistent Label

<template>
  <ScriptGoogleMaps api-key="your-api-key">
    <ScriptGoogleMapsOverlayView
      :position="{ lat: -34.397, lng: 150.644 }"
      anchor="center"
      :block-map-interaction="false"
    >
      <span class="bg-white px-1.5 py-0.5 rounded">
        Label text
      </span>
    </ScriptGoogleMapsOverlayView>
  </ScriptGoogleMaps>
</template>

Position Format

The position prop accepts either a plain LatLngLiteral ({ lat, lng }) or a google.maps.LatLng instance, so you can pass values straight from the Maps API without converting them first.

<script setup lang="ts">
const mapRef = ref()

async function showSydney() {
  // Resolve a query into a LatLng-shaped value via the Maps API
  const sydney = await mapRef.value?.resolveQueryToLatLng('Sydney, Australia')
  // Pass it through unchanged: works for both shapes
  position.value = sydney
}

const position = ref()
</script>

<template>
  <ScriptGoogleMaps ref="mapRef" api-key="your-api-key">
    <ScriptGoogleMapsOverlayView v-if="position" :position="position">
      <div>Resolved position</div>
    </ScriptGoogleMapsOverlayView>
  </ScriptGoogleMaps>
</template>

Map Panning

When an already-open overlay first attaches to the map, it pans into view with 40px of padding. Toggling a mounted overlay from closed to open does not currently run the pan step again.

To customize the padding or disable panning:

<!-- Custom padding -->
<ScriptGoogleMapsOverlayView :pan-on-open="60">
  ...
</ScriptGoogleMapsOverlayView>

<!-- Disable panning -->
<ScriptGoogleMapsOverlayView :pan-on-open="false">
  ...
</ScriptGoogleMapsOverlayView>

Cluster Awareness

When used inside a ScriptGoogleMapsMarkerClusterer, overlay views automatically hide when their parent marker joins a cluster on zoom out. This prevents orphaned overlays from floating over cluster icons.

When its marker is clustered, the overlay updates v-model:open to false. The user will need to reopen the overlay (e.g. click the marker again) after zooming back in.

To disable this behavior:

<ScriptGoogleMapsMarkerClusterer>
  <ScriptGoogleMapsMarker :position="markerPosition">
    <ScriptGoogleMapsOverlayView :hide-when-clustered="false">
      ...
    </ScriptGoogleMapsOverlayView>
  </ScriptGoogleMapsMarker>
</ScriptGoogleMapsMarkerClusterer>

Open-state animation

The overlay sets data-state="open" on its content wrapper while visible. Pass a class to the component to target that wrapper and animate its entrance:

<ScriptGoogleMapsOverlayView v-model:open="open" class="popup">
  <div>
    Animated popup
  </div>
</ScriptGoogleMapsOverlayView>

<style>
.popup[data-state="open"] {
  animation: fadeIn 200ms ease-out;
}
@keyframes fadeIn {
  from { opacity: 0; transform: scale(0.95); }
  to { opacity: 1; transform: scale(1); }
}
</style>

Closing sets the anchor to visibility: hidden immediately; the component does not wait for transitionend or animationend, so CSS exit animations are not visible. A template ref exposes dataState for code that needs the current state, but it is not a slot prop.

The blockMapInteraction prop (default true) calls google.maps.OverlayView.preventMapHitsAndGesturesFrom() to stop clicks, taps, and drags from propagating through the overlay to the map. Set it to false for non-interactive overlays like labels.