diff --git a/experience_builder.libraries.yml b/experience_builder.libraries.yml
index 406fa1a7c5c915e619811ff4d913e4e8e00ad00d..98a3f221e77c36fbf16a2e0adfc0548aa6e60bbf 100644
--- a/experience_builder.libraries.yml
+++ b/experience_builder.libraries.yml
@@ -97,8 +97,12 @@ astro.client:
     gpl-compatible: true
 
 astro.hydration:
+  header: true
   js:
     ui/lib/astro-hydration/dist/hydration.js: {}
+  css:
+    theme:
+      ui/lib/astro-hydration/dist/styles/hydration.css: {}
   license:
     name: MIT
     url: https://raw.githubusercontent.com/withastro/astro/refs/heads/main/LICENSE
diff --git a/ui/lib/astro-hydration/README.md b/ui/lib/astro-hydration/README.md
index 3b7b0064fca081c0073825242aba346f7ade8990..e98867fdf7f4ceb730cb4140ee0304e76bd3750c 100644
--- a/ui/lib/astro-hydration/README.md
+++ b/ui/lib/astro-hydration/README.md
@@ -11,7 +11,9 @@ This directory is an Astro project and is defined as an [npm workspace](https://
 
 The build step `astro build` generates bundles for Preact (preact.module.js), hooks (hooks.module.js), etc. that the in-browser-editable components depend on.
 
-`client.js` and `hydration.js` are defined as libraries in `experience_builder.libraries.yml` to make them accessible to the JS component source plugin.
+`client.js`, `client.css`, and `hydration.js` are defined as libraries in `experience_builder.libraries.yml` to make them accessible to the JS component source plugin.
+
+The `client.css` file contains basic styling for Astro elements to ensure proper display using `display: contents`.
 
 ***The rest of the built files do not need to be defined as Drupal libraries because they are imported by `client.js`.**
 
diff --git a/ui/lib/astro-hydration/public/styles/hydration.css b/ui/lib/astro-hydration/public/styles/hydration.css
new file mode 100644
index 0000000000000000000000000000000000000000..e18bee310e515c90615de06de4cdd3de3eb4502a
--- /dev/null
+++ b/ui/lib/astro-hydration/public/styles/hydration.css
@@ -0,0 +1 @@
+astro-island,astro-slot,astro-static-slot{display:contents}
\ No newline at end of file
diff --git a/ui/src/features/layout/preview/DataToHtmlMapContext.tsx b/ui/src/features/layout/preview/DataToHtmlMapContext.tsx
index c9e6758689d6f089173de684ea3e33952acb5083..3c9a07e610a78a1a135711bc7fecfc7adbc0197e 100644
--- a/ui/src/features/layout/preview/DataToHtmlMapContext.tsx
+++ b/ui/src/features/layout/preview/DataToHtmlMapContext.tsx
@@ -1,11 +1,7 @@
 import type { ReactNode } from 'react';
 import { createContext, useContext, useState, useCallback } from 'react';
 
-import type {
-  ComponentsMap,
-  RegionsMap,
-  SlotsMap,
-} from '@/types/AnnotationMaps';
+import type { ComponentsMap, RegionsMap, SlotsMap } from '@/types/Annotations';
 
 interface ComponentHtmlMapProviderProps {
   children: ReactNode;
diff --git a/ui/src/features/layout/previewOverlay/ComponentOverlay.tsx b/ui/src/features/layout/previewOverlay/ComponentOverlay.tsx
index 45b796edd37b5821f1c94343e303aa6895150b40..4984f724ebf781c51f8b69cf6dad1c54a278a04a 100644
--- a/ui/src/features/layout/previewOverlay/ComponentOverlay.tsx
+++ b/ui/src/features/layout/previewOverlay/ComponentOverlay.tsx
@@ -27,7 +27,7 @@ import { useNavigationUtils } from '@/hooks/useNavigationUtils';
 import useXbParams from '@/hooks/useXbParams';
 import ComponentDropZone from '@/features/layout/previewOverlay/ComponentDropZone';
 import { useDraggable } from '@dnd-kit/core';
-import type { StackDirection } from '@/types/AnnotationMaps';
+import type { StackDirection } from '@/types/Annotations';
 
 export interface ComponentOverlayProps {
   component: ComponentNode;
@@ -110,8 +110,7 @@ const ComponentOverlay: React.FC<ComponentOverlayProps> = (props) => {
         const newOffsets = {
           ...getDistanceBetweenElements(
             parentElementInsideIframe,
-            // @todo Potential bug: an element amongst the elementsInsideIframe array other than the first could be further to the top/left than the first element.
-            elementsInsideIframe.current[0],
+            elementsInsideIframe.current,
           ),
         };
 
diff --git a/ui/src/hooks/useRenderPreviewEmptyRegionPlaceholder.ts b/ui/src/hooks/useRenderPreviewEmptyRegionPlaceholder.ts
index 055756cb90eabee5f38db5a11d045ab1217fec20..288ccf678572620b5c70ccb5f90ce2f6772b1510 100644
--- a/ui/src/hooks/useRenderPreviewEmptyRegionPlaceholder.ts
+++ b/ui/src/hooks/useRenderPreviewEmptyRegionPlaceholder.ts
@@ -1,7 +1,7 @@
 import { useEffect, useCallback } from 'react';
 import { useAppSelector } from '@/app/hooks';
 import { selectLayout } from '@/features/layout/layoutModelSlice';
-import type { RegionsMap } from '@/types/AnnotationMaps';
+import type { RegionsMap } from '@/types/Annotations';
 import { DEFAULT_REGION } from '@/features/ui/uiSlice';
 
 /**
diff --git a/ui/src/hooks/useRenderPreviewEmptySlotPlaceholders.ts b/ui/src/hooks/useRenderPreviewEmptySlotPlaceholders.ts
index b23c14ea4770dd3f8e95468fa3dd5cdda51c41d3..f82ad8dc7447514343031c9777ac40f58f4ec10c 100644
--- a/ui/src/hooks/useRenderPreviewEmptySlotPlaceholders.ts
+++ b/ui/src/hooks/useRenderPreviewEmptySlotPlaceholders.ts
@@ -2,7 +2,7 @@ import { useEffect, useCallback } from 'react';
 import { useAppSelector } from '@/app/hooks';
 import { selectLayout } from '@/features/layout/layoutModelSlice';
 import { findSlotById } from '@/features/layout/layoutUtils';
-import type { SlotsMap, SlotInfo } from '@/types/AnnotationMaps';
+import type { SlotsMap, SlotInfo } from '@/types/Annotations';
 
 /**
  * This hook renders a placeholder div in each empty component slot on the page in the preview iFrame.
diff --git a/ui/src/hooks/useSyncPreviewElementSize.ts b/ui/src/hooks/useSyncPreviewElementSize.ts
index 04fa8ac7bcaed92d6c66e282e48800fc1655a610..265be7775a5abb81593b6d3c4f43db7aa64679ba 100644
--- a/ui/src/hooks/useSyncPreviewElementSize.ts
+++ b/ui/src/hooks/useSyncPreviewElementSize.ts
@@ -1,4 +1,5 @@
 import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
+import { calculateBoundingRect, elemIsVisible } from '@/utils/function-utils';
 
 /**
  * This hook takes an HTML element or array of HTML elements and returns a state containing the elements' dimensions and position.
@@ -13,14 +14,6 @@ interface Rect {
   height: number;
 }
 
-function elemIsVisible(elem: HTMLElement) {
-  return !!(
-    elem.offsetWidth ||
-    elem.offsetHeight ||
-    elem.getClientRects().length
-  );
-}
-
 function findParentBody(element: HTMLElement) {
   let currentElement = element;
 
@@ -39,28 +32,14 @@ function findParentBody(element: HTMLElement) {
 function isElementObservable(element: HTMLElement) {
   const style = window.getComputedStyle(element);
 
-  // Check if the element is inline - inline elements to not fire resize events.
-  if (style.display === 'inline') {
+  // display: contents; elements (e.g. <astro-* />) do not fire resize events.
+  if (style.display === 'contents') {
     return false;
   }
 
   return elemIsVisible(element);
 }
 
-function getMaxOfArray(numArray: number[]) {
-  if (numArray.length === 0) {
-    return 0;
-  }
-  return Math.max.apply(null, numArray);
-}
-
-function getMinOfArray(numArray: number[]) {
-  if (numArray.length === 0) {
-    return 0;
-  }
-  return Math.min.apply(null, numArray);
-}
-
 function useSyncPreviewElementSize(input: HTMLElement[] | HTMLElement | null) {
   // Normalize the input to always be an array
   const elements = useMemo(() => {
@@ -82,40 +61,12 @@ function useSyncPreviewElementSize(input: HTMLElement[] | HTMLElement | null) {
   const elementsRef = useRef<HTMLElement[] | null>(null);
 
   const recalculateBorder = useCallback(() => {
-    const tops: number[] = [];
-    const lefts: number[] = [];
-    const rights: number[] = [];
-    const bottoms: number[] = [];
-
-    elementsRef.current?.forEach((el) => {
-      if (!el) {
-        return;
-      }
-      const rect = el.getBoundingClientRect();
-
-      // check the element is actually visible on the page - otherwise we end up incorrectly setting the minTop & minLeft to be 0 for hidden elements.
-      if (elemIsVisible(el)) {
-        tops.push(rect.top);
-        lefts.push(rect.left);
-        rights.push(rect.left + rect.width);
-        bottoms.push(rect.top + rect.height);
-      }
-    });
-
-    const minTop = getMinOfArray(tops);
-    const minLeft = getMinOfArray(lefts);
-    const newRect = {
-      top: minTop,
-      left: minLeft,
-      width: getMaxOfArray(rights) - minLeft,
-      height: getMaxOfArray(bottoms) - minTop,
-    };
+    const elements = elementsRef.current;
+    const newRect = calculateBoundingRect(elements);
 
-    if (elementsRef.current) {
+    if (newRect && elements) {
       requestAnimationFrame(() => {
         setElementRect((prevRect) => {
-          // Only update if the values have changed so the hook returns the same object preventing components that use
-          // it from re-rendering
           if (
             prevRect.top !== newRect.top ||
             prevRect.left !== newRect.left ||
@@ -165,7 +116,7 @@ function useSyncPreviewElementSize(input: HTMLElement[] | HTMLElement | null) {
 
     elementsRef.current?.forEach((element) => {
       /**
-       * <astro-island> elements (XB Code Components) are display: inline; and that means you can't observe them with
+       * <astro-island> elements (XB Code Components) are display: contents; and that means you can't observe them with
        * resizeObserver. Here, if the element we're syncing with can't be observed we traverse up the DOM to find the
        * first parent that can be and watch that instead
        */
diff --git a/ui/src/types/AnnotationMaps.ts b/ui/src/types/Annotations.ts
similarity index 85%
rename from ui/src/types/AnnotationMaps.ts
rename to ui/src/types/Annotations.ts
index 82c8990b6043771ac0c07a26dd1c553f884117f2..64fb4c157c7ac2e4f6a24321f975c55c678c50be 100644
--- a/ui/src/types/AnnotationMaps.ts
+++ b/ui/src/types/Annotations.ts
@@ -2,11 +2,19 @@ export interface RegionInfo {
   elements: HTMLElement[];
   regionId: string;
 }
+
 export interface ComponentInfo {
   elements: HTMLElement[];
   componentUuid: string;
 }
 
+export interface SlotInfo {
+  element: HTMLElement;
+  componentUuid: string;
+  slotName: string;
+  stackDirection: StackDirection;
+}
+
 export type StackDirection =
   | 'vertical'
   | 'vertical-grid'
@@ -14,12 +22,12 @@ export type StackDirection =
   | 'horizontal-flex'
   | 'horizontal-grid';
 
-export interface SlotInfo {
-  element: HTMLElement;
-  componentUuid: string;
-  slotName: string;
-  stackDirection: StackDirection;
-}
+export type BoundingRect = {
+  top: number;
+  left: number;
+  width: number;
+  height: number;
+};
 
 export type RegionsMap = Record<string, RegionInfo>;
 export type ComponentsMap = Record<string, ComponentInfo>;
diff --git a/ui/src/utils/function-utils.ts b/ui/src/utils/function-utils.ts
index 9c2974427f510e569ea55e39056eb00059530a27..2df54f780f6e2b1068d8fc3d49f642ff9ae3317f 100644
--- a/ui/src/utils/function-utils.ts
+++ b/ui/src/utils/function-utils.ts
@@ -4,7 +4,8 @@ import type {
   ComponentsMap,
   RegionsMap,
   StackDirection,
-} from '@/types/AnnotationMaps';
+  BoundingRect,
+} from '@/types/Annotations';
 import type { PendingChanges } from '@/services/pendingChangesApi';
 
 export function handleNonWorkingBtn(): void {
@@ -46,18 +47,25 @@ export function parseValue(
 }
 
 /**
- * Calculates the horizontal and vertical distance between two DOM elements.
+ * Calculates the horizontal and vertical distance between the first and second passed element||group of elements.
  *
- * @param el1 - The first DOM element.
- * @param el2 - The second DOM element.
+ * @param el1 - The first element or array of elements.
+ * @param el2 - The second element or array of elements.
  * @returns An object containing the horizontal and vertical distances between the elements.
  */
 export function getDistanceBetweenElements(
-  el1: Element,
-  el2: Element,
+  el1: HTMLElement | HTMLElement[],
+  el2: HTMLElement | HTMLElement[],
 ): { horizontalDistance: number; verticalDistance: number } {
-  const rect1 = el1.getBoundingClientRect();
-  const rect2 = el2.getBoundingClientRect();
+  const rect1 = calculateBoundingRect(el1);
+  const rect2 = calculateBoundingRect(el2);
+
+  if (rect1 === null || rect2 === null) {
+    return {
+      horizontalDistance: 0,
+      verticalDistance: 0,
+    };
+  }
 
   // Calculate the horizontal and vertical distances
   const dx = rect2.left - rect1.left;
@@ -69,6 +77,89 @@ export function getDistanceBetweenElements(
   };
 }
 
+/**
+ * Calculates the bounding rectangle that encompasses all the provided elements.
+ *
+ * @param elements - A single DOM element or an array of DOM elements.
+ * @returns The bounding rectangle with properties: top, left, width, and height.
+ *          Returns null if elements are not provided or invalid.
+ */
+export function calculateBoundingRect(
+  elements: HTMLElement | HTMLElement[] | null,
+): BoundingRect | null {
+  if (!elements) {
+    return null;
+  }
+
+  const elementsArray = Array.isArray(elements) ? elements : [elements];
+  const expandedElements: HTMLElement[] = [];
+
+  elementsArray.forEach((el) => {
+    if (!el) {
+      return;
+    }
+
+    // elements that are display: contents; (e.g <astro-*> elements) take on the size of their children so in that case,
+    // we add the direct children to the array instead.
+    const style = window.getComputedStyle(el);
+    if (style.display === 'contents') {
+      expandedElements.push(
+        ...Array.from(el.children).filter(
+          (child): child is HTMLElement => child.nodeType === Node.ELEMENT_NODE,
+        ),
+      );
+    } else {
+      expandedElements.push(el);
+    }
+  });
+
+  const tops: number[] = [];
+  const lefts: number[] = [];
+  const rights: number[] = [];
+  const bottoms: number[] = [];
+
+  expandedElements.forEach((el) => {
+    const rect = el.getBoundingClientRect();
+    if (elemIsVisible(el)) {
+      tops.push(rect.top);
+      lefts.push(rect.left);
+      rights.push(rect.left + rect.width);
+      bottoms.push(rect.top + rect.height);
+    }
+  });
+
+  const minTop = getMinOfArray(tops);
+  const minLeft = getMinOfArray(lefts);
+  return {
+    top: minTop,
+    left: minLeft,
+    width: getMaxOfArray(rights) - minLeft,
+    height: getMaxOfArray(bottoms) - minTop,
+  };
+}
+
+export function elemIsVisible(elem: HTMLElement) {
+  return !!(
+    elem.offsetWidth ||
+    elem.offsetHeight ||
+    elem.getClientRects().length
+  );
+}
+
+export function getMaxOfArray(numArray: number[]) {
+  if (numArray.length === 0) {
+    return 0;
+  }
+  return Math.max.apply(null, numArray);
+}
+
+export function getMinOfArray(numArray: number[]) {
+  if (numArray.length === 0) {
+    return 0;
+  }
+  return Math.min.apply(null, numArray);
+}
+
 /**
  * Finds empty slots and inserts a placeholder div in between the xb-slot-start/xb-slot-end comments to show the user
  * where they can drop things.
@@ -397,7 +488,10 @@ export function findInChanges(
 }
 
 function getStackingDirection(container: HTMLElement): StackDirection {
-  const style = getComputedStyle(container);
+  let style = getComputedStyle(container);
+  if (style.display === 'contents' && container.parentElement) {
+    style = getComputedStyle(container.parentElement);
+  }
   const display = style.display;
 
   if (display.includes('flex')) {