diff --git a/modules/ui_patterns_legacy/src/Element/ComponentElementAlter.php b/modules/ui_patterns_legacy/src/Element/ComponentElementAlter.php
index e52e585d279a68e94d50935b7992e591e3e8da43..16d2202d4dd9fd17bfe9255c565d8db37bcb3709 100644
--- a/modules/ui_patterns_legacy/src/Element/ComponentElementAlter.php
+++ b/modules/ui_patterns_legacy/src/Element/ComponentElementAlter.php
@@ -7,8 +7,8 @@ namespace Drupal\ui_patterns_legacy\Element;
 use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Theme\ComponentPluginManager;
 use Drupal\Core\Theme\ThemeManagerInterface;
-use Drupal\ui_patterns\Element\ComponentElementAlter as UiPatternsComponentElementAlter;
 use Drupal\ui_patterns_legacy\RenderableConverter;
+use Drupal\ui_patterns_library\StoriesSyntaxConverter;
 
 /**
  * Renders a component story.
@@ -22,13 +22,13 @@ class ComponentElementAlter implements TrustedCallbackInterface {
    *   The theme manager.
    * @param \Drupal\Core\Theme\ComponentPluginManager $componentPluginManager
    *   The component plugin manager.
-   * @param \Drupal\ui_patterns\Element\ComponentElementAlter $componentElementAlter
-   *   The component element alter.
+   * @param \Drupal\ui_patterns_library\StoriesSyntaxConverter $storiesConverter
+   *   The stories syntax converter.
    */
   public function __construct(
     protected ThemeManagerInterface $themeManager,
     protected ComponentPluginManager $componentPluginManager,
-    protected UiPatternsComponentElementAlter $componentElementAlter,
+    protected StoriesSyntaxConverter $storiesConverter,
   ) {
   }
 
@@ -70,7 +70,7 @@ class ComponentElementAlter implements TrustedCallbackInterface {
       return $element;
     }
     $element["#story"] = $this->getStoryId($component["stories"]);
-    $element["#slots"] = $this->componentElementAlter->processStoriesSlots($element["#slots"] ?? []);
+    $element["#slots"] = $this->storiesConverter->convertSlots($element["#slots"] ?? []);
     return $element;
   }
 
diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml
index 3ada3bf59efd28561ff5ba588e4fa89c8c99aaa7..05705e5cd9252db23d9236140409c352a7fad9b5 100644
--- a/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml
+++ b/modules/ui_patterns_legacy/ui_patterns_legacy.info.yml
@@ -5,3 +5,4 @@ core_version_requirement: ^10.3 || ^11
 package: "User interface"
 dependencies:
   - ui_patterns:ui_patterns
+  - ui_patterns:ui_patterns_library
diff --git a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml
index 1383454385426d0b26e9a3ec2a749eb1f303d1ac..eea5e5ccc15cd5a253e7d4a4245b8f0a9103fde1 100644
--- a/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml
+++ b/modules/ui_patterns_legacy/ui_patterns_legacy.services.yml
@@ -22,4 +22,4 @@ services:
     arguments:
       - "@theme.manager"
       - "@plugin.manager.sdc"
-      - "@ui_patterns.component_element_alter"
+      - "@ui_patterns_library.stories_syntax_converter"
diff --git a/modules/ui_patterns_library/src/Element/ComponentElementAlter.php b/modules/ui_patterns_library/src/Element/ComponentElementAlter.php
index 82b2d94962b6147d308b1da6547d88f0b6587d3e..3357b1c615ccf7277c35571c3978a9b7da416e32 100644
--- a/modules/ui_patterns_library/src/Element/ComponentElementAlter.php
+++ b/modules/ui_patterns_library/src/Element/ComponentElementAlter.php
@@ -6,7 +6,7 @@ namespace Drupal\ui_patterns_library\Element;
 
 use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Theme\ComponentPluginManager;
-use Drupal\ui_patterns\Element\ComponentElementAlter as UiPatternsComponentElementAlter;
+use Drupal\ui_patterns_library\StoriesSyntaxConverter;
 
 /**
  * Renders a component story.
@@ -18,12 +18,12 @@ class ComponentElementAlter implements TrustedCallbackInterface {
    *
    * @param \Drupal\Core\Theme\ComponentPluginManager $componentPluginManager
    *   The component plugin manager.
-   * @param \Drupal\ui_patterns\Element\ComponentElementAlter $componentElementAlter
-   *   The component element alter.
+   * @param \Drupal\ui_patterns_library\StoriesSyntaxConverter $storiesConverter
+   *   The stories syntax converter.
    */
   public function __construct(
     protected ComponentPluginManager $componentPluginManager,
-    protected UiPatternsComponentElementAlter $componentElementAlter,
+    protected StoriesSyntaxConverter $storiesConverter,
   ) {
   }
 
@@ -59,7 +59,7 @@ class ComponentElementAlter implements TrustedCallbackInterface {
     }
     $story = $component["stories"][$story_id];
     $slots = array_merge($element["#slots"] ?? [], $story["slots"] ?? []);
-    $element["#slots"] = $this->componentElementAlter->processStoriesSlots($slots);
+    $element["#slots"] = $this->storiesConverter->convertSlots($slots);
     $element["#props"] = array_merge($element["#props"] ?? [], $story["props"] ?? []);
     return $element;
   }
diff --git a/modules/ui_patterns_library/src/StoriesSyntaxConverter.php b/modules/ui_patterns_library/src/StoriesSyntaxConverter.php
new file mode 100644
index 0000000000000000000000000000000000000000..4176bcfd5f195f5391f857e52054c9038e1108b7
--- /dev/null
+++ b/modules/ui_patterns_library/src/StoriesSyntaxConverter.php
@@ -0,0 +1,89 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\ui_patterns_library;
+
+/**
+ * Convert a component story.
+ */
+class StoriesSyntaxConverter {
+
+  /**
+   * Process stories slots.
+   *
+   * Stories slots have no "#" prefix in render arrays. Let's add them.
+   */
+  public function convertSlots(array $slots): array {
+    foreach ($slots as $slot_id => $slot) {
+      if (!is_array($slot)) {
+        continue;
+      }
+      $slots[$slot_id] = $this->convertArray($slot);
+    }
+    return $slots;
+  }
+
+  /**
+   * Convert an array.
+   */
+  protected function convertArray(array $array): array {
+    if ($this->isRenderArray($array)) {
+      return $this->convertRenderArray($array);
+    }
+    foreach ($array as $index => $value) {
+      if (!is_array($value)) {
+        continue;
+      }
+      $array[$index] = $this->convertArray($value);
+    }
+    return $array;
+  }
+
+  /**
+   * Convert a render array.
+   */
+  protected function convertRenderArray(array $renderable): array {
+    foreach ($renderable as $property => $value) {
+      if (is_array($value)) {
+        $renderable[$property] = $this->convertArray($value);
+      }
+    }
+    $in_html_tag = (isset($renderable["type"]) && $renderable["type"] === "html_tag");
+    $html_tag_allowed_render_keys = ["type", "attributes", "tag", "value", "attached"];
+    foreach ($renderable as $property => $value) {
+      // html_tag is special.
+      if ($in_html_tag && !in_array($property, $html_tag_allowed_render_keys)) {
+        continue;
+      }
+      if (str_starts_with($property, "#")) {
+        continue;
+      }
+      $renderable["#" . $property] = $value;
+      unset($renderable[$property]);
+    }
+    return $renderable;
+  }
+
+  /**
+   * Is the array a render array?
+   */
+  protected function isRenderArray(array $array): bool {
+    if (array_is_list($array)) {
+      return FALSE;
+    }
+    $render_keys = ["theme", "type", "markup", "plain_text", "#theme", "#type", "#markup", "#plain_text"];
+    // An array needs one, and only one, of those properties to be a render
+    // array.
+    $intersect = array_intersect(array_keys($array), $render_keys);
+    if (count($intersect) != 1) {
+      return FALSE;
+    }
+    // This property has to be a string value.
+    if (!is_string($array[$intersect[0]])) {
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+}
diff --git a/modules/ui_patterns_library/ui_patterns_library.services.yml b/modules/ui_patterns_library/ui_patterns_library.services.yml
index 5e123e27fed3b90fdb9f8c47bd29336824c4fe05..45de47c9a6054dc0cd6d906d9124002ecb1390a5 100644
--- a/modules/ui_patterns_library/ui_patterns_library.services.yml
+++ b/modules/ui_patterns_library/ui_patterns_library.services.yml
@@ -3,7 +3,9 @@ services:
     class: Drupal\ui_patterns_library\Element\ComponentElementAlter
     arguments:
       - "@plugin.manager.sdc"
-      - "@ui_patterns.component_element_alter"
+      - "@ui_patterns_library.stories_syntax_converter"
+  ui_patterns_library.stories_syntax_converter:
+    class: Drupal\ui_patterns_library\StoriesSyntaxConverter
   ui_patterns_library.twig.extension:
     class: Drupal\ui_patterns_library\Template\TwigExtension
     tags:
diff --git a/src/Element/ComponentElementAlter.php b/src/Element/ComponentElementAlter.php
index 5e9b62e75e30a5df9fa3905025fa635ba5988697..4b7919f574fd23b468e7e2b2f6700827ed49384e 100644
--- a/src/Element/ComponentElementAlter.php
+++ b/src/Element/ComponentElementAlter.php
@@ -127,38 +127,4 @@ class ComponentElementAlter implements TrustedCallbackInterface {
     return $element;
   }
 
-  /**
-   * Process stories slots.
-   *
-   * Stories slots have no "#" prefix in render arrays. Let's add them.
-   * A bit like UI Patterns 1.x's PatternPreview::getPreviewMarkup()
-   * This method belongs here because used by both ui_patterns_library and
-   * ui_patterns_legacy.
-   */
-  public function processStoriesSlots(array $slots): array {
-    foreach ($slots as $slot_id => $slot) {
-      if (!is_array($slot)) {
-        continue;
-      }
-      if (array_is_list($slot)) {
-        $slots[$slot_id] = $this->processStoriesSlots($slot);
-      }
-      $slot_keys = array_keys($slot);
-      $render_keys = ["theme", "type", "markup", "plain_text"];
-      if (count(array_intersect($slot_keys, $render_keys)) > 0) {
-        foreach ($slot as $key => $value) {
-          if (is_array($value)) {
-            $value = $this->processStoriesSlots($value);
-          }
-          if (str_starts_with($key, "#")) {
-            continue;
-          }
-          $slots[$slot_id]["#" . $key] = $value;
-          unset($slots[$slot_id][$key]);
-        }
-      }
-    }
-    return $slots;
-  }
-
 }