diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx
index 3b10d831e..501944158 100644
--- a/apps/client/src/widgets/collections/NoteList.tsx
+++ b/apps/client/src/widgets/collections/NoteList.tsx
@@ -4,6 +4,7 @@ import FNote from "../../entities/fnote";
 import "./NoteList.css";
 import { ListView, GridView } from "./legacy/ListOrGridView";
 import { useEffect, useRef, useState } from "preact/hooks";
+import GeoView from "./geomap";
 
 interface NoteListProps {
     note?: FNote | null;
@@ -67,6 +68,8 @@ function getComponentByViewType(note: FNote, noteIds: string[], viewType: ViewTy
             return ;
         case "grid":
             return ;
+        case "geoMap":
+            return ;
     }
 }
 
diff --git a/apps/client/src/widgets/collections/geomap/index.css b/apps/client/src/widgets/collections/geomap/index.css
new file mode 100644
index 000000000..668962ff1
--- /dev/null
+++ b/apps/client/src/widgets/collections/geomap/index.css
@@ -0,0 +1,74 @@
+.geo-view {
+  overflow: hidden;
+  position: relative;
+  height: 100%;
+}
+
+.geo-map-container {
+  height: 100%;
+  overflow: hidden;
+}
+
+.leaflet-pane {
+  z-index: 1;
+}
+
+.leaflet-top,
+.leaflet-bottom {
+  z-index: 997;
+}
+
+.geo-map-container.placing-note {
+  cursor: crosshair;
+}
+
+.geo-map-container .marker-pin {
+  position: relative;
+}
+
+.geo-map-container .leaflet-div-icon {
+  position: relative;
+  background: transparent;
+  border: 0;
+  overflow: visible;
+}
+
+.geo-map-container .leaflet-div-icon .icon-shadow {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: -1;
+}
+
+.geo-map-container .leaflet-div-icon .bx {
+  position: absolute;
+  top: 3px;
+  left: 2px;
+  background-color: white;
+  color: black;
+  padding: 2px;
+  border-radius: 50%;
+  font-size: 17px;
+}
+
+.geo-map-container .leaflet-div-icon .title-label {
+  display: block;
+  position: absolute;
+  top: 100%;
+  left: 50%;
+  transform: translateX(-50%);
+  font-size: 0.75rem;
+  height: 1rem;
+  color: black;
+  width: 100px;
+  text-align: center;
+  text-overflow: ellipsis;
+  text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
+  white-space: no-wrap;
+  overflow: hidden;
+}
+
+.geo-map-container.dark .leaflet-div-icon .title-label {
+  color: white;
+  text-shadow: -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black, 1px 1px 0 black;
+}
\ No newline at end of file
diff --git a/apps/client/src/widgets/collections/geomap/index.tsx b/apps/client/src/widgets/collections/geomap/index.tsx
new file mode 100644
index 000000000..c116ebd7a
--- /dev/null
+++ b/apps/client/src/widgets/collections/geomap/index.tsx
@@ -0,0 +1,16 @@
+import Map from "./map";
+import "./index.css";
+
+const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
+const DEFAULT_ZOOM = 2;
+
+export default function GeoView() {
+    return (
+        
+            
+        
+    );
+}
diff --git a/apps/client/src/widgets/collections/geomap/map.tsx b/apps/client/src/widgets/collections/geomap/map.tsx
new file mode 100644
index 000000000..8a3dd97b1
--- /dev/null
+++ b/apps/client/src/widgets/collections/geomap/map.tsx
@@ -0,0 +1,70 @@
+import { useEffect, useRef, useState } from "preact/hooks";
+import L, { LatLng, Layer } from "leaflet";
+import "leaflet/dist/leaflet.css";
+import { useNoteContext, useNoteLabel } from "../../react/hooks";
+import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS } from "./map_layer";
+
+interface MapProps {
+    coordinates: LatLng | [number, number];
+    zoom: number;
+}
+
+export default function Map({ coordinates, zoom }: MapProps) {
+    const mapRef = useRef(null);
+    const containerRef = useRef(null);
+    const { note } = useNoteContext();
+    const [ layerName ] = useNoteLabel(note, "map:style");
+
+    useEffect(() => {
+        if (!containerRef.current) return;
+        mapRef.current = L.map(containerRef.current, {
+            worldCopyJump: true
+        });
+    }, []);
+
+    // Load the layer asynchronously.
+    const [ layer, setLayer ] = useState();
+    useEffect(() => {
+        async function load() {
+            const layerData = MAP_LAYERS[layerName ?? DEFAULT_MAP_LAYER_NAME];
+
+            if (layerData.type === "vector") {
+                const style = (typeof layerData.style === "string" ? layerData.style : await layerData.style());
+                await import("@maplibre/maplibre-gl-leaflet");
+
+                setLayer(L.maplibreGL({
+                    style: style as any
+                }));
+            } else {
+                setLayer(L.tileLayer(layerData.url, {
+                    attribution: layerData.attribution,
+                    detectRetina: true
+                }));
+            }
+        }
+
+        load();
+    }, [ layerName ]);
+
+    // Attach layer to the map.
+    useEffect(() => {
+        const map = mapRef.current;
+        const layerToAdd = layer;
+        console.log("Add layer ", map, layerToAdd);
+        if (!map || !layerToAdd) return;
+        layerToAdd.addTo(map);
+        return () => layerToAdd.removeFrom(map);
+    }, [ mapRef, layer ]);
+
+    // React to coordinate changes.
+    useEffect(() => {
+        if (!mapRef.current) return;
+        mapRef.current.setView(coordinates, zoom);
+    }, [ mapRef, coordinates, zoom ]);
+
+    return (
+        
+
+        
+    );
+}
diff --git a/apps/client/src/widgets/view_widgets/geo_view/map_layer.ts b/apps/client/src/widgets/collections/geomap/map_layer.ts
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/map_layer.ts
rename to apps/client/src/widgets/collections/geomap/map_layer.ts
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/colorful/de.json b/apps/client/src/widgets/collections/geomap/styles/colorful/de.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/colorful/de.json
rename to apps/client/src/widgets/collections/geomap/styles/colorful/de.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/colorful/en.json b/apps/client/src/widgets/collections/geomap/styles/colorful/en.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/colorful/en.json
rename to apps/client/src/widgets/collections/geomap/styles/colorful/en.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/colorful/nolabel.json b/apps/client/src/widgets/collections/geomap/styles/colorful/nolabel.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/colorful/nolabel.json
rename to apps/client/src/widgets/collections/geomap/styles/colorful/nolabel.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/colorful/style.json b/apps/client/src/widgets/collections/geomap/styles/colorful/style.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/colorful/style.json
rename to apps/client/src/widgets/collections/geomap/styles/colorful/style.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/eclipse/de.json b/apps/client/src/widgets/collections/geomap/styles/eclipse/de.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/eclipse/de.json
rename to apps/client/src/widgets/collections/geomap/styles/eclipse/de.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/eclipse/en.json b/apps/client/src/widgets/collections/geomap/styles/eclipse/en.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/eclipse/en.json
rename to apps/client/src/widgets/collections/geomap/styles/eclipse/en.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/eclipse/nolabel.json b/apps/client/src/widgets/collections/geomap/styles/eclipse/nolabel.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/eclipse/nolabel.json
rename to apps/client/src/widgets/collections/geomap/styles/eclipse/nolabel.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/eclipse/style.json b/apps/client/src/widgets/collections/geomap/styles/eclipse/style.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/eclipse/style.json
rename to apps/client/src/widgets/collections/geomap/styles/eclipse/style.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/graybeard/de.json b/apps/client/src/widgets/collections/geomap/styles/graybeard/de.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/graybeard/de.json
rename to apps/client/src/widgets/collections/geomap/styles/graybeard/de.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/graybeard/en.json b/apps/client/src/widgets/collections/geomap/styles/graybeard/en.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/graybeard/en.json
rename to apps/client/src/widgets/collections/geomap/styles/graybeard/en.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/graybeard/nolabel.json b/apps/client/src/widgets/collections/geomap/styles/graybeard/nolabel.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/graybeard/nolabel.json
rename to apps/client/src/widgets/collections/geomap/styles/graybeard/nolabel.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/graybeard/style.json b/apps/client/src/widgets/collections/geomap/styles/graybeard/style.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/graybeard/style.json
rename to apps/client/src/widgets/collections/geomap/styles/graybeard/style.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/neutrino/de.json b/apps/client/src/widgets/collections/geomap/styles/neutrino/de.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/neutrino/de.json
rename to apps/client/src/widgets/collections/geomap/styles/neutrino/de.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/neutrino/en.json b/apps/client/src/widgets/collections/geomap/styles/neutrino/en.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/neutrino/en.json
rename to apps/client/src/widgets/collections/geomap/styles/neutrino/en.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/neutrino/nolabel.json b/apps/client/src/widgets/collections/geomap/styles/neutrino/nolabel.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/neutrino/nolabel.json
rename to apps/client/src/widgets/collections/geomap/styles/neutrino/nolabel.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/neutrino/style.json b/apps/client/src/widgets/collections/geomap/styles/neutrino/style.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/neutrino/style.json
rename to apps/client/src/widgets/collections/geomap/styles/neutrino/style.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/shadow/de.json b/apps/client/src/widgets/collections/geomap/styles/shadow/de.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/shadow/de.json
rename to apps/client/src/widgets/collections/geomap/styles/shadow/de.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/shadow/en.json b/apps/client/src/widgets/collections/geomap/styles/shadow/en.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/shadow/en.json
rename to apps/client/src/widgets/collections/geomap/styles/shadow/en.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/shadow/nolabel.json b/apps/client/src/widgets/collections/geomap/styles/shadow/nolabel.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/shadow/nolabel.json
rename to apps/client/src/widgets/collections/geomap/styles/shadow/nolabel.json
diff --git a/apps/client/src/widgets/view_widgets/geo_view/styles/shadow/style.json b/apps/client/src/widgets/collections/geomap/styles/shadow/style.json
similarity index 100%
rename from apps/client/src/widgets/view_widgets/geo_view/styles/shadow/style.json
rename to apps/client/src/widgets/collections/geomap/styles/shadow/style.json
diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.ts b/apps/client/src/widgets/ribbon/collection-properties-config.ts
index 6a0c74d04..d53513a43 100644
--- a/apps/client/src/widgets/ribbon/collection-properties-config.ts
+++ b/apps/client/src/widgets/ribbon/collection-properties-config.ts
@@ -2,7 +2,7 @@ import { t } from "i18next";
 import FNote from "../../entities/fnote";
 import attributes from "../../services/attributes";
 import NoteContextAwareWidget from "../note_context_aware_widget";
-import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, type MapLayer } from "../view_widgets/geo_view/map_layer";
+import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, type MapLayer } from "../collections/geomap/map_layer";
 import { ViewTypeOptions } from "../collections/interface";
 
 interface BookConfig {
diff --git a/apps/client/src/widgets/view_widgets/geo_view/index.ts b/apps/client/src/widgets/view_widgets/geo_view/index.ts
index 9b194f9df..845df3813 100644
--- a/apps/client/src/widgets/view_widgets/geo_view/index.ts
+++ b/apps/client/src/widgets/view_widgets/geo_view/index.ts
@@ -1,7 +1,6 @@
 import ViewMode, { ViewModeArgs } from "../view_mode.js";
 import L from "leaflet";
 import type { GPX, LatLng, Layer, LeafletMouseEvent, Map, Marker } from "leaflet";
-import "leaflet/dist/leaflet.css";
 import SpacedUpdate from "../../../services/spaced_update.js";
 import { t } from "../../../services/i18n.js";
 import processNoteWithMarker, { processNoteWithGpxTrack } from "./markers.js";
@@ -13,88 +12,6 @@ import { openMapContextMenu } from "./context_menu.js";
 import attributes from "../../../services/attributes.js";
 import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS } from "./map_layer.js";
 
-const TPL = /*html*/`
-`;
-
 interface MapData {
     view?: {
         center?: LatLng | [number, number];
@@ -102,8 +19,6 @@ interface MapData {
     };
 }
 
-const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
-const DEFAULT_ZOOM = 2;
 export const LOCATION_ATTRIBUTE = "geolocation";
 
 enum State {
@@ -142,27 +57,8 @@ export default class GeoView extends ViewMode {
     }
 
     async renderMap() {
-        const map = L.map(this.$container[0], {
-            worldCopyJump: true
-        });
+        const layerName = this.parentNote.getLabelValue("map:style") ?? ;
 
-        const layerName = this.parentNote.getLabelValue("map:style") ?? DEFAULT_MAP_LAYER_NAME;
-        let layer: Layer;
-        const layerData = MAP_LAYERS[layerName];
-
-        if (layerData.type === "vector") {
-            const style = (typeof layerData.style === "string" ? layerData.style : await layerData.style());
-            await import("@maplibre/maplibre-gl-leaflet");
-
-            layer = L.maplibreGL({
-                style: style as any
-            });
-        } else {
-            layer = L.tileLayer(layerData.url, {
-                attribution: layerData.attribution,
-                detectRetina: true
-            });
-        }
 
         if (this.parentNote.hasLabel("map:scale")) {
             L.control.scale().addTo(map);
@@ -220,7 +116,6 @@ export default class GeoView extends ViewMode {
         // Restore viewport position & zoom
         const center = parsedContent?.view?.center ?? DEFAULT_COORDINATES;
         const zoom = parsedContent?.view?.zoom ?? DEFAULT_ZOOM;
-        map.setView(center, zoom);
     }
 
     private onSave() {