Repository: elastic/eui
Pull request #8499: [EUI+] Update fullscreen demos
Author: login: weronikaolejniczak
Labels: name: skip-changelog
description: Use on PRs to skip changelog requirement (Don't delete - used for automation)
Created: 2025-03-25T14:29:30Z | Merged: 2025-04-02T12:05:36Z

## Summary

Closes #8151

On this PR, I:

- modify the new docs release pipeline to save the PR url (`pr_8499`) in an environment variable,
- feed the environment variable into [customFields](https://docusaurus.io/docs/api/docusaurus-config#customFields) in `docusaurus.config.ts`,
- add a reusable Storybook link component that abstracts the environment-based redirection logic using [useDocusaurusContext](https://docusaurus.io/docs/next/docusaurus-core#useDocusaurusContext) to grab the PR url,
- add stories for all fullscreen examples (list below in QA section),
- update the examples using `<StorybookLink />` component.

![Screenshot 2025-03-27 at 16 19 51](https://github.com/user-attachments/assets/c59f95dc-25c0-44da-8d30-8233fa3b2e70)

## QA

**Locally:**

1. Checkout the branch: `gh pr checkout 8499`.
2. Run `yarn && yarn workspace @elastic/eui-website build:workspaces && yarn workspace @elastic/eui-website start`.
3. Run `yarn workspace @elastic/eui storybook` in another terminal window.

**Staging:**

 Access the staging environment (links below).

**Examples:**

- [ ] [Layout > Header > "The Elastic navigation pattern"](https://eui.elastic.co/pr_8499/new-docs/docs/layout/header/#putting-it-all-together)
- [ ] [Navigation > Collapsible nav > "Full pattern with header and saved pins"](http://eui.elastic.co/pr_8499/new-docs/docs/navigation/collapsible-nav/#putting-it-all-together)

Linked issue #8151: [EUI+] Update fullscreen demos
> [!WARNING]
> Blocked by https://github.com/elastic/eui-private/issues/257

The purpose of this task is to update the fullscreen demos because they are completely broken due to the custom behavior.

---

**Affected examples**

- `EuiHeader`’s Elastic navigation pattern example
- `EuiTour`'s Fullscreen demo example
- `EuiCollapsibleNav`'s Full pattern with header and saved pins example


Diff:
diff --git a/.buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh b/.buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh
index df2598dce0b7..a3ae1d3968f4 100644
--- a/.buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh
+++ b/.buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh
@@ -16,7 +16,9 @@ echo "Yarn version: $(yarn -v)"
 
 # Calculate paths and directories
 if [[ -n "${BUILDKITE_PULL_REQUEST}" ]] && [[ "${BUILDKITE_PULL_REQUEST}" != "false" ]]; then
-  bucket_directory="pr_${BUILDKITE_PULL_REQUEST}/new-docs/"
+  PR_SLUG="pr_${BUILDKITE_PULL_REQUEST}"
+  export STORYBOOK_BASE_URL="https://eui.elastic.co/${PR_SLUG}/storybook"
+  bucket_directory="${PR_SLUG}/new-docs/"
   echo "Detected a PR preview environment configuration. The built files will be copied to ${bucket_directory}"
 else
   echo "The script has been triggered with no pull request or tag information. This is a no-op."
diff --git a/packages/eui/src/components/collapsible_nav/collapsible_nav.stories.tsx b/packages/eui/src/components/collapsible_nav/collapsible_nav.stories.tsx
index 25cf3234b961..77dde24f100e 100644
--- a/packages/eui/src/components/collapsible_nav/collapsible_nav.stories.tsx
+++ b/packages/eui/src/components/collapsible_nav/collapsible_nav.stories.tsx
@@ -9,10 +9,32 @@
 import React, { useState } from 'react';
 import type { Meta, StoryObj } from '@storybook/react';
 import { action } from '@storybook/addon-actions';
-import { disableStorybookControls } from '../../../.storybook/utils';
+import { css } from '@emotion/react';
+import { find, findIndex } from 'lodash';
 
-import { EuiButton } from '../button';
+import { EuiButton, EuiButtonIcon } from '../button';
 import { EuiCollapsibleNav, EuiCollapsibleNavProps } from './collapsible_nav';
+import { EuiCollapsibleNavGroup } from './collapsible_nav_group';
+import { EuiFlexItem } from '../flex';
+import {
+  EuiHeaderSectionItemButton,
+  EuiHeaderLogo,
+  EuiHeader,
+} from '../header';
+import { EuiHorizontalRule } from '../horizontal_rule';
+import { EuiIcon } from '../icon';
+import { EuiLink } from '../link';
+import { EuiPageTemplate } from '../page_template';
+import {
+  EuiPinnableListGroupItemProps,
+  EuiListGroup,
+  EuiPinnableListGroup,
+} from '../list_group';
+import { EuiText } from '../text';
+import { disableStorybookControls } from '../../../.storybook/utils';
+import { logicalCSSWithFallback } from '../../global_styling/functions';
+import { useGeneratedHtmlId } from '../../services';
+import { EuiCode } from '../code';
 
 const meta: Meta<EuiCollapsibleNavProps> = {
   title: 'Navigation/EuiCollapsibleNav/EuiCollapsibleNav',
@@ -72,3 +94,335 @@ export const Playground: Story = {
     isOpen: true,
   },
 };
+
+/**
+ * Docs fullscreen examples
+ */
+
+const KibanaNavLinks: EuiPinnableListGroupItemProps[] = [
+  { label: 'Discover' },
+  { label: 'Visualize' },
+  { label: 'Dashboards' },
+  { label: 'Canvas' },
+  { label: 'Maps' },
+  { label: 'Machine Learning' },
+  { label: 'Graph' },
+];
+
+const TopLinks: EuiPinnableListGroupItemProps[] = [
+  {
+    label: 'Home',
+    iconType: 'home',
+    isActive: true,
+    'aria-current': true,
+    onClick: () => {},
+    pinnable: false,
+  },
+];
+
+const KibanaLinks: EuiPinnableListGroupItemProps[] = KibanaNavLinks.map(
+  (link) => {
+    return {
+      ...link,
+      onClick: () => {},
+    };
+  }
+);
+
+const LearnLinks: EuiPinnableListGroupItemProps[] = [
+  { label: 'Docs', onClick: () => {} },
+  { label: 'Blogs', onClick: () => {} },
+  { label: 'Webinars', onClick: () => {} },
+  { label: 'Elastic.co', onClick: () => {} },
+];
+
+const FullHeaderPatternExample = () => {
+  const [navIsOpen, setNavIsOpen] = useState(true);
+
+  // Accordion toggling
+  const [openGroups, setOpenGroups] = useState(
+    JSON.parse(String(localStorage.getItem('openNavGroups'))) || [
+      'Kibana',
+      'Learn',
+    ]
+  );
+
+  // Save which groups are open and which are not with state and local store
+  const toggleAccordion = (isOpen: boolean, title?: string) => {
+    if (!title) return;
+    const itExists = openGroups.includes(title);
+    if (isOpen) {
+      if (itExists) return;
+      openGroups.push(title);
+    } else {
+      const index = openGroups.indexOf(title);
+      if (index > -1) {
+        openGroups.splice(index, 1);
+      }
+    }
+    setOpenGroups([...openGroups]);
+    localStorage.setItem('openNavGroups', JSON.stringify(openGroups));
+  };
+
+  // Pinning
+  const [pinnedItems, setPinnedItems] = useState<
+    EuiPinnableListGroupItemProps[]
+  >(JSON.parse(String(localStorage.getItem('pinnedItems'))) || []);
+
+  const addPin = (item: any) => {
+    if (!item || find(pinnedItems, { label: item.label })) {
+      return;
+    }
+    item.pinned = true;
+    const newPinnedItems = pinnedItems ? pinnedItems.concat(item) : [item];
+    setPinnedItems(newPinnedItems);
+    localStorage.setItem('pinnedItems', JSON.stringify(newPinnedItems));
+  };
+
+  const removePin = (item: any) => {
+    const pinIndex = findIndex(pinnedItems, { label: item.label });
+    if (pinIndex > -1) {
+      item.pinned = false;
+      const newPinnedItems = pinnedItems;
+      newPinnedItems.splice(pinIndex, 1);
+      setPinnedItems([...newPinnedItems]);
+      localStorage.setItem('pinnedItems', JSON.stringify(newPinnedItems));
+    }
+  };
+
+  function alterLinksWithCurrentState(
+    links: EuiPinnableListGroupItemProps[],
+    showPinned = false
+  ): EuiPinnableListGroupItemProps[] {
+    return links.map((link) => {
+      const { pinned, ...rest } = link;
+      return {
+        pinned: showPinned ? pinned : false,
+        ...rest,
+      };
+    });
+  }
+
+  function addLinkNameToPinTitle(listItem: EuiPinnableListGroupItemProps) {
+    return `Pin ${listItem.label} to top`;
+  }
+
+  function addLinkNameToUnpinTitle(listItem: EuiPinnableListGroupItemProps) {
+    return `Unpin ${listItem.label}`;
+  }
+
+  const collapsibleNavId = useGeneratedHtmlId({ prefix: 'collapsibleNav' });
+
+  const collapsibleNav = (
+    <EuiCollapsibleNav
+      id={collapsibleNavId}
+      aria-label="Main navigation"
+      isOpen={navIsOpen}
+      button={
+        <EuiHeaderSectionItemButton
+          aria-label="Toggle main navigation"
+          onClick={() => setNavIsOpen(!navIsOpen)}
+        >
+          <EuiIcon type={'menu'} size="m" aria-hidden="true" />
+        </EuiHeaderSectionItemButton>
+      }
+      onClose={() => setNavIsOpen(false)}
+      // Accessibility - Add scroll to nav on very small screens
+      css={css`
+        @media (max-height: 15em) {
+          ${logicalCSSWithFallback('overflow-y', 'auto')}
+        }
+      `}
+    >
+      {/* Dark deployments section */}
+      <EuiFlexItem grow={false} style={{ flexShrink: 0 }}>
+        <EuiCollapsibleNavGroup isCollapsible={false} background="dark">
+          <EuiListGroup
+            maxWidth="none"
+            gutterSize="none"
+            size="s"
+            listItems={[
+              {
+                label: 'Manage deployment',
+                onClick: () => {},
+                iconType: 'logoCloud',
+                iconProps: {
+                  color: 'ghost',
+                },
+              },
+            ]}
+          />
+        </EuiCollapsibleNavGroup>
+      </EuiFlexItem>
+
+      {/* Shaded pinned section always with a home item */}
+      <EuiFlexItem grow={false} style={{ flexShrink: 0 }}>
+        <EuiCollapsibleNavGroup
+          background="light"
+          style={{ maxHeight: '40vh' }}
+          className="eui-yScroll"
+        >
+          <EuiPinnableListGroup
+            aria-label="Pinned links" // A11y: Since this group doesn't have a visible `title` it should be provided an accessible description
+            listItems={alterLinksWithCurrentState(TopLinks).concat(
+              alterLinksWithCurrentState(pinnedItems, true)
+            )}
+            unpinTitle={addLinkNameToUnpinTitle}
+            onPinClick={removePin}
+            maxWidth="none"
+            color="text"
+            gutterSize="none"
+            size="s"
+          />
+        </EuiCollapsibleNavGroup>
+      </EuiFlexItem>
+
+      <EuiHorizontalRule margin="none" />
+
+      {/* BOTTOM */}
+      <EuiFlexItem
+        className="eui-yScroll"
+        // Accessibility - Allows nav items to be seen and interacted with on very small screen sizes
+        css={css`
+          @media (max-height: 15em) {
+            flex: 1 0 auto;
+          }
+        `}
+      >
+        {/* Kibana section */}
+        <EuiCollapsibleNavGroup
+          title={
+            <a
+              className="eui-textInheritColor"
+              onClick={(e) => e.stopPropagation()}
+            >
+              Kibana
+            </a>
+          }
+          buttonElement="div"
+          iconType="logoKibana"
+          isCollapsible={true}
+          initialIsOpen={openGroups.includes('Kibana')}
+          onToggle={(isOpen: boolean) => toggleAccordion(isOpen, 'Kibana')}
+        >
+          <EuiPinnableListGroup
+            aria-label="Kibana" // A11y: EuiCollapsibleNavGroup can't correctly pass the `title` as the `aria-label` to the right HTML element, so it must be added manually
+            listItems={alterLinksWithCurrentState(KibanaLinks)}
+            pinTitle={addLinkNameToPinTitle}
+            onPinClick={addPin}
+            maxWidth="none"
+            color="subdued"
+            gutterSize="none"
+            size="s"
+          />
+        </EuiCollapsibleNavGroup>
+
+        {/* Security callout */}
+        <EuiCollapsibleNavGroup
+          background="light"
+          iconType="logoSecurity"
+          title="Elastic Security"
+          isCollapsible={true}
+          initialIsOpen={true}
+          arrowDisplay="none"
+          extraAction={
+            <EuiButtonIcon
+              aria-label="Hide and never show again"
+              title="Hide and never show again"
+              iconType="cross"
+            />
+          }
+        >
+          <EuiText size="s" color="subdued" style={{ padding: '0 8px 8px' }}>
+            <p>
+              Threat prevention, detection, and response with SIEM and endpoint
+              security.
+              <br />
+              <EuiLink>Learn more</EuiLink>
+            </p>
+          </EuiText>
+        </EuiCollapsibleNavGroup>
+
+        {/* Learn section */}
+        <EuiCollapsibleNavGroup
+          title={
+            <a
+              className="eui-textInheritColor"
+              onClick={(e) => e.stopPropagation()}
+            >
+              Training
+            </a>
+          }
+          buttonElement="div"
+          iconType="training"
+          isCollapsible={true}
+          initialIsOpen={openGroups.includes('Learn')}
+          onToggle={(isOpen: boolean) => toggleAccordion(isOpen, 'Learn')}
+        >
+          <EuiPinnableListGroup
+            aria-label="Learn" // A11y: EuiCollapsibleNavGroup can't correctly pass the `title` as the `aria-label` to the right HTML element, so it must be added manually
+            listItems={alterLinksWithCurrentState(LearnLinks)}
+            pinTitle={addLinkNameToPinTitle}
+            onPinClick={addPin}
+            maxWidth="none"
+            color="subdued"
+            gutterSize="none"
+            size="s"
+          />
+        </EuiCollapsibleNavGroup>
+      </EuiFlexItem>
+
+      <EuiFlexItem grow={false}>
+        {/* Span fakes the nav group into not being the first item and therefore adding a top border */}
+        <span />
+        <EuiCollapsibleNavGroup>
+          <EuiButton fill fullWidth iconType="plusInCircleFilled">
+            Add data
+          </EuiButton>
+        </EuiCollapsibleNavGroup>
+      </EuiFlexItem>
+    </EuiCollapsibleNav>
+  );
+
+  const leftSectionItems = [
+    collapsibleNav,
+    <EuiHeaderLogo iconType="logoElastic">Elastic</EuiHeaderLogo>,
+  ];
+
+  return (
+    <>
+      <EuiHeader
+        position="fixed"
+        sections={[
+          {
+            items: leftSectionItems,
+          },
+        ]}
+      />
+
+      <EuiPageTemplate>
+        <EuiPageTemplate.Header>
+          <EuiText>
+            <h1>Collapsible Nav</h1>
+          </EuiText>
+        </EuiPageTemplate.Header>
+        <EuiPageTemplate.Section>
+          <EuiText>
+            <h2>Full pattern with header and saved pins</h2>
+            <p>
+              This story showcases <EuiCode>EuiHeader</EuiCode> with a toggle
+              button to open an
+              <EuiCode>EuiCollapsibleNav</EuiCode>, the contents of which are
+              multiple EuiCollapsibleNavGroups and saves the open/closed/pinned
+              state for each section and item in local store.
+            </p>
+          </EuiText>
+        </EuiPageTemplate.Section>
+      </EuiPageTemplate>
+    </>
+  );
+};
+
+export const FullHeaderPattern: Story = {
+  render: ({ ...args }) => <FullHeaderPatternExample {...args} />,
+};
diff --git a/packages/eui/src/components/header/header.stories.tsx b/packages/eui/src/components/header/header.stories.tsx
index a10a86299b6b..0b90e57f612e 100644
--- a/packages/eui/src/components/header/header.stories.tsx
+++ b/packages/eui/src/components/header/header.stories.tsx
@@ -8,21 +8,35 @@
 
 import React, { useState } from 'react';
 import type { Meta, StoryObj } from '@storybook/react';
-import { LOKI_SELECTORS } from '../../../.storybook/loki';
 
+import { LOKI_SELECTORS } from '../../../.storybook/loki';
 import {
-  EuiHeaderLogo,
-  EuiHeaderSectionItemButton,
-  EuiHeaderLinks,
-  EuiHeaderLink,
-  EuiSelectableTemplateSitewide,
-  EuiIcon,
   EuiAvatar,
+  EuiBadge,
   EuiButton,
+  EuiCollapsibleNav,
+  EuiCollapsibleNavGroup,
+  EuiFlexItem,
   EuiFlyout,
+  EuiFlyoutBody,
+  EuiFlyoutHeader,
+  EuiHeaderLink,
+  EuiHeaderLinks,
+  EuiHeaderLogo,
+  EuiHeaderSectionItemButton,
+  EuiIcon,
+  EuiLink,
+  EuiListGroupItem,
   EuiPageTemplate,
+  EuiPopover,
+  EuiPortal,
+  EuiSelectableMessage,
+  EuiSelectableTemplateSitewide,
+  EuiShowFor,
+  EuiText,
+  EuiTitle,
 } from '../../components';
-
+import { useEuiTheme, useGeneratedHtmlId } from '../../services';
 import { EuiHeader, EuiHeaderProps } from './header';
 
 const meta: Meta<EuiHeaderProps> = {
@@ -230,3 +244,339 @@ export const MultipleFixedHeaders: Story = {
   },
   render: (args) => <MultipleFixedHeadersExample {...args} />,
 };
+
+/**
+ * Docs fullscreen examples
+ */
+
+const ElasticNavigationPatternExample = () => {
+  const { euiTheme } = useEuiTheme();
+
+  const guideHeaderCollapsibleNavId = useGeneratedHtmlId({
+    prefix: 'guideHeaderCollapsibleNav',
+  });
+  const guideHeaderAlertFlyoutId = useGeneratedHtmlId({
+    prefix: 'guideHeaderAlertFlyout',
+  });
+  const guideHeaderAlertFlyoutTitleId = useGeneratedHtmlId({
+    prefix: 'guideHeaderAlertFlyoutTitle',
+  });
+  const guideHeaderUserPopoverId = useGeneratedHtmlId({
+    prefix: 'guideHeaderUserPopover',
+  });
+  const guideHeaderSpacesPopoverId = useGeneratedHtmlId({
+    prefix: 'guideHeaderSpacesPopover',
+  });
+  const guideHeaderDeploymentPopoverId = useGeneratedHtmlId({
+    prefix: 'guideHeaderDeploymentPopover',
+  });
+
+  /**
+   * Collapsible Nav
+   */
+  const [navIsOpen, setNavIsOpen] = useState(
+    JSON.parse(String(localStorage.getItem('navIsDocked'))) || false
+  );
+  const [navIsDocked, setNavIsDocked] = useState(
+    JSON.parse(String(localStorage.getItem('navIsDocked'))) || false
+  );
+  const collapsibleNav = (
+    <EuiCollapsibleNav
+      id={guideHeaderCollapsibleNavId}
+      aria-label="Main navigation"
+      isOpen={navIsOpen}
+      isDocked={navIsDocked}
+      button={
+        <EuiHeaderSectionItemButton
+          aria-label="Toggle main navigation"
+          onClick={() => setNavIsOpen(!navIsOpen)}
+        >
+          <EuiIcon type={'menu'} size="m" aria-hidden="true" />
+        </EuiHeaderSectionItemButton>
+      }
+      onClose={() => setNavIsOpen(false)}
+    >
+      <EuiFlexItem className="eui-yScroll">
+        {/* Docs callout */}
+        <EuiCollapsibleNavGroup background="none" title="EuiCollapsibleNav">
+          <EuiText size="s" color="subdued" style={{ padding: '0 8px 8px' }}>
+            <p>
+              Please see the component page for{' '}
+              <EuiLink href="https://eui.elastic.co/#/navigation/collapsible-nav">
+                <strong>EuiCollapsibleNav</strong>
+              </EuiLink>{' '}
+              on how to configure your navigation.
+            </p>
+          </EuiText>
+        </EuiCollapsibleNavGroup>
+      </EuiFlexItem>
+      <EuiFlexItem grow={false}>
+        {/* Docking button only for larger screens that can support it*/}
+        <EuiShowFor sizes={['l', 'xl']}>
+          <EuiCollapsibleNavGroup>
+            <EuiListGroupItem
+              size="xs"
+              color="subdued"
+              label={`${navIsDocked ? 'Undock' : 'Dock'} navigation`}
+              onClick={() => {
+                setNavIsDocked(!navIsDocked);
+                localStorage.setItem(
+                  'navIsDocked',
+                  JSON.stringify(!navIsDocked)
+                );
+              }}
+              iconType={navIsDocked ? 'lock' : 'lockOpen'}
+            />
+          </EuiCollapsibleNavGroup>
+        </EuiShowFor>
+      </EuiFlexItem>
+    </EuiCollapsibleNav>
+  );
+
+  /**
+   * Header Alerts
+   */
+  const [isAlertFlyoutVisible, setIsAlertFlyoutVisible] = useState(false);
+  const headerAlerts = (
+    <EuiPortal>
+      <EuiFlyout
+        onClose={() => setIsAlertFlyoutVisible(false)}
+        size="s"
+        id={guideHeaderAlertFlyoutId}
+        aria-labelledby={guideHeaderAlertFlyoutTitleId}
+      >
+        <EuiFlyoutHeader hasBorder>
+          <EuiTitle size="s">
+            <h2 id={guideHeaderAlertFlyoutTitleId}>EuiHeaderAlert</h2>
+          </EuiTitle>
+        </EuiFlyoutHeader>
+        <EuiFlyoutBody>
+          <EuiText size="s" color="subdued">
+            <p>
+              Please see the component page for{' '}
+              <EuiLink href="https://eui.elastic.co/#/layout/header#header-notifications">
+                <strong>EuiHeaderAlert</strong>
+              </EuiLink>{' '}
+              on how to configure your alerts.
+            </p>
+          </EuiText>
+        </EuiFlyoutBody>
+      </EuiFlyout>
+    </EuiPortal>
+  );
+
+  /**
+   * User Menu
+   */
+  const [isUserMenuVisible, setIsUserMenuVisible] = useState(false);
+  const userMenu = (
+    <EuiPopover
+      id={guideHeaderUserPopoverId}
+      repositionOnScroll
+      button={
+        <EuiHeaderSectionItemButton
+          aria-controls={guideHeaderUserPopoverId}
+          aria-expanded={isUserMenuVisible}
+          aria-haspopup="true"
+          aria-label="User menu"
+          onClick={() => setIsUserMenuVisible(!isUserMenuVisible)}
+        >
+          <EuiAvatar name="John Username" size="s" />
+        </EuiHeaderSectionItemButton>
+      }
+      isOpen={isUserMenuVisible}
+      anchorPosition="downRight"
+      closePopover={() => setIsUserMenuVisible(false)}
+    >
+      <div style={{ width: 320 }}>
+        <EuiText size="s" color="subdued">
+          <p>
+            Please see the component page for{' '}
+            <EuiLink href="https://eui.elastic.co/#/layout/header">
+              <strong>EuiHeader</strong>
+            </EuiLink>{' '}
+            on how to configure your user menu.
+          </p>
+        </EuiText>
+      </div>
+    </EuiPopover>
+  );
+
+  /**
+   * Spaces Menu
+   */
+  const [isSpacesMenuVisible, setIsSpacesMenuVisible] = useState(false);
+  const spacesMenu = (
+    <EuiPopover
+      id={guideHeaderSpacesPopoverId}
+      repositionOnScroll
+      button={
+        <EuiHeaderSectionItemButton
+          aria-controls={guideHeaderSpacesPopoverId}
+          aria-expanded={isSpacesMenuVisible}
+          aria-haspopup="true"
+          aria-label="Spaces menu"
+          onClick={() => setIsSpacesMenuVisible(!isSpacesMenuVisible)}
+        >
+          <EuiAvatar type="space" name="Default Space" size="s" />
+        </EuiHeaderSectionItemButton>
+      }
+      isOpen={isSpacesMenuVisible}
+      anchorPosition="downRight"
+      closePopover={() => setIsSpacesMenuVisible(false)}
+    >
+      <div style={{ width: 320 }}>
+        <EuiText size="s" color="subdued">
+          <p>
+            Please see the component page for{' '}
+            <EuiLink href="https://eui.elastic.co/#/layout/header">
+              <strong>EuiHeader</strong>
+            </EuiLink>{' '}
+            on how to configure your spaces menu.
+          </p>
+        </EuiText>
+      </div>
+    </EuiPopover>
+  );
+
+  /**
+   * Deployment Menu
+   */
+  const [isDeploymentMenuVisible, setIsDeploymentMenuVisible] = useState(false);
+  const deploymentMenu = (
+    <EuiPopover
+      id={guideHeaderDeploymentPopoverId}
+      repositionOnScroll
+      button={
+        <EuiBadge
+          color={euiTheme.colors.darkestShade}
+          iconType="arrowDown"
+          iconSide="right"
+          aria-controls={guideHeaderDeploymentPopoverId}
+          aria-expanded={isDeploymentMenuVisible}
+          aria-haspopup="true"
+          onClickAriaLabel="Current deployment: Production logs. Click to open deployment menu."
+          onClick={() => setIsDeploymentMenuVisible(!isDeploymentMenuVisible)}
+        >
+          Production logs
+        </EuiBadge>
+      }
+      isOpen={isDeploymentMenuVisible}
+      anchorPosition="downRight"
+      closePopover={() => setIsDeploymentMenuVisible(false)}
+    >
+      <EuiText size="s" color="subdued">
+        <p>Deployment menu pattern TBD</p>
+      </EuiText>
+    </EuiPopover>
+  );
+
+  /**
+   * Sitewide search
+   */
+  const search = (
+    <EuiSelectableTemplateSitewide
+      options={[]}
+      searchProps={{
+        append: '⌘K',
+        compressed: true,
+      }}
+      popoverButton={
+        <EuiHeaderSectionItemButton aria-label="Sitewide search">
+          <EuiIcon type="search" size="m" />
+        </EuiHeaderSectionItemButton>
+      }
+      popoverButtonBreakpoints={['xs', 's']}
+      popoverProps={{
+        repositionOnScroll: true, // Necessary when placing search in a fixed component
+      }}
+      emptyMessage={
+        <EuiSelectableMessage style={{ minHeight: 300 }}>
+          <p>
+            Please see the component page for{' '}
+            <EuiLink href="https://eui.elastic.co/#/forms/selectable">
+              <strong>EuiSelectableTemplateSitewide</strong>
+            </EuiLink>{' '}
+            on how to configure your sitewide search.
+          </p>
+        </EuiSelectableMessage>
+      }
+    />
+  );
+
+  return (
+    <>
+      <EuiHeader
+        theme="dark"
+        position="fixed"
+        sections={[
+          {
+            items: [
+              <EuiHeaderLogo iconType="logoElastic" href="">
+                Elastic
+              </EuiHeaderLogo>,
+              deploymentMenu,
+            ],
+          },
+          {
+            items: [<EuiShowFor sizes={['m', 'l', 'xl']}>{search}</EuiShowFor>],
+          },
+          {
+            items: [
+              <EuiShowFor sizes={['xs', 's']}>{search}</EuiShowFor>,
+              <EuiHeaderSectionItemButton
+                notification={true}
+                aria-label="Notifications: Updates available"
+                onClick={() => setIsAlertFlyoutVisible(!isAlertFlyoutVisible)}
+              >
+                <EuiIcon type="cheer" size="m" />
+              </EuiHeaderSectionItemButton>,
+              userMenu,
+            ],
+          },
+        ]}
+      />
+      <EuiHeader
+        position="fixed"
+        sections={[
+          {
+            items: [collapsibleNav, spacesMenu],
+            breadcrumbs: [
+              {
+                text: 'Management',
+                onClick: () => {},
+              },
+              {
+                text: 'Users',
+              },
+            ],
+          },
+          {
+            items: [
+              <EuiHeaderLinks
+                popoverProps={{
+                  repositionOnScroll: true, // Necessary when placing search in a fixed component
+                }}
+              >
+                <EuiHeaderLink color="primary">Share</EuiHeaderLink>
+                <EuiHeaderLink color="primary">Clone</EuiHeaderLink>
+              </EuiHeaderLinks>,
+            ],
+          },
+        ]}
+      />
+
+      {isAlertFlyoutVisible ? headerAlerts : null}
+    </>
+  );
+};
+
+export const ElasticNavigationPattern: Story = {
+  parameters: {
+    layout: 'fullscreen',
+    codeSnippet: {
+      resolveChildren: true,
+    },
+  },
+  render: (args) => <ElasticNavigationPatternExample {...args} />,
+};
diff --git a/packages/website/docs/components/layout/header.mdx b/packages/website/docs/components/layout/header.mdx
index 1e99216be4fa..54191449df80 100644
--- a/packages/website/docs/components/layout/header.mdx
+++ b/packages/website/docs/components/layout/header.mdx
@@ -1299,369 +1299,11 @@ The button below will launch a fullscreen example that includes two **EuiHeaders
 
 This is just a pattern and should be treated as such. Consuming applications will need to recreate the pattern according to their context and save the states as is appropriate to their data store.
 
-<Demo scope={{ Link }}>
-  ```tsx
-  import React, { useState } from 'react';
-  import {
-    EuiAvatar,
-    EuiBadge,
-    EuiButton,
-    EuiCollapsibleNav,
-    EuiCollapsibleNavGroup,
-    EuiFlexItem,
-    EuiFlyout,
-    EuiFlyoutBody,
-    EuiFlyoutHeader,
-    EuiHeader,
-    EuiHeaderLink,
-    EuiHeaderLinks,
-    EuiHeaderLogo,
-    EuiHeaderSectionItemButton,
-    EuiIcon,
-    EuiListGroupItem,
-    EuiPopover,
-    EuiPortal,
-    EuiShowFor,
-    EuiText,
-    EuiTitle,
-    EuiSelectableTemplateSitewide,
-    EuiSelectableMessage,
-    useEuiTheme,
-    useGeneratedHtmlId,
-  } from '@elastic/eui';
-  import { Link } from 'react-router-dom';
-
-  export default () => {
-    const { euiTheme } = useEuiTheme();
-    const guideHeaderCollapsibleNavId = useGeneratedHtmlId({
-      prefix: 'guideHeaderCollapsibleNav',
-    });
-    const guideHeaderAlertFlyoutId = useGeneratedHtmlId({
-      prefix: 'guideHeaderAlertFlyout',
-    });
-    const guideHeaderAlertFlyoutTitleId = useGeneratedHtmlId({
-      prefix: 'guideHeaderAlertFlyoutTitle',
-    });
-    const guideHeaderUserPopoverId = useGeneratedHtmlId({
-      prefix: 'guideHeaderUserPopover',
-    });
-    const guideHeaderSpacesPopoverId = useGeneratedHtmlId({
-      prefix: 'guideHeaderSpacesPopover',
-    });
-    const guideHeaderDeploymentPopoverId = useGeneratedHtmlId({
-      prefix: 'guideHeaderDeploymentPopover',
-    });
-
-    /**
-     * Collapsible Nav
-     */
-    const [navIsOpen, setNavIsOpen] = useState(
-      JSON.parse(String(localStorage.getItem('navIsDocked'))) || false
-    );
-    const [navIsDocked, setNavIsDocked] = useState(
-      JSON.parse(String(localStorage.getItem('navIsDocked'))) || false
-    );
-    const collapsibleNav = (
-      <EuiCollapsibleNav
-        id={guideHeaderCollapsibleNavId}
-        aria-label="Main navigation"
-        isOpen={navIsOpen}
-        isDocked={navIsDocked}
-        button={
-          <EuiHeaderSectionItemButton
-            aria-label="Toggle main navigation"
-            onClick={() => setNavIsOpen(!navIsOpen)}
-          >
-            <EuiIcon type={'menu'} size="m" aria-hidden="true" />
-          </EuiHeaderSectionItemButton>
-        }
-        onClose={() => setNavIsOpen(false)}
-      >
-        <EuiFlexItem className="eui-yScroll">
-          {/* Docs callout */}
-          <EuiCollapsibleNavGroup background="none" title="EuiCollapsibleNav">
-            <EuiText size="s" color="subdued" style={{ padding: '0 8px 8px' }}>
-              <p>
-                Please see the component page for{' '}
-                <Link to="/navigation/collapsible-nav">
-                  <strong>EuiCollapsibleNav</strong>
-                </Link>{' '}
-                on how to configure your navigation.
-              </p>
-            </EuiText>
-          </EuiCollapsibleNavGroup>
-        </EuiFlexItem>
-        <EuiFlexItem grow={false}>
-          {/* Docking button only for larger screens that can support it*/}
-          <EuiShowFor sizes={['l', 'xl']}>
-            <EuiCollapsibleNavGroup>
-              <EuiListGroupItem
-                size="xs"
-                color="subdued"
-                label={`${navIsDocked ? 'Undock' : 'Dock'} navigation`}
-                onClick={() => {
-                  setNavIsDocked(!navIsDocked);
-                  localStorage.setItem(
-                    'navIsDocked',
-                    JSON.stringify(!navIsDocked)
-                  );
-                }}
-                iconType={navIsDocked ? 'lock' : 'lockOpen'}
-              />
-            </EuiCollapsibleNavGroup>
-          </EuiShowFor>
-        </EuiFlexItem>
-      </EuiCollapsibleNav>
-    );
-
-    /**
-     * Header Alerts
-     */
-    const [isAlertFlyoutVisible, setIsAlertFlyoutVisible] = useState(false);
-    const headerAlerts = (
-      <EuiPortal>
-        <EuiFlyout
-          onClose={() => setIsAlertFlyoutVisible(false)}
-          size="s"
-          id={guideHeaderAlertFlyoutId}
-          aria-labelledby={guideHeaderAlertFlyoutTitleId}
-        >
-          <EuiFlyoutHeader hasBorder>
-            <EuiTitle size="s">
-              <h2 id={guideHeaderAlertFlyoutTitleId}>EuiHeaderAlert</h2>
-            </EuiTitle>
-          </EuiFlyoutHeader>
-          <EuiFlyoutBody>
-            <EuiText size="s" color="subdued">
-              <p>
-                Please see the component page for{' '}
-                <Link to="/layout/header">
-                  <strong>EuiHeaderAlert</strong>
-                </Link>{' '}
-                on how to configure your alerts.
-              </p>
-            </EuiText>
-          </EuiFlyoutBody>
-        </EuiFlyout>
-      </EuiPortal>
-    );
-
-    /**
-     * User Menu
-     */
-    const [isUserMenuVisible, setIsUserMenuVisible] = useState(false);
-    const userMenu = (
-      <EuiPopover
-        id={guideHeaderUserPopoverId}
-        repositionOnScroll
-        button={
-          <EuiHeaderSectionItemButton
-            aria-controls={guideHeaderUserPopoverId}
-            aria-expanded={isUserMenuVisible}
-            aria-haspopup="true"
-            aria-label="User menu"
-            onClick={() => setIsUserMenuVisible(!isUserMenuVisible)}
-          >
-            <EuiAvatar name="John Username" size="s" />
-          </EuiHeaderSectionItemButton>
-        }
-        isOpen={isUserMenuVisible}
-        anchorPosition="downRight"
-        closePopover={() => setIsUserMenuVisible(false)}
-      >
-        <div style={{ width: 320 }}>
-          <EuiText size="s" color="subdued">
-            <p>
-              Please see the component page for{' '}
-              <Link to="/layout/header">
-                <strong>EuiHeader</strong>
-              </Link>{' '}
-              on how to configure your user menu.
-            </p>
-          </EuiText>
-        </div>
-      </EuiPopover>
-    );
-
-    /**
-     * Spaces Menu
-     */
-    const [isSpacesMenuVisible, setIsSpacesMenuVisible] = useState(false);
-    const spacesMenu = (
-      <EuiPopover
-        id={guideHeaderSpacesPopoverId}
-        repositionOnScroll
-        button={
-          <EuiHeaderSectionItemButton
-            aria-controls={guideHeaderSpacesPopoverId}
-            aria-expanded={isSpacesMenuVisible}
-            aria-haspopup="true"
-            aria-label="Spaces menu"
-            onClick={() => setIsSpacesMenuVisible(!isSpacesMenuVisible)}
-          >
-            <EuiAvatar type="space" name="Default Space" size="s" />
-          </EuiHeaderSectionItemButton>
-        }
-        isOpen={isSpacesMenuVisible}
-        anchorPosition="downRight"
-        closePopover={() => setIsSpacesMenuVisible(false)}
-      >
-        <div style={{ width: 320 }}>
-          <EuiText size="s" color="subdued">
-            <p>
-              Please see the component page for{' '}
-              <Link to="/layout/header">
-                <strong>EuiHeader</strong>
-              </Link>{' '}
-              on how to configure your spaces menu.
-            </p>
-          </EuiText>
-        </div>
-      </EuiPopover>
-    );
-
-    /**
-     * Deployment Menu
-     */
-    const [isDeploymentMenuVisible, setIsDeploymentMenuVisible] = useState(false);
-    const deploymentMenu = (
-      <EuiPopover
-        id={guideHeaderDeploymentPopoverId}
-        repositionOnScroll
-        button={
-          <EuiBadge
-            color={euiTheme.colors.darkestShade}
-            iconType="arrowDown"
-            iconSide="right"
-            aria-controls={guideHeaderDeploymentPopoverId}
-            aria-expanded={isDeploymentMenuVisible}
-            aria-haspopup="true"
-            onClickAriaLabel="Current deployment: Production logs. Click to open deployment menu."
-            onClick={() => setIsDeploymentMenuVisible(!isDeploymentMenuVisible)}
-          >
-            Production logs
-          </EuiBadge>
-        }
-        isOpen={isDeploymentMenuVisible}
-        anchorPosition="downRight"
-        closePopover={() => setIsDeploymentMenuVisible(false)}
-      >
-        <EuiText size="s" color="subdued">
-          <p>Deployment menu pattern TBD</p>
-        </EuiText>
-      </EuiPopover>
-    );
-
-    /**
-     * Sitewide search
-     */
-    const search = (
-      <EuiSelectableTemplateSitewide
-        options={[]}
-        searchProps={{
-          append: '⌘K',
-          compressed: true,
-        }}
-        popoverButton={
-          <EuiHeaderSectionItemButton aria-label="Sitewide search">
-            <EuiIcon type="search" size="m" />
-          </EuiHeaderSectionItemButton>
-        }
-        popoverButtonBreakpoints={['xs', 's']}
-        popoverProps={{
-          repositionOnScroll: true, // Necessary when placing search in a fixed component
-        }}
-        emptyMessage={
-          <EuiSelectableMessage style={{ minHeight: 300 }}>
-            <p>
-              Please see the component page for{' '}
-              <Link to="/forms/selectable">
-                <strong>EuiSelectableTemplateSitewide</strong>
-              </Link>{' '}
-              on how to configure your sitewide search.
-            </p>
-          </EuiSelectableMessage>
-        }
-      />
-    );
-
-    return (
-      <>
-        <EuiHeader
-          theme="dark"
-          position="fixed"
-          sections={[
-            {
-              items: [
-                <EuiHeaderLogo iconType="logoElastic" href="">
-                  Elastic
-                </EuiHeaderLogo>,
-                deploymentMenu,
-              ],
-            },
-            {
-              items: [<EuiShowFor sizes={['m', 'l', 'xl']}>{search}</EuiShowFor>],
-            },
-            {
-              items: [
-                <EuiShowFor sizes={['xs', 's']}>{search}</EuiShowFor>,
-                <EuiHeaderSectionItemButton
-                  notification={true}
-                  aria-label="Notifications: Updates available"
-                  onClick={() => setIsAlertFlyoutVisible(!isAlertFlyoutVisible)}
-                >
-                  <EuiIcon type="cheer" size="m" />
-                </EuiHeaderSectionItemButton>,
-                userMenu,
-              ],
-            },
-          ]}
-        />
-        <EuiHeader
-          position="fixed"
-          sections={[
-            {
-              items: [collapsibleNav, spacesMenu],
-              breadcrumbs: [
-                {
-                  text: 'Management',
-                  onClick: () => {},
-                },
-                {
-                  text: 'Users',
-                },
-              ],
-            },
-            {
-              items: [
-                <EuiHeaderLinks
-                  popoverProps={{
-                    repositionOnScroll: true, // Necessary when placing search in a fixed component
-                  }}
-                >
-                  <EuiHeaderLink color="primary">Share</EuiHeaderLink>
-                  <EuiHeaderLink color="primary">Clone</EuiHeaderLink>
-                  <EuiButton
-                    iconType="exit"
-                    style={{ minWidth: 80 }}
-                    size="s"
-                    color="primary"
-                    href="#"
-                  >
-                    Exit fullscreen
-                  </EuiButton>
-                </EuiHeaderLinks>,
-              ],
-            },
-          ]}
-        />
+```mdx-code-block
+import { StorybookLink } from '@site/src/components';
+```
 
-        {isAlertFlyoutVisible ? headerAlerts : null}
-      </>
-    );
-  };
-  ```
-</Demo>
+<StorybookLink id="layout-euiheader-euiheader--elastic-navigation-pattern" />
 
 ## Props
 
diff --git a/packages/website/docs/components/navigation/collapsible_nav.mdx b/packages/website/docs/components/navigation/collapsible_nav.mdx
index b154b17316fc..e2617e8e2881 100644
--- a/packages/website/docs/components/navigation/collapsible_nav.mdx
+++ b/packages/website/docs/components/navigation/collapsible_nav.mdx
@@ -296,328 +296,12 @@ The button below will launch a fullscreen example that includes [**EuiHeader**](
 
 This is just a pattern and should be treated as such. Consuming applications will need to create the navigation groups according to their context and save the states as is appropriate to their data store.
 
-```tsx interactive
-import React, { useState } from 'react';
-import {
-  logicalCSSWithFallback,
-  EuiCollapsibleNav,
-  EuiCollapsibleNavGroup,
-  EuiHeaderSectionItemButton,
-  EuiHeaderLogo,
-  EuiHeader,
-  EuiIcon,
-  EuiButton,
-  EuiButtonEmpty,
-  EuiPageTemplate,
-  EuiPinnableListGroup,
-  EuiPinnableListGroupItemProps,
-  EuiFlexItem,
-  EuiHorizontalRule,
-  EuiImage,
-  EuiListGroup,
-  useGeneratedHtmlId,
-} from '@elastic/eui';
-import find from 'lodash/find';
-import findIndex from 'lodash/findIndex';
-import { css } from '@emotion/react';
-import { KibanaNavLinks, SecurityGroup } from './collapsible_nav_list';
-import contentSvg from '../../images/content.svg';
-import { useExitPath } from '../../services/routing/routing';
-
-const TopLinks: EuiPinnableListGroupItemProps[] = [
-  {
-    label: 'Home',
-    iconType: 'home',
-    isActive: true,
-    'aria-current': true,
-    onClick: () => {},
-    pinnable: false,
-  },
-];
-const KibanaLinks: EuiPinnableListGroupItemProps[] = KibanaNavLinks.map(
-  (link) => {
-    return {
-      ...link,
-      onClick: () => {},
-    };
-  }
-);
-const LearnLinks: EuiPinnableListGroupItemProps[] = [
-  { label: 'Docs', onClick: () => {} },
-  { label: 'Blogs', onClick: () => {} },
-  { label: 'Webinars', onClick: () => {} },
-  { label: 'Elastic.co', href: 'https://elastic.co' },
-];
-
-const CollapsibleNavAll = () => {
-  const exitPath = useExitPath();
-  const [navIsOpen, setNavIsOpen] = useState(true);
-
-  /**
-   * Accordion toggling
-   */
-  const [openGroups, setOpenGroups] = useState(
-    JSON.parse(String(localStorage.getItem('openNavGroups'))) || [
-      'Kibana',
-      'Learn',
-    ]
-  );
-
-  // Save which groups are open and which are not with state and local store
-  const toggleAccordion = (isOpen: boolean, title?: string) => {
-    if (!title) return;
-    const itExists = openGroups.includes(title);
-    if (isOpen) {
-      if (itExists) return;
-      openGroups.push(title);
-    } else {
-      const index = openGroups.indexOf(title);
-      if (index > -1) {
-        openGroups.splice(index, 1);
-      }
-    }
-    setOpenGroups([...openGroups]);
-    localStorage.setItem('openNavGroups', JSON.stringify(openGroups));
-  };
-
-  /**
-   * Pinning
-   */
-  const [pinnedItems, setPinnedItems] = useState<
-    EuiPinnableListGroupItemProps[]
-  >(JSON.parse(String(localStorage.getItem('pinnedItems'))) || []);
-
-  const addPin = (item: any) => {
-    if (!item || find(pinnedItems, { label: item.label })) {
-      return;
-    }
-    item.pinned = true;
-    const newPinnedItems = pinnedItems ? pinnedItems.concat(item) : [item];
-    setPinnedItems(newPinnedItems);
-    localStorage.setItem('pinnedItems', JSON.stringify(newPinnedItems));
-  };
-
-  const removePin = (item: any) => {
-    const pinIndex = findIndex(pinnedItems, { label: item.label });
-    if (pinIndex > -1) {
-      item.pinned = false;
-      const newPinnedItems = pinnedItems;
-      newPinnedItems.splice(pinIndex, 1);
-      setPinnedItems([...newPinnedItems]);
-      localStorage.setItem('pinnedItems', JSON.stringify(newPinnedItems));
-    }
-  };
-
-  function alterLinksWithCurrentState(
-    links: EuiPinnableListGroupItemProps[],
-    showPinned = false
-  ): EuiPinnableListGroupItemProps[] {
-    return links.map((link) => {
-      const { pinned, ...rest } = link;
-      return {
-        pinned: showPinned ? pinned : false,
-        ...rest,
-      };
-    });
-  }
-
-  function addLinkNameToPinTitle(listItem: EuiPinnableListGroupItemProps) {
-    return `Pin ${listItem.label} to top`;
-  }
-
-  function addLinkNameToUnpinTitle(listItem: EuiPinnableListGroupItemProps) {
-    return `Unpin ${listItem.label}`;
-  }
-
-  const collapsibleNavId = useGeneratedHtmlId({ prefix: 'collapsibleNav' });
-
-  const collapsibleNav = (
-    <EuiCollapsibleNav
-      id={collapsibleNavId}
-      aria-label="Main navigation"
-      isOpen={navIsOpen}
-      button={
-        <EuiHeaderSectionItemButton
-          aria-label="Toggle main navigation"
-          onClick={() => setNavIsOpen(!navIsOpen)}
-        >
-          <EuiIcon type={'menu'} size="m" aria-hidden="true" />
-        </EuiHeaderSectionItemButton>
-      }
-      onClose={() => setNavIsOpen(false)}
-      // Accessibility - Add scroll to nav on very small screens
-      css={css`
-        @media (max-height: 15em) {
-          ${logicalCSSWithFallback('overflow-y', 'auto')}
-        }
-      `}
-    >
-      {/* Dark deployments section */}
-      <EuiFlexItem grow={false} style={{ flexShrink: 0 }}>
-        <EuiCollapsibleNavGroup isCollapsible={false} background="dark">
-          <EuiListGroup
-            maxWidth="none"
-            gutterSize="none"
-            size="s"
-            listItems={[
-              {
-                label: 'Manage deployment',
-                href: '#',
-                iconType: 'logoCloud',
-                iconProps: {
-                  color: 'ghost',
-                },
-              },
-            ]}
-          />
-        </EuiCollapsibleNavGroup>
-      </EuiFlexItem>
-
-      {/* Shaded pinned section always with a home item */}
-      <EuiFlexItem grow={false} style={{ flexShrink: 0 }}>
-        <EuiCollapsibleNavGroup
-          background="light"
-          style={{ maxHeight: '40vh' }}
-          className="eui-yScroll"
-        >
-          <EuiPinnableListGroup
-            aria-label="Pinned links" // A11y : Since this group doesn't have a visible `title` it should be provided an accessible description
-            listItems={alterLinksWithCurrentState(TopLinks).concat(
-              alterLinksWithCurrentState(pinnedItems, true)
-            )}
-            unpinTitle={addLinkNameToUnpinTitle}
-            onPinClick={removePin}
-            maxWidth="none"
-            color="text"
-            gutterSize="none"
-            size="s"
-          />
-        </EuiCollapsibleNavGroup>
-      </EuiFlexItem>
-
-      <EuiHorizontalRule margin="none" />
-
-      {/* BOTTOM */}
-      <EuiFlexItem
-        className="eui-yScroll"
-        // Accessibility - Allows nav items to be seen and interacted with on very small screen sizes
-        css={css`
-          @media (max-height: 15em) {
-            flex: 1 0 auto;
-          }
-        `}
-      >
-        {/* Kibana section */}
-        <EuiCollapsibleNavGroup
-          title={
-            <a
-              className="eui-textInheritColor"
-              href="#/navigation/collapsible-nav"
-              onClick={(e) => e.stopPropagation()}
-            >
-              Kibana
-            </a>
-          }
-          buttonElement="div"
-          iconType="logoKibana"
-          isCollapsible={true}
-          initialIsOpen={openGroups.includes('Kibana')}
-          onToggle={(isOpen: boolean) => toggleAccordion(isOpen, 'Kibana')}
-        >
-          <EuiPinnableListGroup
-            aria-label="Kibana" // A11y : EuiCollapsibleNavGroup can't correctly pass the `title` as the `aria-label` to the right HTML element, so it must be added manually
-            listItems={alterLinksWithCurrentState(KibanaLinks)}
-            pinTitle={addLinkNameToPinTitle}
-            onPinClick={addPin}
-            maxWidth="none"
-            color="subdued"
-            gutterSize="none"
-            size="s"
-          />
-        </EuiCollapsibleNavGroup>
-
-        {/* Security callout */}
-        {SecurityGroup}
-
-        {/* Learn section */}
-        <EuiCollapsibleNavGroup
-          title={
-            <a
-              className="eui-textInheritColor"
-              href="#/navigation/collapsible-nav"
-              onClick={(e) => e.stopPropagation()}
-            >
-              Training
-            </a>
-          }
-          buttonElement="div"
-          iconType="training"
-          isCollapsible={true}
-          initialIsOpen={openGroups.includes('Learn')}
-          onToggle={(isOpen: boolean) => toggleAccordion(isOpen, 'Learn')}
-        >
-          <EuiPinnableListGroup
-            aria-label="Learn" // A11y : EuiCollapsibleNavGroup can't correctly pass the `title` as the `aria-label` to the right HTML element, so it must be added manually
-            listItems={alterLinksWithCurrentState(LearnLinks)}
-            pinTitle={addLinkNameToPinTitle}
-            onPinClick={addPin}
-            maxWidth="none"
-            color="subdued"
-            gutterSize="none"
-            size="s"
-          />
-        </EuiCollapsibleNavGroup>
-      </EuiFlexItem>
-
-      <EuiFlexItem grow={false}>
-        {/* Span fakes the nav group into not being the first item and therefore adding a top border */}
-        <span />
-        <EuiCollapsibleNavGroup>
-          <EuiButton fill fullWidth iconType="plusInCircleFilled">
-            Add data
-          </EuiButton>
-        </EuiCollapsibleNavGroup>
-      </EuiFlexItem>
-    </EuiCollapsibleNav>
-  );
-
-  const leftSectionItems = [
-    collapsibleNav,
-    <EuiHeaderLogo href={exitPath} iconType="logoElastic">
-      Elastic
-    </EuiHeaderLogo>,
-  ];
-
-  return (
-    <>
-      <EuiHeader
-        position="fixed"
-        sections={[
-          {
-            items: leftSectionItems,
-          },
-          {
-            items: [
-              <EuiButtonEmpty href={exitPath} iconType="exit">
-                Exit fullscreen
-              </EuiButtonEmpty>,
-            ],
-          },
-        ]}
-      />
-
-      <EuiPageTemplate>
-        <EuiPageTemplate.EmptyPrompt>
-          <EuiImage size="fullWidth" alt="Fake paragraph" url={contentSvg} />
-        </EuiPageTemplate.EmptyPrompt>
-      </EuiPageTemplate>
-    </>
-  );
-};
-
-export default CollapsibleNavAll;
+```mdx-code-block
+import { StorybookLink } from '@site/src/components';
 ```
 
+<StorybookLink id="navigation-euicollapsiblenav-euicollapsiblenav--full-header-pattern" />
+
 ## Props
 
 import docgen from '@elastic/eui-docgen/dist/components/collapsible_nav';
diff --git a/packages/website/docusaurus.config.ts b/packages/website/docusaurus.config.ts
index 906f1e4c8894..c448eb12d1ef 100644
--- a/packages/website/docusaurus.config.ts
+++ b/packages/website/docusaurus.config.ts
@@ -14,6 +14,14 @@ import type { Options as EuiPresetOptions } from '@elastic/eui-docusaurus-preset
 const baseUrl = process.env.DOCS_BASE_URL || '/';
 const googleTagManagerId = process.env.DOCS_GOOGLE_TAG_MANAGER_ID || undefined;
 
+let storybookBaseUrl: string = 'https://eui.elastic.co/storybook';
+
+if (process.env.NODE_ENV === 'development') {
+  storybookBaseUrl = 'http://localhost:6006';
+} else if (process.env.STORYBOOK_BASE_URL) {
+  storybookBaseUrl = process.env.STORYBOOK_BASE_URL;
+}
+
 const config: Config = {
   title: 'Elastic UI Framework',
   tagline: 'The framework powering the Elastic Stack',
@@ -37,6 +45,10 @@ const config: Config = {
     locales: ['en'],
   },
 
+  customFields: {
+    storybookBaseUrl,
+  },
+
   presets: [
     [
       require.resolve('@elastic/eui-docusaurus-preset'),
@@ -56,7 +68,7 @@ const config: Config = {
         googleTagManager: googleTagManagerId && {
           containerId: googleTagManagerId,
         },
-      } satisfies EuiPresetOptions
+      } satisfies EuiPresetOptions,
     ],
   ],
 
diff --git a/packages/website/src/components/index.ts b/packages/website/src/components/index.ts
index d667b2cb9963..80b4fdc7e8ef 100644
--- a/packages/website/src/components/index.ts
+++ b/packages/website/src/components/index.ts
@@ -8,3 +8,4 @@
 
 export * from './example';
 export * from './display_toggles';
+export * from './storybook_link';
diff --git a/packages/website/src/components/storybook_link/index.tsx b/packages/website/src/components/storybook_link/index.tsx
new file mode 100644
index 000000000000..ecc9c526ce81
--- /dev/null
+++ b/packages/website/src/components/storybook_link/index.tsx
@@ -0,0 +1,18 @@
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import { EuiButton } from '@elastic/eui';
+
+type Props = {
+  id: string;
+};
+
+export const StorybookLink = ({ id }: Props) => {
+  const { siteConfig } = useDocusaurusContext();
+
+  const href = `${siteConfig.customFields.storybookBaseUrl}/iframe.html?id=${id}`;
+
+  return (
+    <EuiButton iconType="popout" href={href} target="_blank">
+      Open demo
+    </EuiButton>
+  );
+};


Reviews:
user: login: tkajtoch
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T13:59:09Z
user: login: weronikaolejniczak
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T14:08:34Z
user: login: acstll
body: thank you @weronikaolejniczak for taking care of this, and sorry that it took me so long to review this!

the full-screen experience is great, and I love how it looks and how simple it is, kudos ✨ 

as for the implementation for the `StorybookLink` component, it works perfectly as expected, but we could improve it a bit based on the [original comment](https://github.com/elastic/eui/pull/8499#discussion_r2014216182) from @tkajtoch (I also share this idea, we had a chat about it), so I left a few brief comments, but in summary the changes would be:
- rename the key in customFields to `storybookBaseUrl`
- move logic that defines `storybookBaseUrl` into docusaurus.config.ts, and
- move the StorybookLink component into packages/website

I hope that makes sense, let me know what you think
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T08:50:57Z
user: login: acstll
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T11:16:47Z
user: login: acstll
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T11:19:34Z
user: login: acstll
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T11:24:13Z
user: login: acstll
body: thanks for addressing the feedback so quickly! 🚢
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T11:25:11Z

Review comments:
diff_hunk: @@ -0,0 +1,33 @@
+import { useLocation } from '@docusaurus/router';
+import { EuiButton } from '@elastic/eui';
+
+type Props = {
+  id: string;
+  viewMode?: 'story' | 'docs' | 'settings';
+};
+
+export const StorybookLink = ({ id, viewMode = 'story' }: Props) => {
+  const location = useLocation();
+
+  let baseUrl: string;
+
+  if (process.env.NODE_ENV === 'development') {
+    // Potentially get the Storybook config for easier maintenance
+    baseUrl = 'http://localhost:6006';
+  } else if (location.pathname.includes('/pr_')) {
+    const prPath = location.pathname.split('/').slice(0, 2).join('/');
path: packages/docusaurus-theme/src/components/storybook_link/index.tsx
user: login: tkajtoch
body: This might fail when we update the base URL for PR previews. Right now, we're using `eui.elastic.co/pr_<id>` for old docs and `eui.elastic.co/pr_<id>/new-docs` for EUI+, but that's going to change soon.

I think we should move this logic to `docusaurus.config.ts` and expose the Storybook root URL as an environment variable for simplicity.
created_at: 2025-03-26T13:59:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,33 @@
+import { useLocation } from '@docusaurus/router';
+import { EuiButton } from '@elastic/eui';
+
+type Props = {
+  id: string;
+  viewMode?: 'story' | 'docs' | 'settings';
+};
+
+export const StorybookLink = ({ id, viewMode = 'story' }: Props) => {
+  const location = useLocation();
+
+  let baseUrl: string;
+
+  if (process.env.NODE_ENV === 'development') {
+    // Potentially get the Storybook config for easier maintenance
+    baseUrl = 'http://localhost:6006';
+  } else if (location.pathname.includes('/pr_')) {
+    const prPath = location.pathname.split('/').slice(0, 2).join('/');
path: packages/docusaurus-theme/src/components/storybook_link/index.tsx
user: login: weronikaolejniczak
body: @tkajtoch this was just for the sake of showcasing the idea, quick iteration 😄 On the same PR I'm adding a story, so for the redirection to work locally and in staging (and not fail because there's no story) it has to redirect to this PR version Storybook.

For the future, I would also opt for an environment variable but that requires more thought and some tweaking to the release pipeline to inject the variable. Let's decide first whether we like this approach, and then I will think about a final implementation.
created_at: 2025-03-26T14:08:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,6 +38,10 @@ const config: Config = {
     locales: ['en'],
   },
 
+  customFields: {
+    prRootUrl,
path: packages/website/docusaurus.config.ts
user: login: acstll
body: this we can make `storybookBaseUrl`

and have `http://localhost:6006` in dev mode, or either `https://eui.elastic.co/storybook` or `STORYBOOK_BASE_URL` if present (that we'd get from CI)
created_at: 2025-04-02T08:43:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import { EuiButton } from '@elastic/eui';
+
+import { hasPrRootUrl } from './has_pr_root_url';
+
+type Props = {
+  id: string;
+  viewMode?: 'story' | 'docs' | 'settings';
+};
+
+export const StorybookLink = ({ id, viewMode = 'story' }: Props) => {
+  const { siteConfig } = useDocusaurusContext();
+
+  let baseUrl: string;
+
+  if (process.env.NODE_ENV === 'development') {
+    // Potentially get the Storybook config for easier maintenance
+    baseUrl = 'http://localhost:6006';
+  } else if (hasPrRootUrl(siteConfig)) {
+    baseUrl = `https://eui.elastic.co/${siteConfig.customFields.prRootUrl}/storybook`;
+  } else {
+    baseUrl = 'https://eui.elastic.co/storybook';
+  }
path: packages/docusaurus-theme/src/components/storybook_link/index.tsx
user: login: acstll
body: this logic can go into docusaurus.config.ts, and instead here only consume the value from `customFields.storybookBaseUrl`
created_at: 2025-04-02T08:43:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,4 +9,5 @@
 export { createDemo, Demo, type DemoProps } from './demo';
 export { Guideline, GuidelineText } from './guideline';
 export { PropTable } from './prop_table';
+export { StorybookLink } from './storybook_link';
path: packages/docusaurus-theme/src/components/index.ts
user: login: acstll
body: we can move this `StorybookLink` component into /website because it's specific to EUI
created_at: 2025-04-02T08:45:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,7 +16,8 @@ echo "Yarn version: $(yarn -v)"
 
 # Calculate paths and directories
 if [[ -n "${BUILDKITE_PULL_REQUEST}" ]] && [[ "${BUILDKITE_PULL_REQUEST}" != "false" ]]; then
-  bucket_directory="pr_${BUILDKITE_PULL_REQUEST}/new-docs/"
+  export PR_ROOT_URL="pr_${BUILDKITE_PULL_REQUEST}"
path: .buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh
user: login: acstll
body: I'm not sure this would be the place to put it (because I'm not familiar with CI) but the variable we'd need is `STORYBOOK_BASE_URL` with the full base url e.g. `https://eui.elastic.co/pr_<number>/storybook`
created_at: 2025-04-02T08:46:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,4 +9,5 @@
 export { createDemo, Demo, type DemoProps } from './demo';
 export { Guideline, GuidelineText } from './guideline';
 export { PropTable } from './prop_table';
+export { StorybookLink } from './storybook_link';
path: packages/docusaurus-theme/src/components/index.ts
user: login: acstll
body: one more reason for `StorybookLink` to live in /website is that that's where `customFields` is used, which is part of the implementation

I would be also in favor of also moving those to /website as well (in M2?)
created_at: 2025-04-02T11:16:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -37,6 +38,10 @@ const config: Config = {
     locales: ['en'],
   },
 
+  customFields: {
+    prRootUrl,
path: packages/website/docusaurus.config.ts
user: login: acstll
body: I thought I understood from Tomasz' comment that these URLs might change, so since these staging URLs are defined also in CI, getting them in an env var is safer than building these URLs in the client — I would say that's the main argument in favor of getting the full base URL instead of the PR part
created_at: 2025-04-02T11:19:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,7 +16,9 @@ echo "Yarn version: $(yarn -v)"
 
 # Calculate paths and directories
 if [[ -n "${BUILDKITE_PULL_REQUEST}" ]] && [[ "${BUILDKITE_PULL_REQUEST}" != "false" ]]; then
-  bucket_directory="pr_${BUILDKITE_PULL_REQUEST}/new-docs/"
+  PR_SLUG="pr_${BUILDKITE_PULL_REQUEST}"
path: .buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh
user: login: acstll
body: `slug` is the right term here I would say, I was looking for it but couldn't think of it 😀 
created_at: 2025-04-02T11:24:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: weronikaolejniczak
created_at: 2025-04-02T11:08:37Z
body: @acstll thank you for the review, I updated the PR with your suggestions 😄 
author_association: CONTRIBUTOR
user: login: kibanamachine
created_at: 2025-04-02T11:43:43Z
body: Preview staging links for this PR:
- Docs site: https://eui.elastic.co/pr_8499/
- Storybook: https://eui.elastic.co/pr_8499/storybook
- New docs: https://eui.elastic.co/pr_8499/new-docs
author_association: NONE
user: login: elasticmachine
created_at: 2025-04-02T11:43:45Z
body: ## :green_heart: Build Succeeded
* [Buildkite Build](https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3765)
* Commit: b6b75e9f09055515f85b337d5f228c916ad0fb80

### History
* :green_heart: [Build #3756](https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3756) succeeded 18444613b23c440429cc1593f98ee695a720242b
* :green_heart: [Build #3749](https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3749) succeeded 9a780181fdbde2b723f5ea2d70bdd1cd657f3bbb
* :green_heart: [Build #3745](https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3745) succeeded f0214e439123d990adf37787bd72048f4b780aac
* :green_heart: [Build #3744](https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3744) succeeded 235d4d64f9b38b642a6345101ccb279cfdbd31e4
* :green_heart: [Build #3730](https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3730) succeeded 4655575824e8b39ad2531ab5943dcdd66ff9e66c
* :green_heart: [Build #3727](https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3727) succeeded be49df532f2612598947a833db727b9d84968c9a

cc @weronikaolejniczak

<!--buildkite-pr-comment-eui-pull-request-test-and-deploy
{"builds":[{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3765","number":3765,"commit":"b6b75e9f09055515f85b337d5f228c916ad0fb80"},{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3756","number":3756,"commit":"18444613b23c440429cc1593f98ee695a720242b"},{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3749","number":3749,"commit":"9a780181fdbde2b723f5ea2d70bdd1cd657f3bbb"},{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3745","number":3745,"commit":"f0214e439123d990adf37787bd72048f4b780aac"},{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3744","number":3744,"commit":"235d4d64f9b38b642a6345101ccb279cfdbd31e4"},{"buildStatus":{"state":"passed","success":true,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/eui-pull-request-test-and-deploy/builds/3730","number":3730,"commit":"4655575824e8b39ad2531ab5943dcdd66ff9e66c"}],"number":3765}
buildkite-pr-comment-->
author_association: COLLABORATOR