{
"$type": "site.standard.document",
"content": {
"$type": "site.standard.content.markdown",
"text": "[Storybook](https://storybook.js.org/) is an essential part of design system development. It is considered an industry standard for creating components in isolation before they appear in production experiences. I’ve been working with the tool for nearly 8 years now and the team has really done a great job supporting the needs of our industry.\n\nThat said, we got a problem.\n\n## Leaky styles\n\nI lean heavily into Storybook as a documentation platform. The product also went in that direction when introducing the ability to write stories as MDX. They’ve since walked back that approach in more recent iterations but, it is clear there is still a desire for writing documentation in the Storybook ecosystem.\n\nWhen stories appear within documentation pages, the styles used to customize the documentation have a tendency to leak into the stories. As an example, if you are relying on the default body color to cascade down in your component, the styles of the documentation will also loosely target some of these things. In cases where your stories are shown in a different mode from the storybook, this can have an unintended presentation.\n\nThere’s a setting in Storybook that you can use which will create a barrier between the documentation styles and the stories.\n\n```\nexport const preview: Preview = {\n parameters: {\n docs: {\n story: { inline: false }\n }\n }\n}\n```\n\nThat configuration solves a problem, but introduces another one.\n\n## Out of the frying pan\n\nThe above setting renders each story in an `<iframe/>` which is great for encapsulation, but awful for presentation. Specifically, the element does not know how big its content will be. Usually, this is a quick fix since the content of the frame is also the same domain as the page. We could just query inside the `contentWindow` for the `body.scrollHeight` and apply that amount to the `<iframe/>`; problem solved!\n\nTo do that, we first need to grab a reference to all the `<iframe/>` elements. Normally, this is `document.getElementsByTagName('iframe')` *except* Storybook renders on the client-side. That is, these `<iframe/>` elements don’t appear until later in the application lifecycle. So we have to wait until the page has these elements. Unfortunately, `DOMContentLoaded` doesn’t work because that fires *before* the hydration, same goes for the `defer` keyword on the `<script/>` tag. So how are we supposed to get these `<iframe/>` elements?\n\nEnter my old friend, the Node Insertion hack. I first found [this approach](https://davidwalsh.name/detect-node-insertion) when working with registering web components. In fact, I speak about it within [my very first post](/posts/fetching-definitions/) in the blog. We can use the same approach to wait for `<iframe/>` elements to appear and grab their reference. First, add the following CSS to the `manager-head.html` file:\n\n```\niframe {\n visibility: hidden;\n animation: appear 0s linear forwards;\n}\n\n@keyframes appear {\n to { visibility: visible }\n}\n```\n\nNext, add a `<script/>` tag in the same file with the following:\n\n```\n\nwindow.addEventListener('animationend', onAnimationEnd);\n\nfunction onAnimationEnd(ev) {\n if (ev.animationName === 'appear') {\n handleIframe(ev.target);\n }\n}\n\nfunction handleIframe($iframe) {\n console.log($iframe);\n}\n```\n\n## This is not the element you are looking for\n\nWhen you run storybook, you should get a log in the console that identifies an `<iframe/>` element. When you dig a bit deeper, you’ll recognize that the element it returned is the preview `<iframe/>`, not the one used for stories on a docs page. To get those `<iframe/>` elements, you’ll need to listen *inside the preview* also! Luckily, we can do this with a small change; update the `preview-head.html` to include the same CSS that we added to the `manager-head.html`. Let’s also update our `handleIframe()` function:\n\n```\nfunction handleIframe($iframe) {\n if ($iframe.id === 'storybook-preview-iframe') {\n $iframe.addEventListener('load', onLoad);\n onLoad.call($iframe);\n } else {\n adjustHeight($iframe);\n }\n}\n\nfunction onLoad() {\n this.contentWindow.addEventListener('animationend', onAnimationEnd);\n}\n\nfunction adjustHeight($iframe) {\n console.log($iframe);\n}\n```\n\nNow, after you restart Storybook, the console should identify each individual Story `<iframe/>`. The next part is what we’ve been waiting to do. We want to get the height of the internal `<body/>` element and make the `<iframe/>` respect the size of the content. Storybook applies the `height` to the parent of the `<iframe/>` so we’ll need to get that in the process.\n\n```\nfunction adjustHeight($iframe) {\n const $body = $iframe.contentWindow.document.body;\n const $parent = $iframe.parentElement;\n const currentHeight = $parent.style.height;\n\n if (currentHeight === '400px') {\n $parent.style.height = $body.scrollHeight + 'px';\n }\n}\n```\n\nIn this function, we’re checking if the `<iframe/>` is the default set by Storybook (`400px`). If it is, we update the height based on the contents of the `<body/>`. This allows you to set the `iframeHeight` as a parameter is specific stories that should be a fixed height. This is helpful for stories that have content shifts, such as loading images.\n\n## Shadow DOM when?\n\nAt this point, I’m pretty sure that all of this could be avoided if the stories were rendered within a Shadow DOM. Maybe once I get truly bored I could start looking to see how I might do that. But in the meantime, check out the new version of the [DAMATO Design System](https://system.damato.design) where I expect to publish real working examples of my approaches. It’s already been a big help in my regular discourse. More to come there over time.",
"version": "1.0"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreidbsdgyt7zwakdfyevcch4jgn22tsonivpon5yn5jgrkwn5zec4na"
},
"mimeType": "image/png",
"size": 246679
},
"description": "Yo dawg, I heard you like iframes. Said no one, ever.",
"path": "/posts/storybook-iframe-tango",
"publishedAt": "2024-11-01T00:00:00.000Z",
"site": "at://did:plc:l2wkngwge5j3vq2njktpvctx/site.standard.publication/3iweqk3ps22rk",
"tags": [
"storybook"
],
"textContent": "Storybook is an essential part of design system development. It is considered an industry standard for creating components in isolation before they appear in production experiences. I’ve been working with the tool for nearly 8 years now and the team has really done a great job supporting the needs of our industry.\n\nThat said, we got a problem.\n\nLeaky styles\n\nI lean heavily into Storybook as a documentation platform. The product also went in that direction when introducing the ability to write stories as MDX. They’ve since walked back that approach in more recent iterations but, it is clear there is still a desire for writing documentation in the Storybook ecosystem.\n\nWhen stories appear within documentation pages, the styles used to customize the documentation have a tendency to leak into the stories. As an example, if you are relying on the default body color to cascade down in your component, the styles of the documentation will also loosely target some of these things. In cases where your stories are shown in a different mode from the storybook, this can have an unintended presentation.\n\nThere’s a setting in Storybook that you can use which will create a barrier between the documentation styles and the stories.\n\nThat configuration solves a problem, but introduces another one.\n\nOut of the frying pan\n\nThe above setting renders each story in an which is great for encapsulation, but awful for presentation. Specifically, the element does not know how big its content will be. Usually, this is a quick fix since the content of the frame is also the same domain as the page. We could just query inside the for the and apply that amount to the ; problem solved!\n\nTo do that, we first need to grab a reference to all the elements. Normally, this is except Storybook renders on the client-side. That is, these elements don’t appear until later in the application lifecycle. So we have to wait until the page has these elements. Unfortunately, doesn’t work because that fires before the hydration, same goes for the keyword on the tag. So how are we supposed to get these elements?\n\nEnter my old friend, the Node Insertion hack. I first found this approach when working with registering web components. In fact, I speak about it within my very first post in the blog. We can use the same approach to wait for elements to appear and grab their reference. First, add the following CSS to the file:\n\nNext, add a tag in the same file with the following:\n\nThis is not the element you are looking for\n\nWhen you run storybook, you should get a log in the console that identifies an element. When you dig a bit deeper, you’ll recognize that the element it returned is the preview , not the one used for stories on a docs page. To get those elements, you’ll need to listen inside the preview also! Luckily, we can do this with a small change; update the to include the same CSS that we added to the . Let’s also update our function:\n\nNow, after you restart Storybook, the console should identify each individual Story . The next part is what we’ve been waiting to do. We want to get the height of the internal element and make the respect the size of the content. Storybook applies the to the parent of the so we’ll need to get that in the process.\n\nIn this function, we’re checking if the is the default set by Storybook (). If it is, we update the height based on the contents of the . This allows you to set the as a parameter is specific stories that should be a fixed height. This is helpful for stories that have content shifts, such as loading images.\n\nShadow DOM when?\n\nAt this point, I’m pretty sure that all of this could be avoided if the stories were rendered within a Shadow DOM. Maybe once I get truly bored I could start looking to see how I might do that. But in the meantime, check out the new version of the DAMATO Design System where I expect to publish real working examples of my approaches. It’s already been a big help in my regular discourse. More to come there over time.",
"title": "Storybook iframe tango",
"updatedAt": "2026-06-18T17:06:50.701Z"
}