<ScriptGoogleMapsOverlayView>
Renders arbitrary Vue slot content at a map lat/lng position. Unlike InfoWindow, you have full control over HTML structure and styling.
v-model:openbooleanMarker Anchoring
When nested inside a ScriptGoogleMapsMarker, the overlay automatically inherits the marker's position. This makes it a fully customizable alternative to InfoWindow, the overlay follows the marker when 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">
Fully custom content, no InfoWindow constraints
</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>
<!-- Starts closed; toggle later via a template ref or marker click -->
<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 behaviour).
<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.
Popup on Marker Click
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 overlay opens, the map automatically pans so the overlay is fully visible, matching the native InfoWindow behavior. The default padding is 40px from the map edge.
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>
Transitions
The overlay sets a data-state attribute ("open" or "closed") on its content element, enabling CSS transitions and animations for enter/leave effects. When closing, the component waits for any running transitionend or animationend event before hiding the element.
Access the dataState via a template ref (it is exposed via defineExpose, not as a slot prop).
CSS Animation
The data-state attribute is automatically set on the overlay content element, so you can target it directly in CSS without needing to bind it manually.
<ScriptGoogleMapsOverlayView v-model:open="open">
<div class="popup">
Animated popup
</div>
</ScriptGoogleMapsOverlayView>
<style>
.popup[data-state="open"] {
animation: fadeIn 200ms ease-out;
}
.popup[data-state="closed"] {
animation: fadeOut 150ms ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes fadeOut {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.95); }
}
</style>
CSS Transition
<script setup lang="ts">
const overlayRef = ref()
</script>
<template>
<ScriptGoogleMapsOverlayView ref="overlayRef" v-model:open="open">
<div
class="popup"
:style="{
opacity: overlayRef?.dataState === 'open' ? 1 : 0,
transition: 'opacity 200ms ease',
}"
>
Fade transition
</div>
</ScriptGoogleMapsOverlayView>
</template>
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.<ScriptGoogleMapsHeatmapLayer>
Heatmap visualization layer. Place inside a <ScriptGoogleMaps> component. The component automatically loads the visualization library.
useScriptGoogleMaps()
The useScriptGoogleMaps() composable lets you have fine-grained control over the Google Maps SDK. It provides a way to load the Google Maps SDK and interact with it programmatically.