Repository: symfony/ux
Pull request #2845 (2025-06-17 11:14:10)

[Map] Add Rectangle support
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Docs?         | yes
| Issues        |
| License       | MIT

Adding a new `Rectangle` element to Map following GoogleMaps and Leaflet features.

<img width="904" alt="Capture d’écran 2025-06-17 à 13 43 36" src="https://github.com/user-attachments/assets/31455146-0b5e-4baf-9d68-a0a3cd567172" />
<img width="520" alt="Capture d’écran 2025-06-17 à 13 44 23" src="https://github.com/user-attachments/assets/f72bbb5e-0374-4506-9697-657e6b463af7" />



github-actions[bot]: <h1>📊 Packages dist files size difference</h1>

Thanks for the PR! Here is the difference in size of the packages dist files between the base branch and the PR.
Please review the changes and make sure they are expected.

<table>
    <thead><tr><th>File</th><th>Before (Size / Gzip)</th><th>After (Size / Gzip)</th></tr></thead>
    <tbody><tr><td colspan="3"><a href="https://github.com/Valmonzo/ux/tree/feat/add-rectangle-support-for-map/src/Map/assets/dist"><b>Map</b></a></td></tr><tr>
                <td><a href="https://github.com/Valmonzo/ux/blob/feat/add-rectangle-support-for-map/src/Map/assets/dist/abstract_map_controller.d.ts"><code>abstract_map_controller.d.ts</code></a></td>
            <td>
                    <code>5.75 kB</code>
                    / <code>1.25 kB</code> 
                </td><td>
                    <code>6.62 kB</code><sup>+15% 📈</sup>
                    / <code>1.36 kB</code><sup>+9% 📈</sup>
                </td></tr><tr>
                <td><a href="https://github.com/Valmonzo/ux/blob/feat/add-rectangle-support-for-map/src/Map/assets/dist/abstract_map_controller.js"><code>abstract_map_controller.js</code></a></td>
            <td>
                    <code>4.48 kB</code>
                    / <code>1.14 kB</code> 
                </td><td>
                    <code>5.01 kB</code><sup>+12% 📈</sup>
                    / <code>1.2 kB</code><sup>+6% 📈</sup>
                </td></tr><tr><td colspan="3"><a href="https://github.com/Valmonzo/ux/tree/feat/add-rectangle-support-for-map/src/Map/src/Bridge/Google/assets/dist"><b>Map (Bridge Google)</b></a></td></tr><tr>
                <td><a href="https://github.com/Valmonzo/ux/blob/feat/add-rectangle-support-for-map/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts"><code>map_controller.d.ts</code></a></td>
            <td>
                    <code>3.03 kB</code>
                    / <code>866 B</code> 
                </td><td>
                    <code>3.35 kB</code><sup>+11% 📈</sup>
                    / <code>915 B</code><sup>+6% 📈</sup>
                </td></tr><tr>
                <td><a href="https://github.com/Valmonzo/ux/blob/feat/add-rectangle-support-for-map/src/Map/src/Bridge/Google/assets/dist/map_controller.js"><code>map_controller.js</code></a></td>
            <td>
                    <code>12.22 kB</code>
                    / <code>2.65 kB</code> 
                </td><td>
                    <code>13.38 kB</code><sup>+9% 📈</sup>
                    / <code>2.81 kB</code><sup>+6% 📈</sup>
                </td></tr><tr><td colspan="3"><a href="https://github.com/Valmonzo/ux/tree/feat/add-rectangle-support-for-map/src/Map/src/Bridge/Leaflet/assets/dist"><b>Map (Bridge Leaflet)</b></a></td></tr><tr>
                <td><a href="https://github.com/Valmonzo/ux/blob/feat/add-rectangle-support-for-map/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts"><code>map_controller.d.ts</code></a></td>
            <td>
                    <code>2.63 kB</code>
                    / <code>781 B</code> 
                </td><td>
                    <code>2.93 kB</code><sup>+12% 📈</sup>
                    / <code>829 B</code><sup>+6% 📈</sup>
                </td></tr><tr>
                <td><a href="https://github.com/Valmonzo/ux/blob/feat/add-rectangle-support-for-map/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js"><code>map_controller.js</code></a></td>
            <td>
                    <code>11.06 kB</code>
                    / <code>2.77 kB</code> 
                </td><td>
                    <code>12.19 kB</code><sup>+10% 📈</sup>
                    / <code>2.92 kB</code><sup>+6% 📈</sup>
                </td></tr></tbody>
</table>

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/src/Map/CHANGELOG.md b/src/Map/CHANGELOG.md
index 4ffff79f..d5be76af 100644
--- a/src/Map/CHANGELOG.md
+++ b/src/Map/CHANGELOG.md
@@ -1,5 +1,15 @@
 # CHANGELOG
 
+## 2.29
+
+-   Add support for creating `Rectangle` by passing two `Point` instances to the `Rectangle` constructor, e.g.:
+```php
+$map->addRectangle(new Rectangle(
+    southWest: new Point(48.856613, 2.352222), // Paris
+    northEast: new Point(48.51238 2.21080) // Gare de Lyon (Paris)
+));
+```
+
 ## 2.28
 
 -   Add support for creating `Circle` by passing a `Point` and a radius (in meters) to the `Circle` constructor, e.g.:
diff --git a/src/Map/assets/dist/abstract_map_controller.d.ts b/src/Map/assets/dist/abstract_map_controller.d.ts
index f96f2e27..67d5c70c 100644
--- a/src/Map/assets/dist/abstract_map_controller.d.ts
+++ b/src/Map/assets/dist/abstract_map_controller.d.ts
@@ -56,6 +56,14 @@ export type CircleDefinition<CircleOptions, InfoWindowOptions> = WithIdentifier<
     rawOptions?: CircleOptions;
     extra: Record<string, unknown>;
 }>;
+export type RectangleDefinition<RectangleOptions, InfoWindowOptions> = WithIdentifier<{
+    infoWindow?: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
+    southWest: Point;
+    northEast: Point;
+    title: string | null;
+    rawOptions?: RectangleOptions;
+    extra: Record<string, unknown>;
+}>;
 export type InfoWindowDefinition<InfoWindowOptions> = {
     headerContent: string | null;
     content: string | null;
@@ -66,7 +74,7 @@ export type InfoWindowDefinition<InfoWindowOptions> = {
     extra: Record<string, unknown>;
 };
 export type InfoWindowWithoutPositionDefinition<InfoWindowOptions> = Omit<InfoWindowDefinition<InfoWindowOptions>, 'position'>;
-export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon, PolylineOptions, Polyline, CircleOptions, Circle> extends Controller<HTMLElement> {
+export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindowOptions, InfoWindow, PolygonOptions, Polygon, PolylineOptions, Polyline, CircleOptions, Circle, RectangleOptions, Rectangle> extends Controller<HTMLElement> {
     static values: {
         providerOptions: ObjectConstructor;
         center: ObjectConstructor;
@@ -76,6 +84,7 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
         polygons: ArrayConstructor;
         polylines: ArrayConstructor;
         circles: ArrayConstructor;
+        rectangles: ArrayConstructor;
         options: ObjectConstructor;
     };
     centerValue: Point | null;
@@ -85,6 +94,7 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
     polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
     polylinesValue: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
     circlesValue: Array<CircleDefinition<CircleOptions, InfoWindowOptions>>;
+    rectanglesValue: Array<RectangleDefinition<RectangleOptions, InfoWindowOptions>>;
     optionsValue: MapOptions;
     hasCenterValue: boolean;
     hasZoomValue: boolean;
@@ -93,23 +103,26 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
     hasPolygonsValue: boolean;
     hasPolylinesValue: boolean;
     hasCirclesValue: boolean;
+    hasRectanglesValue: boolean;
     hasOptionsValue: boolean;
     protected map: Map;
     protected markers: globalThis.Map<string, Marker>;
     protected polygons: globalThis.Map<string, Polygon>;
     protected polylines: globalThis.Map<string, Polyline>;
     protected circles: globalThis.Map<string, Circle>;
+    protected rectangles: globalThis.Map<string, Rectangle>;
     protected infoWindows: Array<InfoWindow>;
     private isConnected;
     private createMarker;
     private createPolygon;
     private createPolyline;
     private createCircle;
+    private createRectangle;
     protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;
     connect(): void;
     createInfoWindow({ definition, element, }: {
         definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
-        element: Marker | Polygon | Polyline | Circle;
+        element: Marker | Polygon | Polyline | Circle | Rectangle;
     }): InfoWindow;
     abstract centerValueChanged(): void;
     abstract zoomValueChanged(): void;
@@ -117,6 +130,7 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
     polygonsValueChanged(): void;
     polylinesValueChanged(): void;
     circlesValueChanged(): void;
+    rectanglesValueChanged(): void;
     protected abstract doCreateMap({ center, zoom, options, }: {
         center: Point | null;
         zoom: number | null;
@@ -139,9 +153,13 @@ export default abstract class<MapOptions, Map, MarkerOptions, Marker, InfoWindow
         definition: CircleDefinition<CircleOptions, InfoWindowOptions>;
     }): Circle;
     protected abstract doRemoveCircle(circle: Circle): void;
+    protected abstract doCreateRectangle({ definition, }: {
+        definition: RectangleDefinition<RectangleOptions, InfoWindowOptions>;
+    }): Rectangle;
+    protected abstract doRemoveRectangle(rectangle: Rectangle): void;
     protected abstract doCreateInfoWindow({ definition, element, }: {
         definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
-        element: Marker | Polygon | Polyline | Circle;
+        element: Marker | Polygon | Polyline | Circle | Rectangle;
     }): InfoWindow;
     protected abstract doCreateIcon({ definition, element, }: {
         definition: Icon;
diff --git a/src/Map/assets/dist/abstract_map_controller.js b/src/Map/assets/dist/abstract_map_controller.js
index 1fd266e0..446fa7c7 100644
--- a/src/Map/assets/dist/abstract_map_controller.js
+++ b/src/Map/assets/dist/abstract_map_controller.js
@@ -12,6 +12,7 @@ class default_1 extends Controller {
         this.polygons = new Map();
         this.polylines = new Map();
         this.circles = new Map();
+        this.rectangles = new Map();
         this.infoWindows = [];
         this.isConnected = false;
     }
@@ -22,6 +23,7 @@ class default_1 extends Controller {
         this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
         this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
         this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
+        this.createRectangle = this.createDrawingFactory('rectangle', this.rectangles, this.doCreateRectangle.bind(this));
         this.map = this.doCreateMap({
             center: this.hasCenterValue ? this.centerValue : null,
             zoom: this.hasZoomValue ? this.zoomValue : null,
@@ -31,6 +33,7 @@ class default_1 extends Controller {
         this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
         this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
         this.circlesValue.forEach((definition) => this.createCircle({ definition }));
+        this.rectanglesValue.forEach((definition) => this.createRectangle({ definition }));
         if (this.fitBoundsToMarkersValue) {
             this.doFitBoundsToMarkers();
         }
@@ -40,6 +43,7 @@ class default_1 extends Controller {
             polygons: [...this.polygons.values()],
             polylines: [...this.polylines.values()],
             circles: [...this.circles.values()],
+            rectangles: [...this.rectangles.values()],
             infoWindows: this.infoWindows,
         });
         this.isConnected = true;
@@ -78,6 +82,12 @@ class default_1 extends Controller {
         }
         this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
     }
+    rectanglesValueChanged() {
+        if (!this.isConnected) {
+            return;
+        }
+        this.onDrawChanged(this.rectangles, this.rectanglesValue, this.createRectangle, this.doRemoveRectangle);
+    }
     createDrawingFactory(type, draws, factory) {
         const eventBefore = `${type}:before-create`;
         const eventAfter = `${type}:after-create`;
@@ -115,6 +125,7 @@ default_1.values = {
     polygons: Array,
     polylines: Array,
     circles: Array,
+    rectangles: Array,
     options: Object,
 };
 
diff --git a/src/Map/assets/src/abstract_map_controller.ts b/src/Map/assets/src/abstract_map_controller.ts
index 8b0c5a70..e81e2671 100644
--- a/src/Map/assets/src/abstract_map_controller.ts
+++ b/src/Map/assets/src/abstract_map_controller.ts
@@ -98,6 +98,24 @@ export type CircleDefinition<CircleOptions, InfoWindowOptions> = WithIdentifier<
     extra: Record<string, unknown>;
 }>;
 
+export type RectangleDefinition<RectangleOptions, InfoWindowOptions> = WithIdentifier<{
+    infoWindow?: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
+    southWest: Point;
+    northEast: Point;
+    title: string | null;
+    /**
+     * Raw options passed to the rectangle constructor, specific to the map provider (e.g.: `L.rectangle()` for Leaflet).
+     */
+    rawOptions?: RectangleOptions;
+    /**
+     * Extra data defined by the developer.
+     * They are not directly used by the Stimulus controller, but they can be used by the developer with event listeners:
+     *    - `ux:map:rectangle:before-create`
+     *    - `ux:map:rectangle:after-create`
+     */
+    extra: Record<string, unknown>;
+}>;
+
 export type InfoWindowDefinition<InfoWindowOptions> = {
     headerContent: string | null;
     content: string | null;
@@ -136,6 +154,8 @@ export default abstract class<
     Polyline,
     CircleOptions,
     Circle,
+    RectangleOptions,
+    Rectangle,
 > extends Controller<HTMLElement> {
     static values = {
         providerOptions: Object,
@@ -146,6 +166,7 @@ export default abstract class<
         polygons: Array,
         polylines: Array,
         circles: Array,
+        rectangles: Array,
         options: Object,
     };
 
@@ -156,6 +177,7 @@ export default abstract class<
     declare polygonsValue: Array<PolygonDefinition<PolygonOptions, InfoWindowOptions>>;
     declare polylinesValue: Array<PolylineDefinition<PolylineOptions, InfoWindowOptions>>;
     declare circlesValue: Array<CircleDefinition<CircleOptions, InfoWindowOptions>>;
+    declare rectanglesValue: Array<RectangleDefinition<RectangleOptions, InfoWindowOptions>>;
     declare optionsValue: MapOptions;
 
     declare hasCenterValue: boolean;
@@ -165,6 +187,7 @@ export default abstract class<
     declare hasPolygonsValue: boolean;
     declare hasPolylinesValue: boolean;
     declare hasCirclesValue: boolean;
+    declare hasRectanglesValue: boolean;
     declare hasOptionsValue: boolean;
 
     protected map: Map;
@@ -172,6 +195,7 @@ export default abstract class<
     protected polygons = new Map<Identifier, Polygon>();
     protected polylines = new Map<Identifier, Polyline>();
     protected circles = new Map<Identifier, Circle>();
+    protected rectangles = new Map<Identifier, Rectangle>();
     protected infoWindows: Array<InfoWindow> = [];
 
     private isConnected = false;
@@ -187,6 +211,9 @@ export default abstract class<
     private createCircle: ({
         definition,
     }: { definition: CircleDefinition<CircleOptions, InfoWindowOptions> }) => Circle;
+    private createRectangle: ({
+        definition,
+    }: { definition: RectangleDefinition<RectangleOptions, InfoWindowOptions> }) => Rectangle;
 
     protected abstract dispatchEvent(name: string, payload: Record<string, unknown>): void;
 
@@ -199,6 +226,11 @@ export default abstract class<
         this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
         this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
         this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
+        this.createRectangle = this.createDrawingFactory(
+            'rectangle',
+            this.rectangles,
+            this.doCreateRectangle.bind(this)
+        );
 
         this.map = this.doCreateMap({
             center: this.hasCenterValue ? this.centerValue : null,
@@ -209,6 +241,7 @@ export default abstract class<
         this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
         this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
         this.circlesValue.forEach((definition) => this.createCircle({ definition }));
+        this.rectanglesValue.forEach((definition) => this.createRectangle({ definition }));
 
         if (this.fitBoundsToMarkersValue) {
             this.doFitBoundsToMarkers();
@@ -220,6 +253,7 @@ export default abstract class<
             polygons: [...this.polygons.values()],
             polylines: [...this.polylines.values()],
             circles: [...this.circles.values()],
+            rectangles: [...this.rectangles.values()],
             infoWindows: this.infoWindows,
         });
 
@@ -232,7 +266,7 @@ export default abstract class<
         element,
     }: {
         definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
-        element: Marker | Polygon | Polyline | Circle;
+        element: Marker | Polygon | Polyline | Circle | Rectangle;
     }): InfoWindow {
         this.dispatchEvent('info-window:before-create', { definition, element });
         const infoWindow = this.doCreateInfoWindow({ definition, element });
@@ -286,6 +320,14 @@ export default abstract class<
         this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
     }
 
+    public rectanglesValueChanged(): void {
+        if (!this.isConnected) {
+            return;
+        }
+
+        this.onDrawChanged(this.rectangles, this.rectanglesValue, this.createRectangle, this.doRemoveRectangle);
+    }
+
     //endregion
 
     //region Abstract factory methods to be implemented by the concrete classes, they are specific to the map provider
@@ -331,12 +373,20 @@ export default abstract class<
 
     protected abstract doRemoveCircle(circle: Circle): void;
 
+    protected abstract doCreateRectangle({
+        definition,
+    }: {
+        definition: RectangleDefinition<RectangleOptions, InfoWindowOptions>;
+    }): Rectangle;
+
+    protected abstract doRemoveRectangle(rectangle: Rectangle): void;
+
     protected abstract doCreateInfoWindow({
         definition,
         element,
     }: {
         definition: InfoWindowWithoutPositionDefinition<InfoWindowOptions>;
-        element: Marker | Polygon | Polyline | Circle;
+        element: Marker | Polygon | Polyline | Circle | Rectangle;
     }): InfoWindow;
     protected abstract doCreateIcon({
         definition,
@@ -369,15 +419,21 @@ export default abstract class<
         draws: typeof this.circles,
         factory: typeof this.doCreateCircle
     ): typeof this.doCreateCircle;
+    private createDrawingFactory(
+        type: 'rectangle',
+        draws: typeof this.rectangles,
+        factory: typeof this.doCreateRectangle
+    ): typeof this.doCreateRectangle;
     private createDrawingFactory<
         Factory extends
             | typeof this.doCreateMarker
             | typeof this.doCreatePolygon
             | typeof this.doCreatePolyline
-            | typeof this.doCreateCircle,
+            | typeof this.doCreateCircle
+            | typeof this.doCreateRectangle,
         Draw extends ReturnType<Factory>,
     >(
-        type: 'marker' | 'polygon' | 'polyline' | 'circle',
+        type: 'marker' | 'polygon' | 'polyline' | 'circle' | 'rectangle',
         draws: globalThis.Map<WithIdentifier<any>, Draw>,
         factory: Factory
     ): Factory {
@@ -421,6 +477,12 @@ export default abstract class<
         factory: typeof this.createCircle,
         remover: typeof this.doRemoveCircle
     ): void;
+    private onDrawChanged(
+        draws: typeof this.rectangles,
+        newDrawDefinitions: typeof this.rectanglesValue,
+        factory: typeof this.createRectangle,
+        remover: typeof this.doRemoveRectangle
+    ): void;
     private onDrawChanged<Draw, DrawDefinition extends WithIdentifier<Record<string, unknown>>>(
         draws: globalThis.Map<WithIdentifier<any>, Draw>,
         newDrawDefinitions: Array<DrawDefinition>,
diff --git a/src/Map/doc/index.rst b/src/Map/doc/index.rst
index 8ed36788..ddbd4ba9 100644
--- a/src/Map/doc/index.rst
+++ b/src/Map/doc/index.rst
@@ -222,6 +222,20 @@ You can add Circles, which represents a circular area defined by a center point
         ),
     ));
 
+Add Rectangles
+~~~~~~~~~~~~~~
+
+You can add Rectangles, which represents a rectangular area defined by two corner points::
+
+    $map->addRectangle(new Rectangle(
+        southWest: new Point(48.8566, 2.3522), // Paris
+        northEast: new Point(50.6292, 3.0573), // Lille
+        title: 'Paris to Lille',
+        infoWindow: new InfoWindow(
+            content: 'A rectangle from Paris to Lille',
+        ),
+    ));
+
 Remove elements from Map
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
index a83ce8eb..4895c2bf 100644
--- a/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
+++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
@@ -1,8 +1,8 @@
 import type { LoaderOptions } from '@googlemaps/js-api-loader';
 import AbstractMapController from '@symfony/ux-map';
-import type { CircleDefinition, Icon, InfoWindowWithoutPositionDefinition, MarkerDefinition, Point, PolygonDefinition, PolylineDefinition } from '@symfony/ux-map';
+import type { CircleDefinition, Icon, InfoWindowWithoutPositionDefinition, MarkerDefinition, Point, PolygonDefinition, PolylineDefinition, RectangleDefinition } from '@symfony/ux-map';
 type MapOptions = Pick<google.maps.MapOptions, 'mapId' | 'gestureHandling' | 'backgroundColor' | 'disableDoubleClickZoom' | 'zoomControl' | 'zoomControlOptions' | 'mapTypeControl' | 'mapTypeControlOptions' | 'streetViewControl' | 'streetViewControlOptions' | 'fullscreenControl' | 'fullscreenControlOptions'>;
-export default class extends AbstractMapController<MapOptions, google.maps.Map, google.maps.marker.AdvancedMarkerElementOptions, google.maps.marker.AdvancedMarkerElement, google.maps.InfoWindowOptions, google.maps.InfoWindow, google.maps.PolygonOptions, google.maps.Polygon, google.maps.PolylineOptions, google.maps.Polyline, google.maps.CircleOptions, google.maps.Circle> {
+export default class extends AbstractMapController<MapOptions, google.maps.Map, google.maps.marker.AdvancedMarkerElementOptions, google.maps.marker.AdvancedMarkerElement, google.maps.InfoWindowOptions, google.maps.InfoWindow, google.maps.PolygonOptions, google.maps.Polygon, google.maps.PolylineOptions, google.maps.Polyline, google.maps.CircleOptions, google.maps.Circle, google.maps.RectangleOptions, google.maps.Rectangle> {
     providerOptionsValue: Pick<LoaderOptions, 'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version' | 'libraries'>;
     map: google.maps.Map;
     parser: DOMParser;
@@ -31,6 +31,10 @@ export default class extends AbstractMapController<MapOptions, google.maps.Map,
         definition: CircleDefinition<google.maps.CircleOptions, google.maps.InfoWindowOptions>;
     }): google.maps.Circle;
     protected doRemoveCircle(circle: google.maps.Circle): void;
+    protected doCreateRectangle({ definition, }: {
+        definition: RectangleDefinition<google.maps.RectangleOptions, google.maps.InfoWindowOptions>;
+    }): google.maps.Rectangle;
+    protected doRemoveRectangle(rectangle: google.maps.Rectangle): void;
     protected doCreateInfoWindow({ definition, element, }: {
         definition: InfoWindowWithoutPositionDefinition<google.maps.InfoWindowOptions>;
         element: google.maps.marker.AdvancedMarkerElement | google.maps.Polygon | google.maps.Polyline;
diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.js b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
index c7878c5c..0675c32d 100644
--- a/src/Map/src/Bridge/Google/assets/dist/map_controller.js
+++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
@@ -13,6 +13,7 @@ class default_1 extends Controller {
         this.polygons = new Map();
         this.polylines = new Map();
         this.circles = new Map();
+        this.rectangles = new Map();
         this.infoWindows = [];
         this.isConnected = false;
     }
@@ -23,6 +24,7 @@ class default_1 extends Controller {
         this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
         this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
         this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
+        this.createRectangle = this.createDrawingFactory('rectangle', this.rectangles, this.doCreateRectangle.bind(this));
         this.map = this.doCreateMap({
             center: this.hasCenterValue ? this.centerValue : null,
             zoom: this.hasZoomValue ? this.zoomValue : null,
@@ -32,6 +34,7 @@ class default_1 extends Controller {
         this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
         this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
         this.circlesValue.forEach((definition) => this.createCircle({ definition }));
+        this.rectanglesValue.forEach((definition) => this.createRectangle({ definition }));
         if (this.fitBoundsToMarkersValue) {
             this.doFitBoundsToMarkers();
         }
@@ -41,6 +44,7 @@ class default_1 extends Controller {
             polygons: [...this.polygons.values()],
             polylines: [...this.polylines.values()],
             circles: [...this.circles.values()],
+            rectangles: [...this.rectangles.values()],
             infoWindows: this.infoWindows,
         });
         this.isConnected = true;
@@ -79,6 +83,12 @@ class default_1 extends Controller {
         }
         this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
     }
+    rectanglesValueChanged() {
+        if (!this.isConnected) {
+            return;
+        }
+        this.onDrawChanged(this.rectangles, this.rectanglesValue, this.createRectangle, this.doRemoveRectangle);
+    }
     createDrawingFactory(type, draws, factory) {
         const eventBefore = `${type}:before-create`;
         const eventAfter = `${type}:after-create`;
@@ -116,6 +126,7 @@ default_1.values = {
     polygons: Array,
     polylines: Array,
     circles: Array,
+    rectangles: Array,
     options: Object,
 };
 
@@ -264,6 +275,24 @@ class map_controller extends default_1 {
     doRemoveCircle(circle) {
         circle.setMap(null);
     }
+    doCreateRectangle({ definition, }) {
+        const { northEast, southWest, title, infoWindow, rawOptions = {} } = definition;
+        const rectangle = new _google.maps.Rectangle({
+            ...rawOptions,
+            bounds: new _google.maps.LatLngBounds(southWest, northEast),
+            map: this.map,
+        });
+        if (title) {
+            rectangle.set('title', title);
+        }
+        if (infoWindow) {
+            this.createInfoWindow({ definition: infoWindow, element: rectangle });
+        }
+        return rectangle;
+    }
+    doRemoveRectangle(rectangle) {
+        rectangle.setMap(null);
+    }
     doCreateInfoWindow({ definition, element, }) {
         const { headerContent, content, extra, rawOptions = {}, ...otherOptions } = definition;
         const infoWindow = new _google.maps.InfoWindow({
diff --git a/src/Map/src/Bridge/Google/assets/src/map_controller.ts b/src/Map/src/Bridge/Google/assets/src/map_controller.ts
index 94cf80bf..5e6b13b0 100644
--- a/src/Map/src/Bridge/Google/assets/src/map_controller.ts
+++ b/src/Map/src/Bridge/Google/assets/src/map_controller.ts
@@ -18,6 +18,7 @@ import type {
     Point,
     PolygonDefinition,
     PolylineDefinition,
+    RectangleDefinition,
 } from '@symfony/ux-map';
 
 type MapOptions = Pick<
@@ -57,7 +58,9 @@ export default class extends AbstractMapController<
     google.maps.PolylineOptions,
     google.maps.Polyline,
     google.maps.CircleOptions,
-    google.maps.Circle
+    google.maps.Circle,
+    google.maps.RectangleOptions,
+    google.maps.Rectangle
 > {
     declare providerOptionsValue: Pick<
         LoaderOptions,
@@ -275,6 +278,34 @@ export default class extends AbstractMapController<
         circle.setMap(null);
     }
 
+    protected doCreateRectangle({
+        definition,
+    }: {
+        definition: RectangleDefinition<google.maps.RectangleOptions, google.maps.InfoWindowOptions>;
+    }): google.maps.Rectangle {
+        const { northEast, southWest, title, infoWindow, rawOptions = {} } = definition;
+
+        const rectangle = new _google.maps.Rectangle({
+            ...rawOptions,
+            bounds: new _google.maps.LatLngBounds(southWest, northEast),
+            map: this.map,
+        });
+
+        if (title) {
+            rectangle.set('title', title);
+        }
+
+        if (infoWindow) {
+            this.createInfoWindow({ definition: infoWindow, element: rectangle });
+        }
+
+        return rectangle;
+    }
+
+    protected doRemoveRectangle(rectangle: google.maps.Rectangle): void {
+        rectangle.setMap(null);
+    }
+
     protected doCreateInfoWindow({
         definition,
         element,
diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
index 4d705693..120df29f 100644
--- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
@@ -1,8 +1,8 @@
 import AbstractMapController from '@symfony/ux-map';
-import type { CircleDefinition, Icon, InfoWindowWithoutPositionDefinition, MarkerDefinition, Point, PolygonDefinition, PolylineDefinition } from '@symfony/ux-map';
+import type { CircleDefinition, Icon, InfoWindowWithoutPositionDefinition, MarkerDefinition, Point, PolygonDefinition, PolylineDefinition, RectangleDefinition } from '@symfony/ux-map';
 import 'leaflet/dist/leaflet.min.css';
 import * as L from 'leaflet';
-import type { CircleOptions, ControlPosition, MapOptions as LeafletMapOptions, MarkerOptions, PolylineOptions as PolygonOptions, PolylineOptions, PopupOptions } from 'leaflet';
+import type { CircleOptions, ControlPosition, MapOptions as LeafletMapOptions, MarkerOptions, PolylineOptions as PolygonOptions, PolylineOptions, PopupOptions, PolylineOptions as RectangleOptions } from 'leaflet';
 type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom' | 'attributionControl' | 'zoomControl'> & {
     attributionControlOptions?: {
         position: ControlPosition;
@@ -21,7 +21,7 @@ type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom' | 'attributionContro
         options: Record<string, unknown>;
     } | false;
 };
-export default class extends AbstractMapController<MapOptions, L.Map, MarkerOptions, L.Marker, PopupOptions, L.Popup, PolygonOptions, L.Polygon, PolylineOptions, L.Polyline, CircleOptions, L.Circle> {
+export default class extends AbstractMapController<MapOptions, L.Map, MarkerOptions, L.Marker, PopupOptions, L.Popup, PolygonOptions, L.Polygon, PolylineOptions, L.Polyline, CircleOptions, L.Circle, RectangleOptions, L.Rectangle> {
     map: L.Map;
     connect(): void;
     centerValueChanged(): void;
@@ -48,9 +48,13 @@ export default class extends AbstractMapController<MapOptions, L.Map, MarkerOpti
         definition: CircleDefinition<CircleOptions, PopupOptions>;
     }): L.Circle;
     protected doRemoveCircle(circle: L.Circle): void;
+    protected doCreateRectangle({ definition, }: {
+        definition: RectangleDefinition<RectangleOptions, PopupOptions>;
+    }): L.Rectangle;
+    protected doRemoveRectangle(rectangle: L.Rectangle): void;
     protected doCreateInfoWindow({ definition, element, }: {
         definition: InfoWindowWithoutPositionDefinition<PopupOptions>;
-        element: L.Marker | L.Polygon | L.Polyline | L.Circle;
+        element: L.Marker | L.Polygon | L.Polyline | L.Circle | L.Rectangle;
     }): L.Popup;
     protected doCreateIcon({ definition, element, }: {
         definition: Icon;
diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
index dff15b2c..eda52997 100644
--- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
+++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
@@ -14,6 +14,7 @@ class default_1 extends Controller {
         this.polygons = new Map();
         this.polylines = new Map();
         this.circles = new Map();
+        this.rectangles = new Map();
         this.infoWindows = [];
         this.isConnected = false;
     }
@@ -24,6 +25,7 @@ class default_1 extends Controller {
         this.createPolygon = this.createDrawingFactory('polygon', this.polygons, this.doCreatePolygon.bind(this));
         this.createPolyline = this.createDrawingFactory('polyline', this.polylines, this.doCreatePolyline.bind(this));
         this.createCircle = this.createDrawingFactory('circle', this.circles, this.doCreateCircle.bind(this));
+        this.createRectangle = this.createDrawingFactory('rectangle', this.rectangles, this.doCreateRectangle.bind(this));
         this.map = this.doCreateMap({
             center: this.hasCenterValue ? this.centerValue : null,
             zoom: this.hasZoomValue ? this.zoomValue : null,
@@ -33,6 +35,7 @@ class default_1 extends Controller {
         this.polygonsValue.forEach((definition) => this.createPolygon({ definition }));
         this.polylinesValue.forEach((definition) => this.createPolyline({ definition }));
         this.circlesValue.forEach((definition) => this.createCircle({ definition }));
+        this.rectanglesValue.forEach((definition) => this.createRectangle({ definition }));
         if (this.fitBoundsToMarkersValue) {
             this.doFitBoundsToMarkers();
         }
@@ -42,6 +45,7 @@ class default_1 extends Controller {
             polygons: [...this.polygons.values()],
             polylines: [...this.polylines.values()],
             circles: [...this.circles.values()],
+            rectangles: [...this.rectangles.values()],
             infoWindows: this.infoWindows,
         });
         this.isConnected = true;
@@ -80,6 +84,12 @@ class default_1 extends Controller {
         }
         this.onDrawChanged(this.circles, this.circlesValue, this.createCircle, this.doRemoveCircle);
     }
+    rectanglesValueChanged() {
+        if (!this.isConnected) {
+            return;
+        }
+        this.onDrawChanged(this.rectangles, this.rectanglesValue, this.createRectangle, this.doRemoveRectangle);
+    }
     createDrawingFactory(type, draws, factory) {
         const eventBefore = `${type}:before-create`;
         const eventAfter = `${type}:after-create`;
@@ -117,6 +127,7 @@ default_1.values = {
     polygons: Array,
     polylines: Array,
     circles: Array,
+    rectangles: Array,
     options: Object,
 };
 
@@ -228,6 +239,23 @@ class map_controller extends default_1 {
     doRemoveCircle(circle) {
         circle.remove();
     }
+    doCreateRectangle({ definition, }) {
+        const { '@id': _id, southWest, northEast, title, infoWindow, rawOptions = {} } = definition;
+        const rectangle = L.rectangle([
+            [southWest.lat, southWest.lng],
+            [northEast.lat, northEast.lng],
+        ], { ...rawOptions }).addTo(this.map);
+        if (title) {
+            rectangle.bindPopup(title);
+        }
+        if (infoWindow) {
+            this.createInfoWindow({ definition: infoWindow, element: rectangle });
+        }
+        return rectangle;
+    }
+    doRemoveRectangle(rectangle) {
+        rectangle.remove();
+    }
     doCreateInfoWindow({ definition, element, }) {
         const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
         element.bindPopup([headerContent, content].filter((x) => x).join('<br>'), { ...otherOptions, ...rawOptions });
diff --git a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
index 96314e6e..277cde10 100644
--- a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
@@ -7,6 +7,7 @@ import type {
     Point,
     PolygonDefinition,
     PolylineDefinition,
+    RectangleDefinition,
 } from '@symfony/ux-map';
 import 'leaflet/dist/leaflet.min.css';
 import * as L from 'leaflet';
@@ -19,6 +20,7 @@ import type {
     PolylineOptions as PolygonOptions,
     PolylineOptions,
     PopupOptions,
+    PolylineOptions as RectangleOptions,
 } from 'leaflet';
 
 type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom' | 'attributionControl' | 'zoomControl'> & {
@@ -45,7 +47,9 @@ export default class extends AbstractMapController<
     PolylineOptions,
     L.Polyline,
     CircleOptions,
-    L.Circle
+    L.Circle,
+    RectangleOptions,
+    L.Rectangle
 > {
     declare map: L.Map;
 
@@ -200,12 +204,40 @@ export default class extends AbstractMapController<
         circle.remove();
     }
 
+    protected doCreateRectangle({
+        definition,
+    }: { definition: RectangleDefinition<RectangleOptions, PopupOptions> }): L.Rectangle {
+        const { '@id': _id, southWest, northEast, title, infoWindow, rawOptions = {} } = definition;
+
+        const rectangle = L.rectangle(
+            [
+                [southWest.lat, southWest.lng],
+                [northEast.lat, northEast.lng],
+            ],
+            { ...rawOptions }
+        ).addTo(this.map);
+
+        if (title) {
+            rectangle.bindPopup(title);
+        }
+
+        if (infoWindow) {
+            this.createInfoWindow({ definition: infoWindow, element: rectangle });
+        }
+
+        return rectangle;
+    }
+
+    protected doRemoveRectangle(rectangle: L.Rectangle): void {
+        rectangle.remove();
+    }
+
     protected doCreateInfoWindow({
         definition,
         element,
     }: {
         definition: InfoWindowWithoutPositionDefinition<PopupOptions>;
-        element: L.Marker | L.Polygon | L.Polyline | L.Circle;
+        element: L.Marker | L.Polygon | L.Polyline | L.Circle | L.Rectangle;
     }): L.Popup {
         const { headerContent, content, rawOptions = {}, ...otherOptions } = definition;
 
diff --git a/src/Map/src/Map.php b/src/Map/src/Map.php
index cee37302..340399e8 100644
--- a/src/Map/src/Map.php
+++ b/src/Map/src/Map.php
@@ -24,12 +24,14 @@ final class Map
     private Polygons $polygons;
     private Polylines $polylines;
     private Circles $circles;
+    private Rectangles $rectangles;
 
     /**
-     * @param Marker[]   $markers
-     * @param Polygon[]  $polygons
-     * @param Polyline[] $polylines
-     * @param Circle[]   $circles
+     * @param Marker[]     $markers
+     * @param Polygon[]    $polygons
+     * @param Polyline[]   $polylines
+     * @param Circle[]     $circles
+     * @param Rectangles[] $rectangles
      */
     public function __construct(
         private readonly ?string $rendererName = null,
@@ -41,11 +43,13 @@ final class Map
         array $polygons = [],
         array $polylines = [],
         array $circles = [],
+        array $rectangles = [],
     ) {
         $this->markers = new Markers($markers);
         $this->polygons = new Polygons($polygons);
         $this->polylines = new Polylines($polylines);
         $this->circles = new Circles($circles);
+        $this->rectangles = new Rectangles($rectangles);
     }
 
     public function getRendererName(): ?string
@@ -147,6 +151,20 @@ final class Map
         return $this;
     }
 
+    public function addRectangle(Rectangle $rectangle): self
+    {
+        $this->rectangles->add($rectangle);
+
+        return $this;
+    }
+
+    public function removeRectangle(Rectangle|string $rectangleOrId): self
+    {
+        $this->rectangles->remove($rectangleOrId);
+
+        return $this;
+    }
+
     public function toArray(): array
     {
         if (!$this->fitBoundsToMarkers) {
@@ -168,6 +186,7 @@ final class Map
             'polygons' => $this->polygons->toArray(),
             'polylines' => $this->polylines->toArray(),
             'circles' => $this->circles->toArray(),
+            'rectangles' => $this->rectangles->toArray(),
         ];
     }
 
@@ -179,6 +198,7 @@ final class Map
      *     polygons?: list<array>,
      *     polylines?: list<array>,
      *     circles?: list<array>,
+     *     rectangles?: list<array>,
      *     fitBoundsToMarkers?: bool,
      *     options?: array<string, mixed>,
      * } $map
@@ -219,6 +239,12 @@ final class Map
         }
         $map['circles'] = array_map(Circle::fromArray(...), $map['circles']);
 
+        $map['rectangles'] ??= [];
+        if (!\is_array($map['rectangles'])) {
+            throw new InvalidArgumentException('The "rectangles" parameter must be an array.');
+        }
+        $map['rectangles'] = array_map(Rectangle::fromArray(...), $map['rectangles']);
+
         return new self(...$map);
     }
 }
diff --git a/src/Map/src/Rectangle.php b/src/Map/src/Rectangle.php
new file mode 100644
index 00000000..98146097
--- /dev/null
+++ b/src/Map/src/Rectangle.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map;
+
+use Symfony\UX\Map\Exception\InvalidArgumentException;
+
+/**
+ * Represents a rectangle on a map defined by two points: the south-west and north-east corners.
+ *
+ * @author Valmont Pehaut-Pietri <valmont.pehaut-pietri@proton.me>
+ */
+final class Rectangle implements Element
+{
+    public function __construct(
+        public readonly Point $southWest,
+        public readonly Point $northEast,
+        public readonly ?string $title = null,
+        public readonly ?InfoWindow $infoWindow = null,
+        public readonly array $extra = [],
+        public readonly ?string $id = null,
+    ) {
+    }
+
+    /**
+     * Convert the rectangle to an array representation.
+     *
+     * @return array{
+     *     southWest: array{lat: float, lng: float},
+     *     northEast: array{lat: float, lng: float},
+     *     title: string|null,
+     *     infoWindow: array<string, mixed>|null,
+     *     extra: array,
+     *     id: string|null
+     * }
+     */
+    public function toArray(): array
+    {
+        return [
+            'southWest' => $this->southWest->toArray(),
+            'northEast' => $this->northEast->toArray(),
+            'title' => $this->title,
+            'infoWindow' => $this->infoWindow?->toArray(),
+            'extra' => $this->extra,
+            'id' => $this->id,
+        ];
+    }
+
+    /**
+     * @param array{
+     *     southWest?: array{lat: float, lng: float},
+     *     northEast?: array{lat: float, lng: float},
+     *     title?: string|null,
+     *     infoWindow?: array<string, mixed>|null,
+     *     extra?: array,
+     *     id?: string|null
+     * } $rectangle
+     *
+     * @internal
+     */
+    public static function fromArray(array $rectangle): self
+    {
+        if (!isset($rectangle['southWest'])) {
+            throw new InvalidArgumentException('The "southWest" point is required to create a Rectangle.');
+        }
+
+        $rectangle['southWest'] = Point::fromArray($rectangle['southWest']);
+
+        if (!isset($rectangle['northEast'])) {
+            throw new InvalidArgumentException('The "northEast" point is required to create a Rectangle.');
+        }
+
+        $rectangle['northEast'] = Point::fromArray($rectangle['northEast']);
+
+        if (isset($rectangle['infoWindow'])) {
+            $rectangle['infoWindow'] = InfoWindow::fromArray($rectangle['infoWindow']);
+        }
+
+        return new self(...$rectangle);
+    }
+}
diff --git a/src/Map/src/Rectangles.php b/src/Map/src/Rectangles.php
new file mode 100644
index 00000000..7ef1b18e
--- /dev/null
+++ b/src/Map/src/Rectangles.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map;
+
+/**
+ * Represents a Rectangle collection.
+ *
+ * @author Valmont Pehaut-Pietri <valmont.pehaut-pietri@proton.me>
+ *
+ * @internal
+ */
+final class Rectangles extends Elements
+{
+    public static function fromArray(array $elements): self
+    {
+        $elementObjects = [];
+
+        foreach ($elements as $element) {
+            $elementObjects[] = Rectangle::fromArray($element);
+        }
+
+        return new self(elements: $elementObjects);
+    }
+}
diff --git a/src/Map/src/Renderer/AbstractRenderer.php b/src/Map/src/Renderer/AbstractRenderer.php
index ad3ba433..b56a5348 100644
--- a/src/Map/src/Renderer/AbstractRenderer.php
+++ b/src/Map/src/Renderer/AbstractRenderer.php
@@ -108,6 +108,14 @@ abstract class AbstractRenderer implements RendererInterface
             $attrs['polylines'][$key]['@id'] = $computeId($polyline);
         }
 
+        foreach ($attrs['circles'] as $key => $circle) {
+            $attrs['circles'][$key]['@id'] = $computeId($circle);
+        }
+
+        foreach ($attrs['rectangles'] as $key => $rectangle) {
+            $attrs['rectangles'][$key]['@id'] = $computeId($rectangle);
+        }
+
         return $attrs;
     }
 }
diff --git a/src/Map/src/Twig/MapRuntime.php b/src/Map/src/Twig/MapRuntime.php
index a729e46e..05690e7b 100644
--- a/src/Map/src/Twig/MapRuntime.php
+++ b/src/Map/src/Twig/MapRuntime.php
@@ -11,11 +11,13 @@
 
 namespace Symfony\UX\Map\Twig;
 
+use Symfony\UX\Map\Circle;
 use Symfony\UX\Map\Map;
 use Symfony\UX\Map\Marker;
 use Symfony\UX\Map\Point;
 use Symfony\UX\Map\Polygon;
 use Symfony\UX\Map\Polyline;
+use Symfony\UX\Map\Rectangle;
 use Symfony\UX\Map\Renderer\RendererInterface;
 use Twig\Extension\RuntimeExtensionInterface;
 
@@ -36,19 +38,23 @@ final class MapRuntime implements RuntimeExtensionInterface
      * @param array<string, mixed> $markers
      * @param array<string, mixed> $polygons
      * @param array<string, mixed> $polylines
+     * @param array<string, mixed> $circles
+     * @param array<string, mixed> $rectangles
      */
     public function renderMap(
         ?Map $map = null,
         array $attributes = [],
+        ?array $center = null,
+        ?float $zoom = null,
         ?array $markers = null,
         ?array $polygons = null,
         ?array $polylines = null,
-        ?array $center = null,
-        ?float $zoom = null,
+        ?array $circles = null,
+        ?array $rectangles = null,
     ): string {
         if ($map instanceof Map) {
-            if (null !== $center || null !== $zoom || $markers) {
-                throw new \InvalidArgumentException('You cannot set "center", "markers" or "zoom" on an existing Map.');
+            if (null !== $center || null !== $zoom || $markers || $polygons || $polylines || $circles || $rectangles) {
+                throw new \InvalidArgumentException('It is not allowed to pass both a Map object and other parameters (like "center", "zoom", "markers", etc...) to the "renderMap" method. Please use either a Map object or the individual parameters.');
             }
 
             return $this->renderer->renderMap($map, $attributes);
@@ -64,6 +70,12 @@ final class MapRuntime implements RuntimeExtensionInterface
         foreach ($polylines ?? [] as $polyline) {
             $map->addPolyline(Polyline::fromArray($polyline));
         }
+        foreach ($circles ?? [] as $circle) {
+            $map->addCircle(Circle::fromArray($circle));
+        }
+        foreach ($rectangles ?? [] as $rectangle) {
+            $map->addRectangle(Rectangle::fromArray($rectangle));
+        }
         if (null !== $center) {
             $map->center(Point::fromArray($center));
         }
@@ -76,7 +88,7 @@ final class MapRuntime implements RuntimeExtensionInterface
 
     public function render(array $args = []): string
     {
-        $map = array_intersect_key($args, ['map' => 0, 'markers' => 1, 'polygons' => 2, 'polylines' => 3, 'center' => 4, 'zoom' => 5]);
+        $map = array_intersect_key($args, array_flip(['map', 'center', 'zoom', 'markers', 'polygons', 'polylines', 'circles', 'rectangles']));
         $attributes = array_diff_key($args, $map);
 
         return $this->renderMap(...$map, attributes: $attributes);
diff --git a/src/Map/src/Twig/UXMapComponent.php b/src/Map/src/Twig/UXMapComponent.php
index 4c167420..0a20552e 100644
--- a/src/Map/src/Twig/UXMapComponent.php
+++ b/src/Map/src/Twig/UXMapComponent.php
@@ -11,10 +11,12 @@
 
 namespace Symfony\UX\Map\Twig;
 
+use Symfony\UX\Map\Circle;
 use Symfony\UX\Map\Marker;
 use Symfony\UX\Map\Point;
 use Symfony\UX\Map\Polygon;
 use Symfony\UX\Map\Polyline;
+use Symfony\UX\Map\Rectangle;
 
 /**
  * @author Simon André <smn.andre@gmail.com>
@@ -41,4 +43,14 @@ final class UXMapComponent
      * @var Polyline[]
      */
     public array $polylines;
+
+    /**
+     * @var Circle[]
+     */
+    public array $circles;
+
+    /**
+     * @var Rectangle[]
+     */
+    public array $rectangles;
 }


Test patch:
diff --git a/src/Map/assets/test/abstract_map_controller.test.ts b/src/Map/assets/test/abstract_map_controller.test.ts
index abef85ef..6ead1d86 100644
--- a/src/Map/assets/test/abstract_map_controller.test.ts
+++ b/src/Map/assets/test/abstract_map_controller.test.ts
@@ -53,6 +53,15 @@ class MyMapController extends AbstractMapController {
         return circle;
     }
 
+    doCreateRectangle({ definition }) {
+        const rectangle = { rectangle: 'rectangle', title: definition.title };
+
+        if (definition.infoWindow) {
+            this.createInfoWindow({ definition: definition.infoWindow, element: rectangle });
+        }
+        return rectangle;
+    }
+
     doCreateInfoWindow({ definition, element }) {
         if (element.marker) {
             return { infoWindow: 'infoWindow', headerContent: definition.headerContent, marker: element.title };
@@ -66,6 +75,9 @@ class MyMapController extends AbstractMapController {
         if (element.circle) {
             return { infoWindow: 'infoWindow', headerContent: definition.headerContent, circle: element.title };
         }
+        if (element.rectangle) {
+            return { infoWindow: 'infoWindow', headerContent: definition.headerContent, rectangle: element.title };
+        }
     }
 
     doFitBoundsToMarkers() {
@@ -96,6 +108,7 @@ describe('AbstractMapController', () => {
                 data-map-polygons-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:45.75,&quot;lng&quot;:4.85},{&quot;lat&quot;:43.6047,&quot;lng&quot;:1.4442}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;@id&quot;:&quot;228ae6f5c1b17cfd&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.4442,&quot;lng&quot;:43.6047},{&quot;lat&quot;:4.85,&quot;lng&quot;:45.75},{&quot;lat&quot;:2.3522,&quot;lng&quot;:48.8566}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Polygon&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:{&quot;foo&quot;:&quot;bar&quot;}},&quot;extra&quot;:{&quot;fillColor&quot;:&quot;#ff0000&quot;},&quot;@id&quot;:&quot;9874334e4e8caa16&quot;}]"
                 data-map-polylines-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.1173,&quot;lng&quot;:-1.6778},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.2082,&quot;lng&quot;:16.3738}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Polyline&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:{&quot;foo&quot;:&quot;bar&quot;}},&quot;extra&quot;:{&quot;strokeColor&quot;:&quot;#ff0000&quot;},&quot;@id&quot;:&quot;0fa955da866c7720&quot;}]"
                 data-map-circles-value="[{&quot;center&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Circle&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:{&quot;foo&quot;:&quot;bar&quot;}},&quot;extra&quot;:{&quot;color&quot;:&quot;#ff0000&quot;, &quot;radii&quot;:1000},&quot;@id&quot;:&quot;7c3e1a9b5f2d4e81&quot;}]"
+                data-map-rectangles-value="[{&quot;bounds&quot;:{&quot;northEast&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;southWest&quot;:{&quot;lat&quot;:45.75,&quot;lng&quot;:4.85}},&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:&quot;Rectangle&quot;,&quot;content&quot;:null,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:{&quot;foo&quot;:&quot;bar&quot;}},&quot;extra&quot;:{&quot;color&quot;:&quot;#ff0000&quot;, &quot;width&quot;:1000},&quot;@id&quot;:&quot;e6b3acef1325fb52&quot;}]"
                 style="height: 600px"
             ></div>
         `);
@@ -105,7 +118,7 @@ describe('AbstractMapController', () => {
         clearDOM();
     });
 
-    it('connect and create map, marker, polygon, polyline, circle and info window', async () => {
+    it('connect and create map, marker, polygon, polyline, circle, rectangle and info window', async () => {
         const div = getByTestId(container, 'map');
         expect(div).not.toHaveClass('connected');
 
@@ -129,6 +142,7 @@ describe('AbstractMapController', () => {
         );
         expect(controller.polylines).toEqual(new Map([['0fa955da866c7720', { polyline: 'polyline', title: null }]]));
         expect(controller.circles).toEqual(new Map([['7c3e1a9b5f2d4e81', { circle: 'circle', title: null }]]));
+        expect(controller.rectangles).toEqual(new Map([['e6b3acef1325fb52', { rectangle: 'rectangle', title: null }]]));
         expect(controller.infoWindows).toEqual([
             {
                 headerContent: 'Paris',
@@ -160,6 +174,11 @@ describe('AbstractMapController', () => {
                 infoWindow: 'infoWindow',
                 circle: null,
             },
+            {
+                headerContent: 'Rectangle',
+                infoWindow: 'infoWindow',
+                rectangle: null,
+            },
         ]);
     });
 });
diff --git a/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts b/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
index ff5d31d0..c681aaa3 100644
--- a/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
+++ b/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
@@ -36,17 +36,18 @@ describe('GoogleMapsController', () => {
 
     beforeEach(() => {
         container = mountDOM(`
-          <div 
+          <div
               data-testid="map"
-              data-controller="check symfony--ux-google-map--map" 
-              data-symfony--ux-google-map--map-provider-options-value="{&quot;version&quot;:&quot;weekly&quot;,&quot;libraries&quot;:[&quot;maps&quot;,&quot;marker&quot;],&quot;apiKey&quot;:&quot;&quot;}" 
-              data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" 
-              data-symfony--ux-google-map--map-zoom-value="7" 
-              data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" 
-              data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" 
-              data-symfony--ux-google-map--map-markers-value="[]" 
-              data-symfony--ux-google-map--map-polygons-value="[]" 
-              data-symfony--ux-google-map--map-polylines-value="[]" 
+              data-controller="check symfony--ux-google-map--map"
+              data-symfony--ux-google-map--map-provider-options-value="{&quot;version&quot;:&quot;weekly&quot;,&quot;libraries&quot;:[&quot;maps&quot;,&quot;marker&quot;],&quot;apiKey&quot;:&quot;&quot;}"
+              data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}"
+              data-symfony--ux-google-map--map-zoom-value="7"
+              data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false"
+              data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}"
+              data-symfony--ux-google-map--map-markers-value="[]"
+              data-symfony--ux-google-map--map-polygons-value="[]"
+              data-symfony--ux-google-map--map-polylines-value="[]"
+              data-symfony--ux-google-map--map-rectangle-value="[]"
               style="height: 600px"
           ></div>
         `);
diff --git a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
index 1882da41..d40a1e71 100644
--- a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
+++ b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
@@ -23,6 +23,7 @@ use Symfony\UX\Map\Marker;
 use Symfony\UX\Map\Point;
 use Symfony\UX\Map\Polygon;
 use Symfony\UX\Map\Polyline;
+use Symfony\UX\Map\Rectangle;
 use Symfony\UX\Map\Test\RendererTestCase;
 use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
 
@@ -111,6 +112,15 @@ class GoogleRendererTest extends RendererTestCase
                 ->addCircle(new Circle(center: new Point(1.1, 2.2), radius: 1000, infoWindow: new InfoWindow(content: 'Circle'))),
         ];
 
+        yield 'with rectangles and infoWindows' => [
+            'renderer' => new GoogleRenderer(new StimulusHelper(null), new UxIconRenderer(null), apiKey: 'api_key'),
+            'map' => (new Map())
+                ->center(new Point(48.8566, 2.3522))
+                ->zoom(12)
+                ->addRectangle(new Rectangle(southWest: new Point(48.8566, 2.3522), northEast: new Point(48.8566, 2.3522), infoWindow: new InfoWindow(content: 'Rectangle')))
+                ->addRectangle(new Rectangle(southWest: new Point(1.1, 2.2), northEast: new Point(3.3, 4.4), infoWindow: new InfoWindow(content: 'Rectangle'))),
+        ];
+
         yield 'with controls enabled' => [
             'renderer' => new GoogleRenderer(new StimulusHelper(null), new UxIconRenderer(null), apiKey: 'api_key'),
             'map' => (new Map())
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set markers with icons__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set markers with icons__1.txt
index 95594024..19bc46ce 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set markers with icons__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set markers with icons__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set simple map, with minimum options__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set simple map, with minimum options__1.txt
index 8f7cd39e..bdbb9ae5 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set simple map, with minimum options__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set simple map, with minimum options__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with all markers removed__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with all markers removed__1.txt
index 8f7cd39e..bdbb9ae5 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with all markers removed__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with all markers removed__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with circles and infoWindows__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with circles and infoWindows__1.txt
index 1ca243a5..c7a07df8 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with circles and infoWindows__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with circles and infoWindows__1.txt
@@ -10,5 +10,6 @@
   data-symfony--ux-google-map--map-markers-value="[]"
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
-  data-symfony--ux-google-map--map-circles-value="[{&quot;center&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;radius&quot;:500,&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Circle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null},{&quot;center&quot;:{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},&quot;radius&quot;:1000,&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Circle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null}]"
+  data-symfony--ux-google-map--map-circles-value="[{&quot;center&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;radius&quot;:500,&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Circle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;3115517621c88996&quot;},{&quot;center&quot;:{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},&quot;radius&quot;:1000,&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Circle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;71febe272ee9a265&quot;}]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with controls enabled__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with controls enabled__1.txt
index 8f7cd39e..bdbb9ae5 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with controls enabled__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with controls enabled__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with custom attributes__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with custom attributes__1.txt
index bdb68d0e..a039ca7a 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with custom attributes__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with custom attributes__1.txt
@@ -11,5 +11,6 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
   class="map"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id overridden by option mapId__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id overridden by option mapId__1.txt
index cf902c81..0a9f3db6 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id overridden by option mapId__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id overridden by option mapId__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id, when passing options (except the mapId)__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id, when passing options (except the mapId)__1.txt
index 7a9a93e8..a313ade2 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id, when passing options (except the mapId)__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id, when passing options (except the mapId)__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id__1.txt
index 7a9a93e8..a313ade2 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with default map id__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with every options__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with every options__1.txt
index b48e93d3..e5943a9f 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with every options__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with every options__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with marker remove and new ones added__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with marker remove and new ones added__1.txt
index 581ad31a..bd3883e6 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with marker remove and new ones added__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with marker remove and new ones added__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with markers and infoWindows__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with markers and infoWindows__1.txt
index 0f83755e..281c1adf 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with markers and infoWindows__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with markers and infoWindows__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with polygons and infoWindows__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with polygons and infoWindows__1.txt
index d615a208..1d2aaf66 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with polygons and infoWindows__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with polygons and infoWindows__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;7cdd432ea54d0ce9&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;9074e0a9ead08c1e&quot;}]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with polylines and infoWindows__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with polylines and infoWindows__1.txt
index ad046db8..2ed6b41d 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with polylines and infoWindows__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with polylines and infoWindows__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;7cdd432ea54d0ce9&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;9074e0a9ead08c1e&quot;}]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with rectangles and infoWindows__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with rectangles and infoWindows__1.txt
new file mode 100644
index 00000000..f29709a8
--- /dev/null
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with rectangles and infoWindows__1.txt
@@ -0,0 +1,15 @@
+<!-- This HTML has been prettified for testing purposes, and may not represent the actual HTML output.
+     Run "php vendor/bin/phpunit -d --update-snapshots" to update the snapshot. -->
+<div
+  data-controller="symfony--ux-google-map--map"
+  data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}"
+  data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}"
+  data-symfony--ux-google-map--map-zoom-value="12"
+  data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false"
+  data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}"
+  data-symfony--ux-google-map--map-markers-value="[]"
+  data-symfony--ux-google-map--map-polygons-value="[]"
+  data-symfony--ux-google-map--map-polylines-value="[]"
+  data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[{&quot;southWest&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;northEast&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Rectangle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;1b754ce32860016a&quot;},{&quot;southWest&quot;:{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},&quot;northEast&quot;:{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Rectangle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;d98c12855ec90699&quot;}]"
+></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set without controls enabled__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set without controls enabled__1.txt
index 09aa8580..77127676 100644
--- a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set without controls enabled__1.txt
+++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set without controls enabled__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-google-map--map-polygons-value="[]"
   data-symfony--ux-google-map--map-polylines-value="[]"
   data-symfony--ux-google-map--map-circles-value="[]"
+  data-symfony--ux-google-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts b/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
index 680f6d3a..f1489889 100644
--- a/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
@@ -36,17 +36,18 @@ describe('LeafletController', () => {
 
     beforeEach(() => {
         container = mountDOM(`
-          <div 
+          <div
               data-testid="map"
-              data-controller="check symfony--ux-leaflet-map--map" 
-              data-symfony--ux-leaflet-map--map-provider-options-value="{}" 
-              data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" 
-              data-symfony--ux-leaflet-map--map-zoom-value="7" 
-              data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" 
-              data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\\/\\/tile.openstreetmap.org\\/{z}\\/{x}\\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\\&quot;https:\\/\\/www.openstreetmap.org\\/copyright\\&quot;&gt;OpenStreetMap&lt;\\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" 
-              data-symfony--ux-leaflet-map--map-markers-value="[]" 
-              data-symfony--ux-leaflet-map--map-polygons-value="[]" 
-              data-symfony--ux-leaflet-map--map-polylines-value="[]" 
+              data-controller="check symfony--ux-leaflet-map--map"
+              data-symfony--ux-leaflet-map--map-provider-options-value="{}"
+              data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}"
+              data-symfony--ux-leaflet-map--map-zoom-value="7"
+              data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false"
+              data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\\/\\/tile.openstreetmap.org\\/{z}\\/{x}\\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\\&quot;https:\\/\\/www.openstreetmap.org\\/copyright\\&quot;&gt;OpenStreetMap&lt;\\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}"
+              data-symfony--ux-leaflet-map--map-markers-value="[]"
+              data-symfony--ux-leaflet-map--map-polygons-value="[]"
+              data-symfony--ux-leaflet-map--map-polylines-value="[]"
+              data-symfony--ux-leaflet-map--map-rectangles-value="[]"
               style="height: 600px"
           ></div>
 
diff --git a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
index f1561e34..07490459 100644
--- a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
+++ b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
@@ -22,6 +22,7 @@ use Symfony\UX\Map\Marker;
 use Symfony\UX\Map\Point;
 use Symfony\UX\Map\Polygon;
 use Symfony\UX\Map\Polyline;
+use Symfony\UX\Map\Rectangle;
 use Symfony\UX\Map\Test\RendererTestCase;
 use Symfony\UX\StimulusBundle\Helper\StimulusHelper;
 
@@ -106,6 +107,25 @@ class LeafletRendererTest extends RendererTestCase
                 ->addCircle(new Circle(center: new Point(1.1, 2.2), radius: 500, infoWindow: new InfoWindow(content: 'Circle'), id: 'circle2')),
         ];
 
+        yield 'with rectangles and infoWindows' => [
+            'renderer' => new LeafletRenderer(new StimulusHelper(null), new UxIconRenderer(null)),
+            'map' => (new Map())
+                ->center(new Point(48.8566, 2.3522))
+                ->zoom(12)
+                ->addRectangle(new Rectangle(
+                    southWest: new Point(48.8566, 2.3522),
+                    northEast: new Point(48.8566, 2.3522),
+                    title: 'Rectangle',
+                    id: 'rectangle1'
+                ))
+                ->addRectangle(new Rectangle(
+                    southWest: new Point(1.1, 2.2),
+                    northEast: new Point(3.3, 4.4),
+                    infoWindow: new InfoWindow(content: 'Rectangle'),
+                    id: 'rectangle2'
+                )),
+        ];
+
         yield 'markers with icons' => [
             'renderer' => new LeafletRenderer(
                 new StimulusHelper(null),
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set markers with icons__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set markers with icons__1.txt
index 9e430161..0001322a 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set markers with icons__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set markers with icons__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-leaflet-map--map-polygons-value="[]"
   data-symfony--ux-leaflet-map--map-polylines-value="[]"
   data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set simple map__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set simple map__1.txt
index 7e2de5e6..6d5492e9 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set simple map__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set simple map__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-leaflet-map--map-polygons-value="[]"
   data-symfony--ux-leaflet-map--map-polylines-value="[]"
   data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with all markers removed__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with all markers removed__1.txt
index 7e2de5e6..6d5492e9 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with all markers removed__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with all markers removed__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-leaflet-map--map-polygons-value="[]"
   data-symfony--ux-leaflet-map--map-polylines-value="[]"
   data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with circles and infoWindows__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with circles and infoWindows__1.txt
index 11da7695..e6bbccce 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with circles and infoWindows__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with circles and infoWindows__1.txt
@@ -10,5 +10,6 @@
   data-symfony--ux-leaflet-map--map-markers-value="[]"
   data-symfony--ux-leaflet-map--map-polygons-value="[]"
   data-symfony--ux-leaflet-map--map-polylines-value="[]"
-  data-symfony--ux-leaflet-map--map-circles-value="[{&quot;center&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;radius&quot;:1000000,&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;circle1&quot;},{&quot;center&quot;:{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},&quot;radius&quot;:500,&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Circle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;circle2&quot;}]"
+  data-symfony--ux-leaflet-map--map-circles-value="[{&quot;center&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;radius&quot;:1000000,&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;circle1&quot;,&quot;@id&quot;:&quot;b12929e5dc57d558&quot;},{&quot;center&quot;:{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},&quot;radius&quot;:500,&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Circle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;circle2&quot;,&quot;@id&quot;:&quot;4953b5cc1d5759f4&quot;}]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with custom attributes__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with custom attributes__1.txt
index 119080da..55933034 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with custom attributes__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with custom attributes__1.txt
@@ -11,5 +11,6 @@
   data-symfony--ux-leaflet-map--map-polygons-value="[]"
   data-symfony--ux-leaflet-map--map-polylines-value="[]"
   data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
   class="map"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with marker remove and new ones added__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with marker remove and new ones added__1.txt
index 63832d46..bedb660d 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with marker remove and new ones added__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with marker remove and new ones added__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-leaflet-map--map-polygons-value="[]"
   data-symfony--ux-leaflet-map--map-polylines-value="[]"
   data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with markers and infoWindows__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with markers and infoWindows__1.txt
index ac12e7b2..c3cbc4e6 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with markers and infoWindows__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with markers and infoWindows__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-leaflet-map--map-polygons-value="[]"
   data-symfony--ux-leaflet-map--map-polylines-value="[]"
   data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with polygons and infoWindows__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with polygons and infoWindows__1.txt
index 1bad78f8..6499703a 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with polygons and infoWindows__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with polygons and infoWindows__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-leaflet-map--map-polygons-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;polygon1&quot;,&quot;@id&quot;:&quot;35bfa920335b849d&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;polygon2&quot;,&quot;@id&quot;:&quot;7be1fe9f10489d73&quot;}]"
   data-symfony--ux-leaflet-map--map-polylines-value="[]"
   data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with polylines and infoWindows__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with polylines and infoWindows__1.txt
index cd7fe232..176a7c69 100644
--- a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with polylines and infoWindows__1.txt
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with polylines and infoWindows__1.txt
@@ -11,4 +11,5 @@
   data-symfony--ux-leaflet-map--map-polygons-value="[]"
   data-symfony--ux-leaflet-map--map-polylines-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;polyline1&quot;,&quot;@id&quot;:&quot;823f6ee5acdb5db3&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polyline&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;polyline2&quot;,&quot;@id&quot;:&quot;77fb0e390b5e91f1&quot;}]"
   data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[]"
 ></div>
\ No newline at end of file
diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with rectangles and infoWindows__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with rectangles and infoWindows__1.txt
new file mode 100644
index 00000000..b85e1a42
--- /dev/null
+++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with rectangles and infoWindows__1.txt
@@ -0,0 +1,15 @@
+<!-- This HTML has been prettified for testing purposes, and may not represent the actual HTML output.
+     Run "php vendor/bin/phpunit -d --update-snapshots" to update the snapshot. -->
+<div
+  data-controller="symfony--ux-leaflet-map--map"
+  data-symfony--ux-leaflet-map--map-provider-options-value="{}"
+  data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}"
+  data-symfony--ux-leaflet-map--map-zoom-value="12"
+  data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false"
+  data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;attributionControlOptions&quot;:{&quot;position&quot;:&quot;bottomright&quot;,&quot;prefix&quot;:&quot;Leaflet&quot;},&quot;zoomControlOptions&quot;:{&quot;position&quot;:&quot;topleft&quot;,&quot;zoomInText&quot;:&quot;&lt;span aria-hidden=\&quot;true\&quot;&gt;+&lt;\/span&gt;&quot;,&quot;zoomInTitle&quot;:&quot;Zoom in&quot;,&quot;zoomOutText&quot;:&quot;&lt;span aria-hidden=\&quot;true\&quot;&gt;&amp;#x2212;&lt;\/span&gt;&quot;,&quot;zoomOutTitle&quot;:&quot;Zoom out&quot;},&quot;@provider&quot;:&quot;leaflet&quot;}"
+  data-symfony--ux-leaflet-map--map-markers-value="[]"
+  data-symfony--ux-leaflet-map--map-polygons-value="[]"
+  data-symfony--ux-leaflet-map--map-polylines-value="[]"
+  data-symfony--ux-leaflet-map--map-circles-value="[]"
+  data-symfony--ux-leaflet-map--map-rectangles-value="[{&quot;southWest&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;northEast&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Rectangle&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;rectangle1&quot;,&quot;@id&quot;:&quot;4cde1a021a127686&quot;},{&quot;southWest&quot;:{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},&quot;northEast&quot;:{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Rectangle&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;rectangle2&quot;,&quot;@id&quot;:&quot;2549f5a73ef6bee7&quot;}]"
+></div>
\ No newline at end of file
diff --git a/src/Map/src/Test/RendererTestCase.php b/src/Map/src/Test/RendererTestCase.php
index 6e8ff909..acb9685f 100644
--- a/src/Map/src/Test/RendererTestCase.php
+++ b/src/Map/src/Test/RendererTestCase.php
@@ -14,6 +14,7 @@ namespace Symfony\UX\Map\Test;
 use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\TestCase;
 use Spatie\Snapshots\MatchesSnapshots;
+use Symfony\UX\Map\Elements;
 use Symfony\UX\Map\Map;
 use Symfony\UX\Map\Renderer\RendererInterface;
 
@@ -38,6 +39,7 @@ abstract class RendererTestCase extends TestCase
         $rendered = $renderer->renderMap($map, $attributes);
         $rendered = $this->prettify($rendered);
 
+        $this->assertElementsHaveComputedId($rendered);
         $this->assertMatchesSnapshot($rendered);
     }
 
@@ -50,4 +52,45 @@ abstract class RendererTestCase extends TestCase
 
         return $html;
     }
+
+    private function assertElementsHaveComputedId(string $html): void
+    {
+        // Extract the `Elements` properties from the Map class
+        static $elementsProperties = null;
+        if (null === $elementsProperties) {
+            $elementsProperties = [];
+            $reflMap = new \ReflectionClass(Map::class);
+
+            foreach ($reflMap->getProperties() as $prop) {
+                if (is_a($prop->getType()->getName(), Elements::class, true)) {
+                    $elementsProperties[] = $prop->getName();
+                }
+            }
+        }
+
+        // Parse the rendered HTML and extract `data-<element>-value` attributes
+        /** @var array<string, string> $htmlAttributes */
+        $htmlAttributes = [];
+        foreach ($elementsProperties as $property) {
+            $matchesResult = preg_match(\sprintf('/data-symfony--ux-[a-z-]+-map-%s-value="([^"]+)"/', preg_quote($property, '/')), $html, $matches);
+            if (false === $matchesResult) {
+                throw new \LogicException(\sprintf('Failed to parse the rendered HTML for property "%s".', $property));
+            } elseif (0 === $matchesResult) {
+                throw new \LogicException(\sprintf('It looks like the property "%s" is missing from "Map::toArray()" normalization.', $property));
+            } else {
+                $htmlAttributes[$property] = $matches[1];
+            }
+        }
+
+        // Check that each property has a computed "@id" attribute
+        foreach ($htmlAttributes as $property => $value) {
+            if ('[]' === $value) {
+                continue;
+            }
+
+            if (!str_contains($value, '@id') || str_contains($value, '&quot;@id&quot;:null')) {
+                throw new \LogicException(\sprintf('It looks like the "AbstractRendered::getMapAttributes()" has not been updated to compute "@id" attribute of "%s" property.', $property));
+            }
+        }
+    }
 }
diff --git a/src/Map/tests/MapTest.php b/src/Map/tests/MapTest.php
index dad69e51..06d09a24 100644
--- a/src/Map/tests/MapTest.php
+++ b/src/Map/tests/MapTest.php
@@ -20,6 +20,7 @@ use Symfony\UX\Map\Marker;
 use Symfony\UX\Map\Point;
 use Symfony\UX\Map\Polygon;
 use Symfony\UX\Map\Polyline;
+use Symfony\UX\Map\Rectangle;
 
 class MapTest extends TestCase
 {
@@ -70,6 +71,7 @@ class MapTest extends TestCase
             'polygons' => [],
             'polylines' => [],
             'circles' => [],
+            'rectangles' => [],
         ], $array);
     }
 
@@ -91,6 +93,7 @@ class MapTest extends TestCase
             'polygons' => [],
             'polylines' => [],
             'circles' => [],
+            'rectangles' => [],
         ], $array);
     }
 
@@ -190,6 +193,30 @@ class MapTest extends TestCase
                     autoClose: true,
                 ),
             ))
+            ->addRectangle(new Rectangle(
+                southWest: new Point(48.853, 2.3499),
+                northEast: new Point(48.8566, 2.3522),
+                title: 'Rectangle around Paris',
+                infoWindow: new InfoWindow(
+                    headerContent: '<b>Rectangle around Paris</b>',
+                    content: 'A rectangle around Paris.',
+                    position: new Point(48.8566, 2.3522),
+                    opened: true,
+                    autoClose: true,
+                ),
+            ))
+            ->addRectangle(new Rectangle(
+                southWest: new Point(45.75, 4.85),
+                northEast: new Point(45.77, 4.82),
+                title: 'Rectangle around Lyon',
+                infoWindow: new InfoWindow(
+                    headerContent: '<b>Rectangle around Lyon</b>',
+                    content: 'A rectangle around Lyon.',
+                    position: new Point(45.764, 4.8357),
+                    opened: true,
+                    autoClose: true,
+                ),
+            ))
         ;
 
         self::assertEquals([
@@ -342,6 +369,38 @@ class MapTest extends TestCase
                     'id' => null,
                 ],
             ],
+            'rectangles' => [
+                [
+                    'southWest' => ['lat' => 48.853, 'lng' => 2.3499],
+                    'northEast' => ['lat' => 48.8566, 'lng' => 2.3522],
+                    'title' => 'Rectangle around Paris',
+                    'infoWindow' => [
+                        'headerContent' => '<b>Rectangle around Paris</b>',
+                        'content' => 'A rectangle around Paris.',
+                        'position' => ['lat' => 48.8566, 'lng' => 2.3522],
+                        'opened' => true,
+                        'autoClose' => true,
+                        'extra' => [],
+                    ],
+                    'extra' => [],
+                    'id' => null,
+                ],
+                [
+                    'southWest' => ['lat' => 45.75, 'lng' => 4.85],
+                    'northEast' => ['lat' => 45.77, 'lng' => 4.82],
+                    'title' => 'Rectangle around Lyon',
+                    'infoWindow' => [
+                        'headerContent' => '<b>Rectangle around Lyon</b>',
+                        'content' => 'A rectangle around Lyon.',
+                        'position' => ['lat' => 45.764, 'lng' => 4.8357],
+                        'opened' => true,
+                        'autoClose' => true,
+                        'extra' => [],
+                    ],
+                    'extra' => [],
+                    'id' => null,
+                ],
+            ],
         ], $map->toArray());
     }
 }
diff --git a/src/Map/tests/RectangleTest.php b/src/Map/tests/RectangleTest.php
new file mode 100644
index 00000000..60517f8b
--- /dev/null
+++ b/src/Map/tests/RectangleTest.php
@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\Map\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\UX\Map\Exception\InvalidArgumentException;
+use Symfony\UX\Map\InfoWindow;
+use Symfony\UX\Map\Point;
+use Symfony\UX\Map\Rectangle;
+
+class RectangleTest extends TestCase
+{
+    public function testToArray()
+    {
+        $infoWindow = new InfoWindow('Hello');
+
+        $southWest = new Point(1.0, 2.0);
+        $northEast = new Point(3.0, 4.0);
+
+        $rectangle = new Rectangle($southWest, $northEast, 'Test Rectangle', $infoWindow, ['foo' => 'bar'], 'rect1');
+
+        $array = $rectangle->toArray();
+        self::assertSame([
+            'southWest' => ['lat' => 1.0, 'lng' => 2.0],
+            'northEast' => ['lat' => 3.0, 'lng' => 4.0],
+            'title' => 'Test Rectangle',
+            'infoWindow' => $infoWindow->toArray(),
+            'extra' => ['foo' => 'bar'],
+            'id' => 'rect1',
+        ], $array);
+    }
+
+    public function testFromArray()
+    {
+        $data = [
+            'southWest' => ['lat' => 1.0, 'lng' => 2.0],
+            'northEast' => ['lat' => 3.0, 'lng' => 4.0],
+            'title' => 'Test Rectangle',
+            'infoWindow' => ['content' => 'Hello'],
+            'extra' => ['foo' => 'bar'],
+            'id' => 'rect1',
+        ];
+
+        $rectangle = Rectangle::fromArray($data);
+
+        $array = $rectangle->toArray();
+        self::assertSame([
+            'southWest' => ['lat' => 1.0, 'lng' => 2.0],
+            'northEast' => ['lat' => 3.0, 'lng' => 4.0],
+            'title' => 'Test Rectangle',
+            'infoWindow' => [
+                'headerContent' => null,
+                'content' => 'Hello',
+                'position' => null,
+                'opened' => false,
+                'autoClose' => true,
+                'extra' => $array['infoWindow']['extra'],
+            ],
+            'extra' => ['foo' => 'bar'],
+            'id' => 'rect1',
+        ], $array);
+    }
+
+    public function testFromArrayThrowsExceptionIfSouthWestMissing()
+    {
+        $this->expectException(InvalidArgumentException::class);
+
+        Rectangle::fromArray([
+            'northEast' => ['lat' => 3.0, 'lng' => 4.0],
+        ]);
+    }
+
+    public function testFromArrayThrowsExceptionIfNorthEastMissing()
+    {
+        $this->expectException(InvalidArgumentException::class);
+
+        Rectangle::fromArray([
+            'southWest' => ['lat' => 1.0, 'lng' => 2.0],
+        ]);
+    }
+}