diff --git a/docs/source/morphing.md b/docs/source/morphing.md
new file mode 100644
index 0000000..762b5f6
--- /dev/null
+++ b/docs/source/morphing.md
@@ -0,0 +1,21 @@
+## turbo_refreshes_with
+
+This template tag generates HTML meta tags for Turbo Drive to control the refresh method and scroll behavior in Turbo 8.0+ ([currently in beta](https://github.com/hotwired/turbo/releases/tag/v8.0.0-beta.4)).
+
+### Parameters
+
+- `method` (optional, default: `'replace'`): Specifies the refresh method. Must be one of `'replace'` or `'morph'`.
+- `scroll` (optional, default: `'reset'`): Specifies the scroll behavior. Must be one of `'reset'` or `'preserve'`.
+
+### Behavior
+
+This tag creates HTML meta tags defining the Turbo Frames' refresh method and scroll behavior based on the provided parameters. If the provided parameters are not within the valid options, a `ValidationError` is raised.
+
+### Example Usage
+
+```django
+{% load turbo_helper %}
+
+
+{% turbo_refreshes_with method='replace' scroll='reset' %}
+```
diff --git a/src/turbo_helper/templatetags/turbo_helper.py b/src/turbo_helper/templatetags/turbo_helper.py
index be239c8..d383e28 100644
--- a/src/turbo_helper/templatetags/turbo_helper.py
+++ b/src/turbo_helper/templatetags/turbo_helper.py
@@ -3,7 +3,9 @@
from django import template
from django.db.models.base import Model
from django.template import Node, TemplateSyntaxError
+from django.core.exceptions import ValidationError
from django.template.base import token_kwargs
+from django.utils.html import format_html
from turbo_helper.renderers import render_turbo_frame, render_turbo_stream_from
from turbo_helper.stream import action_proxy
@@ -260,3 +262,19 @@ def turbo_stream_from_tag(parser, token):
stream_name_array = [parser.compile_filter(bit) for bit in remaining_bits]
return TurboStreamFromTagNode(stream_name_array)
+
+
+@register.simple_tag
+ def turbo_refreshes_with(method='replace', scroll='reset') -> str:
+ valid_methods = ['replace', 'morph']
+ valid_scrolls = ['reset', 'preserve']
+
+ if method not in valid_methods:
+ raise ValidationError(f"Invalid refresh option '{method}'")
+ if scroll not in valid_scrolls:
+ raise ValidationError(f"Invalid scroll option '{scroll}'")
+
+ meta_tags = f''
+ meta_tags += f''
+
+ return format_html(meta_tags)
diff --git a/tests/test_tags.py b/tests/test_tags.py
index 447912c..f4041c3 100644
--- a/tests/test_tags.py
+++ b/tests/test_tags.py
@@ -1,4 +1,5 @@
import pytest
+from django.core.exceptions import ValidationError
from django.template import Context, Template
from tests.testapp.models import TodoItem
@@ -169,3 +170,37 @@ def test_dom_id_variable(self):
output
== ''
)
+
+
+class TestRefreshesWith:
+ def test_turbo_refreshes_with_tag(self):
+ template = """
+ {% load turbo_helper %}
+
+ {% turbo_refreshes_with method='replace' scroll='reset' %}
+ """
+
+ output = render(template, {}).strip()
+
+ expected_method_tag = ''
+ expected_scroll_tag = ''
+
+ assert expected_method_tag in output
+ assert expected_scroll_tag in output
+
+ def test_invalid_options(self):
+ with pytest.raises(ValidationError):
+ template = """
+ {% load turbo_helper %}
+
+ {% turbo_refreshes_with method='invalid' scroll='reset' %}
+ """
+ output = render(template, {}).strip()
+
+ with pytest.raises(ValidationError):
+ template = """
+ {% load turbo_helper %}
+
+ {% turbo_refreshes_with method='replace' scroll='invalid' %}
+ """
+ output = render(template, {}).strip()