diff --git a/src/TurboStreamRender/src/Core/TurboStream.php b/src/TurboStreamRender/src/Core/TurboStream.php
new file mode 100644
index 00000000000..77616975e9b
--- /dev/null
+++ b/src/TurboStreamRender/src/Core/TurboStream.php
@@ -0,0 +1,19 @@
+addStream(new TurboStream($target, $action, $view, $parameters));
+ }
+
+ public function addStream(TurboStream $stream): self
+ {
+ $this->streams[] = $stream;
+
+ return $this;
+ }
+
+ /**
+ * @return TurboStream[]
+ */
+ public function all(): array
+ {
+ return $this->streams;
+ }
+}
diff --git a/src/TurboStreamRender/src/Core/TurboStreamRenderer.php b/src/TurboStreamRender/src/Core/TurboStreamRenderer.php
new file mode 100644
index 00000000000..b6d4e973ad3
--- /dev/null
+++ b/src/TurboStreamRender/src/Core/TurboStreamRenderer.php
@@ -0,0 +1,68 @@
+renderer = $renderer;
+ }
+
+ /**
+ * @throws \InvalidRendererException
+ */
+ public function renderView(string $view, array $parameters = []): string
+ {
+ try {
+ $renderer = $this->renderer;
+ return $renderer($view, $parameters);
+ }catch (\Throwable $e) {
+ throw new \InvalidRendererException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ public function renderAsStreamView(string $target, TurboAction $action, string $view, array $parameters = []): string
+ {
+ $streams = (new TurboStreamCollection())->add($target, $action, $view, $parameters);
+
+ return $this->renderTurboStreamsView($streams->all());
+ }
+
+ /**
+ * @throws \InvalidRendererException
+ */
+ public function renderTurboStreamsView(array $streams): string
+ {
+ $response = "";
+ foreach ($streams as $stream) {
+ $content = $this->renderView($stream['view'], $stream['parameters']);
+ $response .= $this->wrapWithTurboStream($stream['target'], $stream['action'], $content);
+ $response .= "\n";
+ }
+
+ return $response;
+ }
+
+ private function wrapWithTurboStream(string $target, TurboAction $action, string $content): string
+ {
+ return <<
+
+ $content
+
+
+ HTML;
+ }
+}
diff --git a/src/TurboStreamRender/src/Enum/TurboAction.php b/src/TurboStreamRender/src/Enum/TurboAction.php
new file mode 100644
index 00000000000..19081557392
--- /dev/null
+++ b/src/TurboStreamRender/src/Enum/TurboAction.php
@@ -0,0 +1,17 @@
+add($target, $action, $view, $parameters);
+
+ return $this->renderTurboStreamsView($streams->all());
+ }
+
+ /**
+ * @param TurboStream[] $streams
+ */
+ protected function renderTurboStreamsView(array $streams): string
+ {
+ return $this->getTurboStreamRenderer()->renderTurboStreamsView($streams);
+ }
+
+ private function wrapWithTurboStream(string $target, TurboAction $action, string $content): string
+ {
+ return <<
+
+ $content
+
+
+ HTML;
+ }
+}
diff --git a/src/TurboStreamResponse/src/Trait/TurboResponseTrait.php b/src/TurboStreamResponse/src/Trait/TurboResponseTrait.php
new file mode 100644
index 00000000000..249f153242a
--- /dev/null
+++ b/src/TurboStreamResponse/src/Trait/TurboResponseTrait.php
@@ -0,0 +1,66 @@
+renderTurboStreams([new TurboStream($target, $action, $view, $parameters)]);
+ }
+
+ /**
+ * @param TurboStream[] $streams
+ */
+ protected function renderTurboStreams(array $streams): Response
+ {
+ $content = $this->renderTurboStreamsView($streams);
+ $response ??= new Response();
+
+ // I would like to keep this part of the standard AbstractController, but I don't really like the idea of duplication the code.
+ // Any suggestions for a way to reuse it?
+
+ foreach ($streams as $stream) {
+ if (200 === $response->getStatusCode()) {
+ $parameters = $stream->getParameters();
+ foreach ($parameters as $v) {
+ if ($v instanceof FormInterface && $v->isSubmitted() && !$v->isValid()) {
+ $response->setStatusCode(422);
+ break 2;
+ }
+ }
+ }
+ }
+
+ $response->setContent($content);
+ $response->headers->set('Content-Type', TurboBundle::STREAM_MEDIA_TYPE);
+
+ return $response;
+ }
+
+ private function wrapWithTurboStream(string $target, TurboAction $action, string $content): string
+ {
+ return <<
+
+ $content
+
+
+ HTML;
+ }
+}
diff --git a/ux.symfony.com/templates/components/TurboStream.html.twig b/ux.symfony.com/templates/components/TurboStream.html.twig
new file mode 100644
index 00000000000..4a083d8d18f
--- /dev/null
+++ b/ux.symfony.com/templates/components/TurboStream.html.twig
@@ -0,0 +1,5 @@
+
+
+ {% block content %}{% endblock %}
+
+