diff --git a/.github/workflows/test-turbo.yml b/.github/workflows/test-turbo.yml
index fb2b145e4c3..47008d2605a 100644
--- a/.github/workflows/test-turbo.yml
+++ b/.github/workflows/test-turbo.yml
@@ -8,6 +8,9 @@ on:
         paths:
             - 'src/Turbo/**'
 
+env:
+    SYMFONY_REQUIRE: '>=5.4'
+
 jobs:
     phpstan:
         runs-on: ubuntu-latest
@@ -38,11 +41,11 @@ jobs:
         strategy:
            fail-fast: false
            matrix:
-               php-version: ['8.1', '8.3']
+               php-version: ['8.1', '8.4']
                include:
                   - php-version: '8.1'
                     dependency-version: 'lowest'
-                  - php-version: '8.3'
+                  - php-version: '8.4'
                     dependency-version: 'highest'
 
         services:
@@ -69,6 +72,9 @@ jobs:
               with:
                   php-version: ${{ matrix.php-version }}
 
+            - name: Install symfony/flex
+              run: composer global config allow-plugins.symfony/flex true && composer global require symfony/flex
+
             - name: Install Turbo packages
               uses: ramsey/composer-install@v3
               with:
@@ -101,6 +107,8 @@ jobs:
 
             - name: Run tests
               working-directory: src/Turbo
-              run: vendor/bin/simple-phpunit
+              run: |
+                [ 'lowest' = '${{ matrix.dependency-version }}' ] && export SYMFONY_DEPRECATIONS_HELPER=weak
+                vendor/bin/simple-phpunit
               env:
                   SYMFONY_DEPRECATIONS_HELPER: 'max[self]=1'
diff --git a/src/Turbo/CHANGELOG.md b/src/Turbo/CHANGELOG.md
index d3e4d1c9eeb..df89967eb89 100644
--- a/src/Turbo/CHANGELOG.md
+++ b/src/Turbo/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 -   Add `Helper/TurboStream::append()` et al. methods
 -   Add `TurboStreamResponse`
+-   Add `<twig:Turbo:Stream:*>` components
 
 ## 2.19.0
 
diff --git a/src/Turbo/composer.json b/src/Turbo/composer.json
index 46acc1f579c..e3cb46514a4 100644
--- a/src/Turbo/composer.json
+++ b/src/Turbo/composer.json
@@ -52,6 +52,7 @@
         "symfony/property-access": "^5.4|^6.0|^7.0",
         "symfony/security-core": "^5.4|^6.0|^7.0",
         "symfony/stopwatch": "^5.4|^6.0|^7.0",
+        "symfony/ux-twig-component": "^2.21",
         "symfony/twig-bundle": "^5.4|^6.0|^7.0",
         "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0",
         "symfony/webpack-encore-bundle": "^2.1.1",
diff --git a/src/Turbo/templates/components/Stream/After.html.twig b/src/Turbo/templates/components/Stream/After.html.twig
new file mode 100644
index 00000000000..3105a376dec
--- /dev/null
+++ b/src/Turbo/templates/components/Stream/After.html.twig
@@ -0,0 +1,5 @@
+{% props target -%}
+
+<turbo-stream action="after" targets="{{ target }}" {{ attributes }}>
+    <template>{% block content %}{% endblock %}</template>
+</turbo-stream>
diff --git a/src/Turbo/templates/components/Stream/Append.html.twig b/src/Turbo/templates/components/Stream/Append.html.twig
new file mode 100644
index 00000000000..aea22f28227
--- /dev/null
+++ b/src/Turbo/templates/components/Stream/Append.html.twig
@@ -0,0 +1,5 @@
+{% props target -%}
+
+<turbo-stream action="append" targets="{{ target }}" {{ attributes }}>
+    <template>{% block content %}{% endblock %}</template>
+</turbo-stream>
diff --git a/src/Turbo/templates/components/Stream/Before.html.twig b/src/Turbo/templates/components/Stream/Before.html.twig
new file mode 100644
index 00000000000..3ffdeded6aa
--- /dev/null
+++ b/src/Turbo/templates/components/Stream/Before.html.twig
@@ -0,0 +1,5 @@
+{% props target -%}
+
+<turbo-stream action="before" targets="{{ target }}" {{ attributes }}>
+    <template>{% block content %}{% endblock %}</template>
+</turbo-stream>
diff --git a/src/Turbo/templates/components/Stream/Prepend.html.twig b/src/Turbo/templates/components/Stream/Prepend.html.twig
new file mode 100644
index 00000000000..b75d3534ef9
--- /dev/null
+++ b/src/Turbo/templates/components/Stream/Prepend.html.twig
@@ -0,0 +1,5 @@
+{% props target -%}
+
+<turbo-stream action="prepend" targets="{{ target }}" {{ attributes }}>
+    <template>{% block content %}{% endblock %}</template>
+</turbo-stream>
diff --git a/src/Turbo/templates/components/Stream/Refresh.html.twig b/src/Turbo/templates/components/Stream/Refresh.html.twig
new file mode 100644
index 00000000000..182129e6533
--- /dev/null
+++ b/src/Turbo/templates/components/Stream/Refresh.html.twig
@@ -0,0 +1,3 @@
+{% props requestId = null -%}
+
+<turbo-stream action="refresh"{% if requestId is not null %} request-id="{{ requestId }}"{% endif %} {{ attributes }}></turbo-stream>
diff --git a/src/Turbo/templates/components/Stream/Remove.html.twig b/src/Turbo/templates/components/Stream/Remove.html.twig
new file mode 100644
index 00000000000..9e3c023480d
--- /dev/null
+++ b/src/Turbo/templates/components/Stream/Remove.html.twig
@@ -0,0 +1,3 @@
+{% props target -%}
+
+<turbo-stream action="remove" targets="{{ target }}" {{ attributes }}></turbo-stream>
diff --git a/src/Turbo/templates/components/Stream/Replace.html.twig b/src/Turbo/templates/components/Stream/Replace.html.twig
new file mode 100644
index 00000000000..d11598f6885
--- /dev/null
+++ b/src/Turbo/templates/components/Stream/Replace.html.twig
@@ -0,0 +1,5 @@
+{% props target, morph = false -%}
+
+<turbo-stream action="replace" targets="{{ target }}"{% if morph %} method="morph"{% endif %} {{ attributes }}>
+    <template>{% block content %}{% endblock %}</template>
+</turbo-stream>
diff --git a/src/Turbo/templates/components/Stream/Update.html.twig b/src/Turbo/templates/components/Stream/Update.html.twig
new file mode 100644
index 00000000000..f15fbfc0094
--- /dev/null
+++ b/src/Turbo/templates/components/Stream/Update.html.twig
@@ -0,0 +1,5 @@
+{% props target, morph = false -%}
+
+<turbo-stream action="update" targets="{{ target }}"{% if morph %} method="morph"{% endif %} {{ attributes }}>
+    <template>{% block content %}{% endblock %}</template>
+</turbo-stream>
diff --git a/src/Turbo/tests/app/Kernel.php b/src/Turbo/tests/app/Kernel.php
index f61de905272..5e2594135e0 100644
--- a/src/Turbo/tests/app/Kernel.php
+++ b/src/Turbo/tests/app/Kernel.php
@@ -38,6 +38,7 @@
 use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
 use Symfony\UX\StimulusBundle\StimulusBundle;
 use Symfony\UX\Turbo\TurboBundle;
+use Symfony\UX\TwigComponent\TwigComponentBundle;
 use Symfony\WebpackEncoreBundle\WebpackEncoreBundle;
 use Twig\Environment;
 
@@ -54,6 +55,7 @@ public function registerBundles(): iterable
         yield new DoctrineBundle();
         yield new TwigBundle();
         yield new MercureBundle();
+        yield new TwigComponentBundle();
         yield new TurboBundle();
         yield new WebpackEncoreBundle();
         yield new StimulusBundle();
@@ -120,6 +122,11 @@ protected function configureContainer(ContainerConfigurator $container): void
                 ],
             ],
         ]);
+
+        $container->extension('twig_component', [
+            'anonymous_template_directory' => 'components/',
+            'defaults' => ['App\Twig\Components\\' => 'components/'],
+        ]);
     }
 
     protected function configureRoutes(RoutingConfigurator $routes): void
diff --git a/src/Turbo/tests/app/templates/broadcast/Artist.stream.html.twig b/src/Turbo/tests/app/templates/broadcast/Artist.stream.html.twig
index 7d7d7bd59f4..f975b3ca5ca 100644
--- a/src/Turbo/tests/app/templates/broadcast/Artist.stream.html.twig
+++ b/src/Turbo/tests/app/templates/broadcast/Artist.stream.html.twig
@@ -1,19 +1,15 @@
 {% block create %}
-    <turbo-stream action="append" target="artists">
-        <template>
-            <div id="{{ 'artist_' ~ id }}"><a href="{{ path('artist', {id: id}) }}">{{ entity.name }} (#{{ id }})</a></div>
-        </template>
-    </turbo-stream>
+    <twig:Turbo:Stream:Append target="#artists">
+        <div id="artist_{{ id }}"><a href="{{ path('artist', {id: id}) }}">{{ entity.name }} (#{{ id }})</a></div>
+    </twig:Turbo:Stream:Append>
 {% endblock %}
 
 {% block update %}
-    <turbo-stream action="update" target="artist_{{ id }}">
-        <template>
-            {{ entity.name }} (#{{ id }}, updated)
-        </template>
-    </turbo-stream>
+    <twig:Turbo:Stream:Update target="#artist_{{ id }}">
+        {{ entity.name }} (#{{ id }}, updated)
+    </twig:Turbo:Stream:Update>
 {% endblock %}
 
 {% block remove %}
-    <turbo-stream action="remove" target="artist_{{ id }}"></turbo-stream>
+    <twig:Turbo:Stream:Remove target="#artist_{{ id }}"></twig:Turbo:Stream:Remove>
 {% endblock %}
diff --git a/src/Turbo/tests/app/templates/broadcast/Book.stream.html.twig b/src/Turbo/tests/app/templates/broadcast/Book.stream.html.twig
index 94b1d72afc2..1ed70a70de1 100644
--- a/src/Turbo/tests/app/templates/broadcast/Book.stream.html.twig
+++ b/src/Turbo/tests/app/templates/broadcast/Book.stream.html.twig
@@ -1,19 +1,15 @@
 {% block create %}
-    <turbo-stream action="append" target="books">
-        <template>
-            <div id="{{ 'book_' ~ id }}">{{ entity.title }} (#{{ id }})</div>
-        </template>
-    </turbo-stream>
+    <twig:Turbo:Stream:Append target="#books">
+        <div id="book_{{ id }}">{{ entity.title }} (#{{ id }})</div>
+    </twig:Turbo:Stream:Append>
 {% endblock %}
 
 {% block update %}
-    <turbo-stream action="update" target="book_{{ id }}">
-        <template>
-            {{ entity.title }} (#{{ id }}, updated)
-        </template>
-    </turbo-stream>
+    <twig:Turbo:Stream:Update target="#book_{{ id }}">
+        {{ entity.title }} (#{{ id }}, updated)
+    </twig:Turbo:Stream:Update>
 {% endblock %}
 
 {% block remove %}
-    <turbo-stream action="remove" target="book_{{ id }}"></turbo-stream>
+    <twig:Turbo:Stream:Remove target="#book_{{ id }}"></twig:Turbo:Stream:Remove>
 {% endblock %}
diff --git a/src/Turbo/tests/app/templates/broadcast/Song.stream.html.twig b/src/Turbo/tests/app/templates/broadcast/Song.stream.html.twig
index 14b283c435c..f2719f3b1e8 100644
--- a/src/Turbo/tests/app/templates/broadcast/Song.stream.html.twig
+++ b/src/Turbo/tests/app/templates/broadcast/Song.stream.html.twig
@@ -1,19 +1,15 @@
 {% block create %}
-    <turbo-stream action="append" target="songs">
-        <template>
-            <div id="{{ 'song_' ~ id }}">{{ entity.title }} (#{{ id }}){% if entity.artist %} by {{ entity.artist.name }} (#{{ entity.artist.id }}){% endif %}</div>
-        </template>
-    </turbo-stream>
+    <twig:Turbo:Stream:Append target="#songs">
+        <div id="song_{{ id }}">{{ entity.title }} (#{{ id }}){% if entity.artist %} by {{ entity.artist.name }} (#{{ entity.artist.id }}){% endif %}</div>
+    </twig:Turbo:Stream:Append>
 {% endblock %}
 
 {% block update %}
-    <turbo-stream action="update" target="song_{{ id }}">
-        <template>
-            {{ entity.title }} (#{{ id }}, updated)
-        </template>
-    </turbo-stream>
+    <twig:Turbo:Stream:Update target="#song_{{ id }}">
+        {{ entity.title }} (#{{ id }}, updated)
+    </twig:Turbo:Stream:Update>
 {% endblock %}
 
 {% block remove %}
-    <turbo-stream action="remove" target="song_{{ id }}"></turbo-stream>
+    <twig:Turbo:Stream:Remove target="#song_{{ id }}"></twig:Turbo:Stream:Remove>
 {% endblock %}
diff --git a/src/Turbo/tests/app/templates/chat/message.stream.html.twig b/src/Turbo/tests/app/templates/chat/message.stream.html.twig
index 00bffdd5f23..791a5dcce01 100644
--- a/src/Turbo/tests/app/templates/chat/message.stream.html.twig
+++ b/src/Turbo/tests/app/templates/chat/message.stream.html.twig
@@ -1,5 +1,3 @@
-<turbo-stream action="append" target="messages">
-    <template>
-        <div>{{ message }}</div>
-    </template>
-</turbo-stream>
+<twig:Turbo:Stream:Append target="#messages">
+    <div>{{ message }}</div>
+</twig:Turbo:Stream:Append>
diff --git a/src/Turbo/tests/app/templates/form.stream.html.twig b/src/Turbo/tests/app/templates/form.stream.html.twig
index 043c31da36a..0c848d830f1 100644
--- a/src/Turbo/tests/app/templates/form.stream.html.twig
+++ b/src/Turbo/tests/app/templates/form.stream.html.twig
@@ -1,13 +1,9 @@
-<turbo-stream action="replace" target="form">
-    <template>
-        <div id="form">
-            This div replaces the existing element with the DOM ID "form".
-        </div>
-    </template>
-</turbo-stream>
+<twig:Turbo:Stream:Replace target="#form">
+    <div id="form">
+        This div replaces the existing element with the DOM ID "form".
+    </div>
+</twig:Turbo:Stream:Replace>
 
-<turbo-stream action="append" target="another_block">
-    <template>
-        <div>Appended!</div>
-    </template>
-</turbo-stream>
+<twig:Turbo:Stream:Append target="#another_block">
+    <div>Appended!</div>
+</twig:Turbo:Stream:Append>