Repository: department-of-veterans-affairs/vets-design-system-documentation
Pull request #3789: Resizing iframe heights for storybook examples
Author: login: babsdenney
Created: 2025-02-10T21:29:50Z | Merged: 2025-04-30T18:00:48Z

Discovered [this solution](https://blog.damato.design/posts/storybook-iframe-tango/) implemented to solve the issue with iframe height not adjusting correctly. It's challenging to test storybook loading locally, so moving this to a draft PR on GitHub to use the preview deployment to test.

#771 <!-- start placeholder for CI job -->
[Open Preview Environment](https://dev-design.va.gov/3789)
<!-- end placeholder -->


Diff:
diff --git a/src/_components/back-to-top.md b/src/_components/back-to-top.md
index 6b8258611..c0de64765 100644
--- a/src/_components/back-to-top.md
+++ b/src/_components/back-to-top.md
@@ -17,7 +17,7 @@ anchors:
 
 ### Desktop
 
-{% include storybook-preview.html height="300px" story="components-va-back-to-top--default" link_text="va-back-to-top" %}
+{% include storybook-preview.html height="300px" story="components-va-back-to-top--default" link_text="va-back-to-top" auto_resize=false %}
 
 ## Usage
 
diff --git a/src/_components/modal/index.md b/src/_components/modal/index.md
index c92f57554..67982039e 100644
--- a/src/_components/modal/index.md
+++ b/src/_components/modal/index.md
@@ -22,51 +22,51 @@ anchors:
 
 ### Default
 
-{% include storybook-preview.html story="uswds-va-modal--default" link_text="va-modal" %}
+{% include storybook-preview.html story="uswds-va-modal--default" link_text="va-modal" auto_resize=false height="300px" %}
 
 ### Info
 
-{% include storybook-preview.html story="uswds-va-modal--info" link_text="va-modal info" %}
+{% include storybook-preview.html story="uswds-va-modal--info" link_text="va-modal info" auto_resize=false height="320px" %}
 
 ### Continue
 
-{% include storybook-preview.html story="uswds-va-modal--continue" link_text="va-modal continue" %}
+{% include storybook-preview.html story="uswds-va-modal--continue" link_text="va-modal continue" auto_resize=false height="340px" %}
 
 ### Success
 
-{% include storybook-preview.html story="uswds-va-modal--success" link_text="va-modal success" %}
+{% include storybook-preview.html story="uswds-va-modal--success" link_text="va-modal success" auto_resize=false height="320px" %}
 
 ### Warning
 
-{% include storybook-preview.html story="uswds-va-modal--warning" link_text="va-modal warning" %}
+{% include storybook-preview.html story="uswds-va-modal--warning" link_text="va-modal warning" auto_resize=false height="320px" %}
 
 ### Error
 
-{% include storybook-preview.html story="uswds-va-modal--error" link_text="va-modal error" %}
+{% include storybook-preview.html story="uswds-va-modal--error" link_text="va-modal error" auto_resize=false height="320px" %}
 
 ### Click outside to close
 
-{% include storybook-preview.html story="uswds-va-modal--click-outside-to-close" link_text="va-modal click outside to close" %}
+{% include storybook-preview.html story="uswds-va-modal--click-outside-to-close" link_text="va-modal click outside to close" auto_resize=false height="300px" %}
 
 ### Without buttons
 
-{% include storybook-preview.html story="uswds-va-modal--without-buttons" link_text="va-modal without buttons" %}
+{% include storybook-preview.html story="uswds-va-modal--without-buttons" link_text="va-modal without buttons" auto_resize=false height="240px" %}
 
 ### Without title
 
-{% include storybook-preview.html story="uswds-va-modal--without-title" link_text="va-modal without title" %}
+{% include storybook-preview.html story="uswds-va-modal--without-title" link_text="va-modal without title" auto_resize=false height="240px" %}
 
 ### With nested web components
 
-{% include storybook-preview.html story="uswds-va-modal--with-nested-web-components" link_text="va-modal with nested web components" %}
+{% include storybook-preview.html story="uswds-va-modal--with-nested-web-components" link_text="va-modal with nested web components" auto_resize=false height="410px" %}
 
 ### Large
 
-{% include storybook-preview.html story="uswds-va-modal--large" link_text="va-modal large" %}
+{% include storybook-preview.html story="uswds-va-modal--large" link_text="va-modal large" auto_resize=false height="360px" %}
 
 ### With forced action
 
-{% include storybook-preview.html story="uswds-va-modal--with-forced-action" link_text="va-modal with forced action" %}
+{% include storybook-preview.html story="uswds-va-modal--with-forced-action" link_text="va-modal with forced action" auto_resize=false height="300px" %}
 
 ## Usage
 
diff --git a/src/_includes/head.html b/src/_includes/head.html
index 5dedd1c67..1f4b9d903 100644
--- a/src/_includes/head.html
+++ b/src/_includes/head.html
@@ -41,4 +41,4 @@
 
   <script src="{{ '/assets/javascripts/polyfills/array.from.js' | prepend: site.baseurl | append: cacheBust }}"></script>
 
-</head>
\ No newline at end of file
+</head>
diff --git a/src/_includes/storybook-preview.html b/src/_includes/storybook-preview.html
index 9077b7e90..73904b26a 100644
--- a/src/_includes/storybook-preview.html
+++ b/src/_includes/storybook-preview.html
@@ -1,6 +1,18 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 {% if include.scrollable %}
 <div
@@ -20,15 +32,59 @@
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 {% endif %}
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
+
+      try {
+        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
+        if (!iframeDoc) return;
+
+        const newHeight = iframeDoc.body.scrollHeight;
+
+        if (iframe.dataset.lastHeight != newHeight) {
+          iframe.style.height = newHeight + 35 + 'px';
+          iframe.dataset.lastHeight = newHeight;
+        }
+      } catch (err) {
+        console.warn('Cannot access iframe content due to cross-origin restrictions:', err);
+      }
+    });
+  }
+
+  setInterval(resizeIframes, 500);
+  window.addEventListener('load', resizeIframes);
+
+  document.querySelectorAll('iframe.storybook-embed').forEach((iframe) => {
+    iframe.addEventListener('load', () => {
+      iframe.style.opacity = 1;
+    });
+  });
+</script>


Reviews:
user: login: humancompanion-usds
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-28T16:53:25Z
user: login: jamigibbs
body: Good effort on this, Barb! I left a few questions but let me know if anything needs clarifying.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T13:40:02Z
user: login: humancompanion-usds
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T19:27:18Z
user: login: humancompanion-usds
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T19:29:33Z
user: login: humancompanion-usds
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T19:31:22Z
user: login: babsdenney
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T20:06:47Z
user: login: babsdenney
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T20:11:27Z
user: login: babsdenney
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T20:13:48Z
user: login: jamigibbs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:22:57Z
user: login: jamigibbs
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T12:23:14Z
user: login: humancompanion-usds
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-30T13:04:32Z

Review comments:
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
+
+      try {
+        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
+        if (!iframeDoc) return;
+
+        const newHeight = iframeDoc.body.scrollHeight;
+
+        if (iframe.dataset.lastHeight != newHeight) {
+          iframe.style.height = newHeight + 35 + 'px';
+          iframe.dataset.lastHeight = newHeight;
+        }
+      } catch (err) {
+        console.warn('Cannot access iframe content due to cross-origin restrictions:', err);
+      }
+    });
+  }
+
+  setInterval(resizeIframes, 500);
path: src/_includes/storybook-preview.html
user: login: jamigibbs
body: There might be some concern with using `setInterval`. This will keep running infinitely every 500 milliseconds and I wonder if it would introduce performance issues. 

Is it possible to just use `setTimeout`? Or did we find that to be difficult to determine the delay value?
created_at: 2025-04-29T13:31:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
path: src/_includes/storybook-preview.html
user: login: jamigibbs
body: It does not seem to work for the mobile stories:

https://dev-design.va.gov/3789/components/alert/#mobile

![Screenshot 2025-04-29 at 8 32 11 AM](https://github.com/user-attachments/assets/e0bd2d24-18c1-4a7d-a30a-785e246a4afc)

created_at: 2025-04-29T13:32:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
path: src/_includes/storybook-preview.html
user: login: jamigibbs
body: In what situations would we not want it to resize? Are there many stories that we have to manually set the `data-auto-resize` to false?
created_at: 2025-04-29T13:35:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
path: src/_includes/storybook-preview.html
user: login: jamigibbs
body: If this commented out line isn't needed, it should be removed.
created_at: 2025-04-29T13:38:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
+
+      try {
+        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
+        if (!iframeDoc) return;
+
+        const newHeight = iframeDoc.body.scrollHeight;
+
+        if (iframe.dataset.lastHeight != newHeight) {
+          iframe.style.height = newHeight + 35 + 'px';
+          iframe.dataset.lastHeight = newHeight;
+        }
+      } catch (err) {
+        console.warn('Cannot access iframe content due to cross-origin restrictions:', err);
+      }
+    });
+  }
+
+  setInterval(resizeIframes, 500);
path: src/_includes/storybook-preview.html
user: login: humancompanion-usds
body: I think this is what allows it to be responsive to the user interacting with the components in the iFrames, which I quite like. But I see the performance concerns. We could either give up on that or find another way to trigger the resize. But that seems difficult given that the components are in iFrames. Thoughts?
created_at: 2025-04-29T19:27:18Z
author_association: COLLABORATOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
path: src/_includes/storybook-preview.html
user: login: humancompanion-usds
body: Barb accounted for this under known issues:
> It seems to ignore sizing for mobile iframes. See [alerts](https://dev-design.va.gov/3789/components/alert/#mobile)

There are only 7 mobile components thus far. I'm willing to let this go until we figure out where the mobile components are going to ultimately live.
created_at: 2025-04-29T19:29:33Z
author_association: COLLABORATOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
path: src/_includes/storybook-preview.html
user: login: humancompanion-usds
body: Barb gave the example of Back to Top. I suspect there are a few more.
created_at: 2025-04-29T19:31:22Z
author_association: COLLABORATOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
+
+      try {
+        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
+        if (!iframeDoc) return;
+
+        const newHeight = iframeDoc.body.scrollHeight;
+
+        if (iframe.dataset.lastHeight != newHeight) {
+          iframe.style.height = newHeight + 35 + 'px';
+          iframe.dataset.lastHeight = newHeight;
+        }
+      } catch (err) {
+        console.warn('Cannot access iframe content due to cross-origin restrictions:', err);
+      }
+    });
+  }
+
+  setInterval(resizeIframes, 500);
path: src/_includes/storybook-preview.html
user: login: babsdenney
body: It would be ideal to fix the performance issues. I asked ChatGPT [about this](https://chatgpt.com/share/68112fe9-4d98-8010-ab46-bfff07b925bb) just now.  Two prompts about improving performance and then also keeping the feature of dynamically resizing iframes. 

<hr />

<html><head></head><body><p>Great follow-up — the answer depends on which method you choose and whether your iframes are same-origin or cross-origin.</p>
<p>Here's a breakdown of the options and their behavior when components inside the iframe (like modals, accordions, tabs, etc.) cause the height to change dynamically:</p>
<hr>
<h2>✅ 1. Using postMessage + ResizeObserver (inside Storybook)</h2>
<p>☑️ Best for cross-origin setups<br>
☑️ Resizes when content changes dynamically<br>
☑️ Efficient — no polling</p>
<p>This works even when content expands later (e.g., a modal opens, an accordion expands), because ResizeObserver tracks content size changes in the iframe and posts new height to the parent.</p>
<p>🛠️ Requires adding this JavaScript to the Storybook's preview-body.html:</p>
<pre><code class="language-html">&lt;script&gt;
(function () {
  const id = window.frameElement?.id || 'storybook';
  let lastHeight = 0;

  function postHeight() {
    const height = document.documentElement.scrollHeight;
    if (height !== lastHeight) {
      lastHeight = height;
      window.parent.postMessage({
        type: 'storybook-resize',
        id,
        height
      }, '*');
    }
  }

  const observer = new ResizeObserver(postHeight);
  observer.observe(document.body);

  // Fallback: post periodically in case ResizeObserver misses changes
  setInterval(postHeight, 500);

  // Trigger once on load
  window.addEventListener('DOMContentLoaded', postHeight);
})();
&lt;/script&gt;
</code></pre>
<p>And the parent page just listens:</p>
<pre><code class="language-js">window.addEventListener('message', (event) =&gt; {
  if (event.data?.type === 'storybook-resize') {
    const iframe = document.getElementById(event.data.id);
    if (iframe) {
      iframe.style.height = event.data.height + 'px';
    }
  }
});
</code></pre>
<p>✅ Result: Resizes whenever content changes — no constant polling on parent.</p>
<hr>
<h2>⚠️ 2. Using parent-side setInterval (your current approach)</h2>
<p>☑️ Works with same-origin iframes only<br>
☒ May not detect all content changes (e.g., transitions, async rendering)<br>
☒ Can be performance-intensive if run too frequently</p>
<p>Even if you're using setInterval every 500ms, it might miss resizes if the content animates in slowly or you have React portals (like modals outside the main DOM flow).</p>
<p>Also, iframes from another domain cannot be read by JavaScript in the parent (security restriction), so the interval will fail unless they're same-origin.</p>
<hr>
<h2>✅ Best Practice Summary</h2>

Approach | Cross-Origin | Dynamic Content | Performance
-- | -- | -- | --
postMessage + ResizeObserver | ✅ | ✅ | ✅
Parent-side setInterval | ❌ (X-origin) | ⚠️ Sometimes | ❌
Manual height via include | ✅ | ❌ | ✅


<p>—</p>
<p>💡 Recommendation:</p>
<p>Use the ResizeObserver + postMessage approach for dynamic resizing with minimal performance overhead — especially when your Storybook iframe is hosted separately (Netlify, GitHub Pages, etc.).</p>
<p>Would you like me to give you a drop-in version for your setup that only touches the parent and the preview-body.html inside Storybook?</p></body></html>
created_at: 2025-04-29T20:06:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
path: src/_includes/storybook-preview.html
user: login: babsdenney
body: I also used this feature to turn off the resize for the modals so we could manually apply the heights. I was thinking of manually applying the height to some of the components that still have a scroll bar, like the progress bar and expandable alert, so we can get to a state where there is no scrolling in the iframes. 
created_at: 2025-04-29T20:11:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
path: src/_includes/storybook-preview.html
user: login: babsdenney
body: > If this commented out line isn't needed, it should be removed.

Good catch! Thank you! 
created_at: 2025-04-29T20:13:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
+
+      try {
+        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
+        if (!iframeDoc) return;
+
+        const newHeight = iframeDoc.body.scrollHeight;
+
+        if (iframe.dataset.lastHeight != newHeight) {
+          iframe.style.height = newHeight + 35 + 'px';
+          iframe.dataset.lastHeight = newHeight;
+        }
+      } catch (err) {
+        console.warn('Cannot access iframe content due to cross-origin restrictions:', err);
+      }
+    });
+  }
+
+  setInterval(resizeIframes, 500);
path: src/_includes/storybook-preview.html
user: login: jamigibbs
body: > dynamically resizing iframes

In that case, using `ResizeObserver` would be preferred if it's possible. This is the approach we use for a few component-library components as well.

But it will require updating the Storybook package too in order to send a `message`. I think that's how Andres was [originally going to try and approach the problem](https://github.com/department-of-veterans-affairs/vets-design-system-documentation/issues/771):

> We might be able to dispatch an event in Storybook and update the height when we know this element exists in the DOM as a workaround.

Your AI suggestion for the `ResizeObserver` approach still wants to use setInterval as a fallback anyway.

```
// Fallback: post periodically in case ResizeObserver misses changes
setInterval(postHeight, 500);
```

I guess for the sake of time, we can go with the single `setInterval` approach but I suspect that, if there are issues loading Storybook (like for local development), we might see some odd behavior.
created_at: 2025-04-30T12:22:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,20 +1,77 @@
 {% assign storybook_path = site.storybook_path %}
-{% if include.is_mobile %}{% assign storybook_path = site.storybook_mobile_path %}{% endif %}
-{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&amp;viewMode=story' %}
+{% if include.is_mobile %}
+  {% assign storybook_path = site.storybook_mobile_path %}
+{% endif %}
+{% assign storybook_iframe_path = storybook_path | append: '/iframe.html?id=' | append: include.story | append: '&viewMode=story' %}
+
+<style>
+  iframe {
+    border: none;
+    width: 100%;
+    /* min-height: 300px; fallback height */
+    opacity: 0;
+    transition: opacity 0.3s ease;
+    display: block;
+  }
+</style>
 
 <div
   class="site-showcase"
   style="max-width: {{ include.width | default: '100%' }}"
 >
   <iframe
-    width="100%"
-    height="{{ include.height }}"
+    class="storybook-embed"
+    id="{{ include.story }}"
     src="{{ storybook_iframe_path }}"
+    title="Storybook: {{ include.story }}"
+    {% if include.auto_resize == false %}
+      data-auto-resize="false"
+      style="width: 100%; height: {{ include.height | default: '150px' }}; border: none;"
+    {% else %}
+      data-auto-resize="true"
+      style="width: 100%; border: none;"
+    {% endif %}
   ></iframe>
 </div>
 
 {% assign story_name_prefix = '' %}
-{% if include.is_mobile %}{% assign story_name_prefix = 'va-mobile_' %}{% endif %}
+{% if include.is_mobile %}
+  {% assign story_name_prefix = 'va-mobile_' %}
+{% endif %}
 {% assign storybook_preview_path = site.storybook_path | append: '/?path=/story/' | append: story_name_prefix | append: include.story %}
 
-[{% if include.link_text %}<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text}}{% else %}See this{% endif %} in Storybook]({{ storybook_preview_path }})
+[<img aria-hidden="true" role="img" src="{{ site.baseurl }}/images/storybook.svg" class="site-component-resources-links__icon" width="16px" alt="Storybook logo">View {{ include.link_text | default: 'this' }} in Storybook]({{ storybook_preview_path }})
+
+<script>
+  function resizeIframes() {
+    const iframes = document.querySelectorAll('iframe.storybook-embed');
+
+    iframes.forEach((iframe) => {
+      // Skip if data-auto-resize is explicitly false
+      if (iframe.dataset.autoResize === 'false') return;
+
+      try {
+        const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
+        if (!iframeDoc) return;
+
+        const newHeight = iframeDoc.body.scrollHeight;
+
+        if (iframe.dataset.lastHeight != newHeight) {
+          iframe.style.height = newHeight + 35 + 'px';
+          iframe.dataset.lastHeight = newHeight;
+        }
+      } catch (err) {
+        console.warn('Cannot access iframe content due to cross-origin restrictions:', err);
+      }
+    });
+  }
+
+  setInterval(resizeIframes, 500);
path: src/_includes/storybook-preview.html
user: login: humancompanion-usds
body: Given that it would be a fallback we could also push that setInterval time out a bit (1000? 1500?)
created_at: 2025-04-30T13:04:32Z
author_association: COLLABORATOR

PR comments:
user: login: humancompanion-usds
created_at: 2025-04-11T19:42:21Z
body: @babsdenney and @jamigibbs - Do we still need this draft PR? I seem to recall a PR going by that Jami was working on to do this same thing?
author_association: COLLABORATOR
user: login: jamigibbs
created_at: 2025-04-14T13:46:39Z
body: > Do we still need this draft PR? I seem to recall a PR going by that Jami was working on to do this same thing?

I unfortunately wasn't able to dedicate any more time to this I'll try to look at it a little more if I have a chance though. I've saved this PR link @babsdenney though if you want to close it for now.
author_association: CONTRIBUTOR
user: login: babsdenney
created_at: 2025-04-14T17:32:01Z
body: @humancompanion-usds @jamigibbs I asked @RobAWilkinson if he had time to look at it. He is working on something right now. 
author_association: CONTRIBUTOR
user: login: babsdenney
created_at: 2025-04-23T18:24:26Z
body: @danbrady @jamigibbs This is ready for review!

I tried using Copilot to fix this iframe resizer, but it didn't work very well and made the iframes take up the whole page. I'll have to get better at refining my prompts with copilots. I asked ChatGPT to fix it, and it was able to figure it out. [Here are my prompts](https://chatgpt.com/share/68092f13-7440-800c-9dd1-2166ff14ed9d) if you want to look. 

**Known issues**
- It takes a bit longer to load all the iframes when there are a lot of iframes on the page. See [checkbox](https://dev-design.va.gov/3789/components/form/checkbox) or [text input ](https://dev-design.va.gov/3789/components/form/text-input)for an example.
- It seems to ignore sizing for mobile iframes. See [alerts](https://dev-design.va.gov/3789/components/alert/#mobile)
- Still see scrolling on some iframes
  - [Progress bar - segmented](https://dev-design.va.gov/3789/components/form/progress-bar-segmented)
  - [Alert - Expandable](https://dev-design.va.gov/3789/components/alert/alert-expandable/)
- It doesn't recognize the resizing for [modals](https://dev-design.va.gov/3789/components/modal/) (I manually adjusted the iframes)
- Had to manually add a extra 30px to the computed height. It still seems to be missing some height settings in the iframe. There's probably some investigation that could be done to figure out the total size of the iframe and that could fix the issue with it not working with some of the iframes. 

**Features**
- When you click on an element inside of the iframe it will resize. See [accordions](https://dev-design.va.gov/3789/components/accordion)
- You can turn off the iframe resize by adding this to the iframe in the doc site - auto_resize=false. See the [Back to top](https://dev-design.va.gov/3789/components/back-to-top) page for example of this. Not sure where I should document this so others are aware.
- If the auto resize is turned off you can manually add the height


author_association: CONTRIBUTOR
user: login: humancompanion-usds
created_at: 2025-04-28T16:52:57Z
body: This is great @babsdenney !! Well done.
author_association: COLLABORATOR