diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/AssetAggregationAcrossPagesTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/AssetAggregationAcrossPagesTest.php
index dd7110f1ede6c998b5f33de076f6637eecf16f69..9cb51a0598e8e6f88a75f204135a996794b8b540 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/AssetAggregationAcrossPagesTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/AssetAggregationAcrossPagesTest.php
@@ -48,7 +48,7 @@ public function testFrontAndRecipesPagesAuthenticated(): void {
 
     $expected = [
       'ScriptCount' => 2,
-      'ScriptBytes' => 249200,
+      'ScriptBytes' => 249700,
       'StylesheetCount' => 6,
     ];
     $this->assertMetrics($expected, $performance_data);
diff --git a/core/profiles/demo_umami/themes/umami/css/components/layout_builder/layout-builder.css b/core/profiles/demo_umami/themes/umami/css/components/layout_builder/layout-builder.css
index 754d7d7c0f92a624a74fcb2559370a4e9c8797ff..556ec078339ee8db045a0c0d99174ddab9b6dea1 100644
--- a/core/profiles/demo_umami/themes/umami/css/components/layout_builder/layout-builder.css
+++ b/core/profiles/demo_umami/themes/umami/css/components/layout_builder/layout-builder.css
@@ -6,9 +6,9 @@
 .layout-builder__message .messages {
   background-image: none;
 }
-.layout-builder__message--defaults .messages__content {
-  --umami-message-icon: url("../../../../../../../misc/icons/73b355/globe.svg");
+.layout-builder__message--defaults .messages {
+  --umami-message-icon: url("/core/misc/icons/73b355/globe.svg");
 }
-.layout-builder__message--overrides .messages__content {
-  --umami-message-icon: url("../../../../../../../misc/icons/73b355/location.svg");
+.layout-builder__message--overrides .messages {
+  --umami-message-icon: url("/core/misc/icons/73b355/location.svg");
 }
diff --git a/core/profiles/demo_umami/themes/umami/css/components/messages/messages.css b/core/profiles/demo_umami/themes/umami/css/components/messages/messages.css
index df80f509f0c7a61a450da3a2cb9224d74e1a3fa3..2e90717892b5eb71eec549d910ecf54cd3e98da4 100644
--- a/core/profiles/demo_umami/themes/umami/css/components/messages/messages.css
+++ b/core/profiles/demo_umami/themes/umami/css/components/messages/messages.css
@@ -9,16 +9,6 @@
   color: inherit;
   overflow-wrap: break-word;
 }
-.messages__content {
-  padding-inline-start: 24px;
-  background-image: var(--umami-message-icon);
-  background-repeat: no-repeat;
-  background-position-x: left;
-  background-position-y: center;
-}
-[dir="rtl"] .messages__content {
-  background-position-x: right;
-}
 .messages--status {
   --umami-message-icon: url(/core/misc/icons/73b355/check.svg);
 
@@ -44,14 +34,32 @@
   color: #cc2a00;
 }
 .messages__list {
+  display: grid;
+  gap: 0.769em;
   margin: 0;
   padding: 0;
   list-style: none;
 }
-.messages__item + .messages__item {
-  margin-top: 0.769em;
-}
 .messages__wrapper {
   display: grid;
   gap: 1.538em;
 }
+drupal-umami-messages::part(heading) {
+  position: absolute !important;
+  overflow: hidden;
+  clip: rect(1px, 1px, 1px, 1px);
+  width: 1px;
+  height: 1px;
+  word-wrap: normal;
+}
+drupal-umami-messages::part(content) {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+}
+drupal-umami-messages::part(icon) {
+  flex: 0 0 1rem;
+  width: 1rem;
+  height: 1rem;
+  background-image: var(--umami-message-icon);
+}
diff --git a/core/profiles/demo_umami/themes/umami/js/components/messages/messages.js b/core/profiles/demo_umami/themes/umami/js/components/messages/messages.js
index 3df0790cb3c276610befd2470b3ddee3dc5644eb..2b3c8e7a61feda140102e1ce548ebab4f0be34fa 100644
--- a/core/profiles/demo_umami/themes/umami/js/components/messages/messages.js
+++ b/core/profiles/demo_umami/themes/umami/js/components/messages/messages.js
@@ -4,6 +4,20 @@
  */
 
 ((Drupal) => {
+  customElements.define(
+    'drupal-umami-messages',
+    class extends HTMLElement {
+      constructor() {
+        super();
+        const template = document.getElementById('umami-messages-template');
+        const templateContent = template.content;
+
+        const shadowRoot = this.attachShadow({ mode: 'open' });
+        shadowRoot.appendChild(templateContent.cloneNode(true));
+      }
+    },
+  );
+
   /**
    * Overrides message theme function.
    *
@@ -23,7 +37,7 @@
    */
   Drupal.theme.message = ({ text }, { type, id }) => {
     const messagesTypes = Drupal.Message.getMessageTypeLabels();
-    const messageWrapper = document.createElement('div');
+    const messageWrapper = document.createElement('drupal-umami-messages');
 
     messageWrapper.setAttribute('class', `messages messages--${type}`);
     messageWrapper.setAttribute(
@@ -34,14 +48,12 @@
     messageWrapper.setAttribute('data-drupal-message-type', type);
 
     messageWrapper.innerHTML = `
-    <div class="messages__content">
-      <h2 class="visually-hidden">
-        ${messagesTypes[type]}
-      </h2>
-      <span class="messages__item">
-        ${text}
-      </span>
-    </div>
+    <span slot="title">
+      ${messagesTypes[type]}
+    </span>
+    <span class="messages__item" slot="content">
+      ${text}
+    </span>
   `;
 
     return messageWrapper;
diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/block/block--system-messages-block.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/block/block--system-messages-block.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..216888c61f24e7926314ed6522c4746df536c9d2
--- /dev/null
+++ b/core/profiles/demo_umami/themes/umami/templates/classy/block/block--system-messages-block.html.twig
@@ -0,0 +1,38 @@
+{#
+/**
+ * @file
+ * Default theme implementation for the messages block.
+ *
+ * Removes wrapper elements from block so that empty block does not appear when
+ * there are no messages.
+ *
+ * Available variables:
+ * - content: The content of this block.
+ *
+ * @ingroup themeable
+ */
+#}
+{{ content }}
+
+{#
+  The Umami theme is for demonstration only.
+
+  Umami uses a web component for rendering messages. The component is
+  "<drupal-umami-messages>".
+
+  This implementation is a trial only and should not be used on production
+  sites.
+
+  @see https://www.drupal.org/project/drupal/issues/3476471
+  @see https://developer.mozilla.org/en-US/docs/Web/API/Web_components
+#}
+
+<template id="umami-messages-template">
+  <h2 part="heading">
+    <slot name="title"></slot>
+  </h2>
+  <div part="content">
+    <div part="icon"></div>
+    <slot name="content"></slot>
+  </div>
+</template>
diff --git a/core/profiles/demo_umami/themes/umami/templates/components/messages/status-messages.html.twig b/core/profiles/demo_umami/themes/umami/templates/components/messages/status-messages.html.twig
index 8f2127521b5bfb0732c1e30fa523b67122979fc1..cc133482c0607349f4ad64eb9c33f93740312247 100644
--- a/core/profiles/demo_umami/themes/umami/templates/components/messages/status-messages.html.twig
+++ b/core/profiles/demo_umami/themes/umami/templates/components/messages/status-messages.html.twig
@@ -22,36 +22,30 @@
 <div data-drupal-messages>
   <div class="messages__wrapper container">
   {% block messages %}
-  {% for type, messages in message_list %}
-    {%
-      set classes = [
-        'messages',
-        'messages--' ~ type,
-      ]
-    %}
-    <div
-      aria-label="{{ status_headings[type] }}"
-      {{ attributes.addClass(classes)|without('aria-label') }}
-      role={{ type == 'error' or type == 'warning' ? 'alert' : 'status' }}
-    >
-      <div class="messages__content">
+    {% for type, messages in message_list %}
+      {%
+        set classes = [
+          'messages',
+          'messages--' ~ type,
+        ]
+      %}
+      <drupal-umami-messages aria-label="{{ status_headings[type] }}" {{ attributes.addClass(classes)|without('aria-label') }} role={{ type == 'error' or type == 'warning' ? 'alert' : 'status' }}>
         {% if status_headings[type] %}
-          <h2 class="visually-hidden">{{ status_headings[type] }}</h2>
+          <span slot="title">{{ status_headings[type] }}</span>
         {% endif %}
-        {% if messages|length > 1 %}
-          <ul class="messages__list">
+        {% if messages|length > 0 %}
+          <ul class="messages__list" slot="content">
             {% for message in messages %}
               <li class="messages__item">{{ message }}</li>
             {% endfor %}
           </ul>
         {% else %}
-          <span class="messages__item">{{ messages|first }}</span>
+          <span class="messages__item" slot="content">{{ messages|first }}</span>
         {% endif %}
-      </div>
-    </div>
-    {# Remove type specific classes. #}
-    {% set attributes = attributes.removeClass(classes) %}
-  {% endfor %}
+      </drupal-umami-messages>
+      {# Remove type specific classes. #}
+      {% set attributes = attributes.removeClass(classes) %}
+    {% endfor %}
   {% endblock messages %}
   </div>
 </div>