diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index b25ab116aa..cf902af6cd 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -35,6 +35,7 @@ Changelog
  * Improve performance of empty search results by avoiding downloading the entire search index in these scenarios (Lars van de Kerkhof, Coen van der Kamp)
  * Replace `gulp-sass` with `gulp-dart-sass` to improve core development across different platforms (Thibaud Colas)
  * Add SVG icons to resolve accessibility and customisation issues and start using them in a subset of Wagtail's admin (Coen van der Kamp, Scott Cranfill, Thibaud Colas)
+ * Remove markup around rich text rendering by default, provide a way to use old behaviour via `wagtail.contrib.legacy.richtext` (Coen van der Kamp, Dan Braghis)
  * Fix: Support IPv6 domain (Alex Gleason, Coen van der Kamp)
  * Fix: Ensure link to add a new user works when no users are visible in the users list (LB (Ben Johnston))
  * Fix: `AbstractEmailForm` saved submission fields are now aligned with the email content fields, `form.cleaned_data` will be used instead of `form.fields` (Haydn Greatnews)
diff --git a/docs/getting_started/tutorial.rst b/docs/getting_started/tutorial.rst
index 819bd84333..eba012adfd 100644
--- a/docs/getting_started/tutorial.rst
+++ b/docs/getting_started/tutorial.rst
@@ -202,11 +202,9 @@ Produces:
 
 .. code-block:: html
 
-    <div class="rich-text">
-        <p>
-            <b>Welcome</b> to our new site!
-        </p>
-    </div>
+    <p>
+        <b>Welcome</b> to our new site!
+    </p>
 
 **Note:** You'll need to include ``{% load wagtailcore_tags %}`` in each
 template that uses Wagtail's tags. Django will throw a ``TemplateSyntaxError``
diff --git a/docs/reference/contrib/index.rst b/docs/reference/contrib/index.rst
index 3405da2c2e..dd26bbeca2 100644
--- a/docs/reference/contrib/index.rst
+++ b/docs/reference/contrib/index.rst
@@ -17,6 +17,7 @@ Wagtail ships with a variety of extra optional modules.
     searchpromotions
     table_block
     redirects
+    legacy_richtext
 
 
 :doc:`settings`
@@ -72,3 +73,9 @@ Provides a TableBlock for adding HTML tables to pages.
 -----------------------
 
 Provides a way to manage redirects.
+
+
+:doc:`legacy_richtext`
+-----------------------
+
+Provides the legacy richtext wrapper (``<div class="rich-text"></div>``).
diff --git a/docs/reference/contrib/legacy_richtext.rst b/docs/reference/contrib/legacy_richtext.rst
new file mode 100644
index 0000000000..64a613cbaf
--- /dev/null
+++ b/docs/reference/contrib/legacy_richtext.rst
@@ -0,0 +1,26 @@
+.. _legacy_richtext:
+
+=====================
+Legacy richtext
+=====================
+
+.. module:: wagtail.contrib.legacy.richtext
+
+Provides the legacy richtext wrapper.
+
+Place ``wagtail.contrib.legacy.richtext`` before ``wagtail.core`` in  ``INSTALLED_APPS``.
+
+ .. code-block:: python
+
+    INSTALLED_APPS = [
+        ...
+        "wagtail.contrib.legacy.richtext",
+        "wagtail.core",
+        ...
+    ]
+
+The ``{{ page.body|richtext }}`` template filter will now render:
+
+ .. code-block:: html+django
+
+    <div class="rich-text">...</div>
diff --git a/docs/releases/2.10.rst b/docs/releases/2.10.rst
index 5ef21045d5..c25cfa9ba5 100644
--- a/docs/releases/2.10.rst
+++ b/docs/releases/2.10.rst
@@ -48,6 +48,7 @@ Other features
  * Improve performance of empty search results by avoiding downloading the entire search index in these scenarios (Lars van de Kerkhof, Coen van der Kamp)
  * Replace ``gulp-sass`` with ``gulp-dart-sass`` to improve core development across different platforms (Thibaud Colas)
  * Add SVG icons to resolve accessibility and customisation issues and start using them in a subset of Wagtail's admin (Coen van der Kamp, Scott Cranfill, Thibaud Colas)
+ * Remove markup around rich text rendering by default, provide a way to use old behaviour via ``wagtail.contrib.legacy.richtext``. See :ref:`legacy_richtext`. (Coen van der Kamp, Dan Braghis)
 
 
 Bug fixes
@@ -66,6 +67,9 @@ Bug fixes
 Upgrade considerations
 ======================
 
+ * Rich text will now be rendered without any markup, to use the old behaviour where a wrapper ``<div class="richt-text">`` is used see :ref:`legacy_richtext`.
+
+
 Removed support for Python 3.5
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/wagtail/admin/tests/test_rich_text.py b/wagtail/admin/tests/test_rich_text.py
index 5ea01cc872..8aadafdf74 100644
--- a/wagtail/admin/tests/test_rich_text.py
+++ b/wagtail/admin/tests/test_rich_text.py
@@ -323,8 +323,7 @@ class TestRichTextValue(TestCase):
         value = RichText(text)
         result = str(value)
         expected = (
-            '<div class="rich-text"><p>To the <a href="'
-            '/foo/pointless-suffix/">moon</a>!</p></div>')
+            '<p>To the <a href="/foo/pointless-suffix/">moon</a>!</p>')
         self.assertEqual(result, expected)
 
 
diff --git a/wagtail/contrib/legacy/richtext/templates/wagtailcore/shared/richtext.html b/wagtail/contrib/legacy/richtext/templates/wagtailcore/shared/richtext.html
new file mode 100644
index 0000000000..12e914c8ac
--- /dev/null
+++ b/wagtail/contrib/legacy/richtext/templates/wagtailcore/shared/richtext.html
@@ -0,0 +1 @@
+<div class="rich-text">{{ html|safe }}</div>
\ No newline at end of file
diff --git a/wagtail/contrib/legacy/richtext/tests/__init__.py b/wagtail/contrib/legacy/richtext/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/wagtail/contrib/legacy/richtext/tests/test_templatetag.py b/wagtail/contrib/legacy/richtext/tests/test_templatetag.py
new file mode 100644
index 0000000000..e5e7254753
--- /dev/null
+++ b/wagtail/contrib/legacy/richtext/tests/test_templatetag.py
@@ -0,0 +1,14 @@
+from django.test import TestCase
+
+from wagtail.core.templatetags.wagtailcore_tags import richtext
+
+
+class TestTemplateTag(TestCase):
+    def test_no_contrib_legacy_richtext_no_wrapper(self):
+        self.assertEqual(richtext("Foo"), "Foo")
+
+    def test_contrib_legacy_richtext_renders_wrapper(self):
+        with self.modify_settings(
+            INSTALLED_APPS={"prepend": "wagtail.contrib.legacy.richtext"}
+        ):
+            self.assertEqual(richtext("Foo"), """<div class="rich-text">Foo</div>""")
diff --git a/wagtail/core/rich_text/__init__.py b/wagtail/core/rich_text/__init__.py
index 6eef65d738..e90b37d333 100644
--- a/wagtail/core/rich_text/__init__.py
+++ b/wagtail/core/rich_text/__init__.py
@@ -1,4 +1,5 @@
 from django.db.models import Model
+from django.template.loader import render_to_string
 from django.utils.safestring import mark_safe
 
 from wagtail.core.rich_text.feature_registry import FeatureRegistry
@@ -43,7 +44,7 @@ class RichText:
         self.source = (source or '')
 
     def __html__(self):
-        return '<div class="rich-text">' + expand_db_html(self.source) + '</div>'
+        return render_to_string('wagtailcore/shared/richtext.html', {'html': expand_db_html(self.source)})
 
     def __str__(self):
         return mark_safe(self.__html__())
diff --git a/wagtail/core/templates/wagtailcore/shared/richtext.html b/wagtail/core/templates/wagtailcore/shared/richtext.html
new file mode 100644
index 0000000000..cd10e94a25
--- /dev/null
+++ b/wagtail/core/templates/wagtailcore/shared/richtext.html
@@ -0,0 +1 @@
+{{ html|safe }}
\ No newline at end of file
diff --git a/wagtail/core/templatetags/wagtailcore_tags.py b/wagtail/core/templatetags/wagtailcore_tags.py
index ef871a5738..b7d8287602 100644
--- a/wagtail/core/templatetags/wagtailcore_tags.py
+++ b/wagtail/core/templatetags/wagtailcore_tags.py
@@ -1,8 +1,8 @@
 from django import template
 from django.shortcuts import reverse
 from django.template.defaulttags import token_kwargs
+from django.template.loader import render_to_string
 from django.utils.encoding import force_str
-from django.utils.safestring import mark_safe
 
 from wagtail import VERSION, __version__
 from wagtail.core.models import Page, Site
@@ -104,8 +104,7 @@ def richtext(value):
             html = expand_db_html(value)
         else:
             raise TypeError("'richtext' template filter received an invalid value; expected string, got {}.".format(type(value)))
-
-    return mark_safe('<div class="rich-text">' + html + '</div>')
+    return render_to_string('wagtailcore/shared/richtext.html', {'html': html})
 
 
 class IncludeBlockNode(template.Node):
diff --git a/wagtail/core/tests/test_blocks.py b/wagtail/core/tests/test_blocks.py
index f39350736c..93885674f8 100644
--- a/wagtail/core/tests/test_blocks.py
+++ b/wagtail/core/tests/test_blocks.py
@@ -508,7 +508,7 @@ class TestRichTextBlock(TestCase):
         value = RichText('<p>Merry <a linktype="page" id="4">Christmas</a>!</p>')
         result = block.render(value)
         self.assertEqual(
-            result, '<div class="rich-text"><p>Merry <a href="/events/christmas/">Christmas</a>!</p></div>'
+            result, '<p>Merry <a href="/events/christmas/">Christmas</a>!</p>'
         )
 
     def test_render_form(self):
@@ -1448,7 +1448,7 @@ class TestStructBlock(SimpleTestCase):
             'body': '<b>world</b>',
         })
         body_bound_block = struct_value.bound_blocks['body']
-        expected = '<div class="rich-text"><b>world</b></div>'
+        expected = '<b>world</b>'
         self.assertEqual(str(body_bound_block), expected)
 
     def test_get_form_context(self):
@@ -1725,13 +1725,13 @@ class TestStructBlock(SimpleTestCase):
         block = SectionBlock()
         value = block.to_python({'title': 'Hello', 'body': '<i>italic</i> world'})
         result = block.render(value)
-        self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
+        self.assertEqual(result, """<h1>Hello</h1><i>italic</i> world""")
 
     def test_render_block_with_extra_context(self):
         block = SectionBlock()
         value = block.to_python({'title': 'Bonjour', 'body': 'monde <i>italique</i>'})
         result = block.render(value, context={'language': 'fr'})
-        self.assertEqual(result, """<h1 lang="fr">Bonjour</h1><div class="rich-text">monde <i>italique</i></div>""")
+        self.assertEqual(result, """<h1 lang="fr">Bonjour</h1>monde <i>italique</i>""")
 
     def test_render_structvalue(self):
         """
@@ -1740,11 +1740,11 @@ class TestStructBlock(SimpleTestCase):
         block = SectionBlock()
         value = block.to_python({'title': 'Hello', 'body': '<i>italic</i> world'})
         result = value.__html__()
-        self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
+        self.assertEqual(result, """<h1>Hello</h1><i>italic</i> world""")
 
         # value.render_as_block() should be equivalent to value.__html__()
         result = value.render_as_block()
-        self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
+        self.assertEqual(result, """<h1>Hello</h1><i>italic</i> world""")
 
     def test_str_structvalue(self):
         """
@@ -1769,7 +1769,7 @@ class TestStructBlock(SimpleTestCase):
         block = SectionBlock()
         value = block.to_python({'title': 'Bonjour', 'body': 'monde <i>italique</i>'})
         result = value.render_as_block(context={'language': 'fr'})
-        self.assertEqual(result, """<h1 lang="fr">Bonjour</h1><div class="rich-text">monde <i>italique</i></div>""")
+        self.assertEqual(result, """<h1 lang="fr">Bonjour</h1>monde <i>italique</i>""")
 
 
 class TestStructBlockWithCustomStructValue(SimpleTestCase):
@@ -2493,8 +2493,8 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
         ])
 
         self.assertIn('<div class="block-heading">My title</div>', html)
-        self.assertIn('<div class="block-paragraph"><div class="rich-text">My <i>first</i> paragraph</div></div>', html)
-        self.assertIn('<div class="block-paragraph"><div class="rich-text">My second paragraph</div></div>', html)
+        self.assertIn('<div class="block-paragraph">My <i>first</i> paragraph</div>', html)
+        self.assertIn('<div class="block-paragraph">My second paragraph</div>', html)
 
     def test_render_unknown_type(self):
         # This can happen if a developer removes a type from their StreamBlock
@@ -2510,7 +2510,7 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
         ])
         self.assertNotIn('foo', html)
         self.assertNotIn('Hello', html)
-        self.assertIn('<div class="block-paragraph"><div class="rich-text">My first paragraph</div></div>', html)
+        self.assertIn('<div class="block-paragraph">My first paragraph</div>', html)
 
     def test_render_calls_block_render_on_children(self):
         """
@@ -3674,7 +3674,7 @@ class TestIncludeBlockTag(TestCase):
         })
 
         self.assertIn(
-            """<body><h1 lang="fr">Bonjour</h1><div class="rich-text">monde <i>italique</i></div></body>""",
+            """<body><h1 lang="fr">Bonjour</h1>monde <i>italique</i></body>""",
             result
         )
 
diff --git a/wagtail/core/tests/test_jinja2.py b/wagtail/core/tests/test_jinja2.py
index a47406e783..7685a89f68 100644
--- a/wagtail/core/tests/test_jinja2.py
+++ b/wagtail/core/tests/test_jinja2.py
@@ -33,7 +33,7 @@ class TestCoreGlobalsAndFilters(TestCase):
         richtext = '<p>Merry <a linktype="page" id="2">Christmas</a>!</p>'
         self.assertEqual(
             self.render('{{ text|richtext }}', {'text': richtext}),
-            '<div class="rich-text"><p>Merry <a href="/">Christmas</a>!</p></div>')
+            '<p>Merry <a href="/">Christmas</a>!</p>')
 
     def test_pageurl(self):
         page = Page.objects.get(pk=2)
@@ -95,7 +95,7 @@ class TestJinjaEscaping(TestCase):
             'value': stream_value,
         })
 
-        self.assertIn('<div class="rich-text"><p>Merry <a href="/events/christmas/">Christmas</a>!</p></div>', result)
+        self.assertIn('<p>Merry <a href="/events/christmas/">Christmas</a>!</p>', result)
 
 
 class TestIncludeBlockTag(TestCase):
@@ -127,7 +127,7 @@ class TestIncludeBlockTag(TestCase):
         })
 
         self.assertIn(
-            """<body><h1 lang="fr">Bonjour</h1><div class="rich-text">monde <i>italique</i></div></body>""",
+            """<body><h1 lang="fr">Bonjour</h1>monde <i>italique</i></body>""",
             result
         )
 
diff --git a/wagtail/core/tests/test_rich_text.py b/wagtail/core/tests/test_rich_text.py
index c3806e2fd2..3962509f7f 100644
--- a/wagtail/core/tests/test_rich_text.py
+++ b/wagtail/core/tests/test_rich_text.py
@@ -67,7 +67,7 @@ class TestRichTextValue(TestCase):
         result = str(value)
         self.assertEqual(
             result,
-            '<div class="rich-text"><p>Merry <a href="/events/christmas/">Christmas</a>!</p></div>'
+            '<p>Merry <a href="/events/christmas/">Christmas</a>!</p>'
         )
 
     def test_evaluate_value(self):
diff --git a/wagtail/core/tests/test_streamfield.py b/wagtail/core/tests/test_streamfield.py
index 64b9442641..01bec7272f 100644
--- a/wagtail/core/tests/test_streamfield.py
+++ b/wagtail/core/tests/test_streamfield.py
@@ -192,8 +192,8 @@ class TestStreamFieldRenderingBase(TestCase):
 
         img_tag = self.image.get_rendition('original').img_tag()
         self.expected = ''.join([
-            '<div class="block-rich_text"><div class="rich-text"><p>Rich text</p></div></div>',
-            '<div class="block-rich_text"><div class="rich-text"><p>Привет, Микола</p></div></div>',
+            '<div class="block-rich_text"><p>Rich text</p></div>',
+            '<div class="block-rich_text"><p>Привет, Микола</p></div>',
             '<div class="block-image">{}</div>'.format(img_tag),
             '<div class="block-text">Hello, World!</div>',
         ])
diff --git a/wagtail/core/tests/tests.py b/wagtail/core/tests/tests.py
index 42ff571cde..3ddb824d1d 100644
--- a/wagtail/core/tests/tests.py
+++ b/wagtail/core/tests/tests.py
@@ -311,12 +311,12 @@ class TestResolveModelString(TestCase):
 class TestRichtextTag(TestCase):
     def test_call_with_text(self):
         result = richtext("Hello world!")
-        self.assertEqual(result, '<div class="rich-text">Hello world!</div>')
+        self.assertEqual(result, 'Hello world!')
         self.assertIsInstance(result, SafeString)
 
     def test_call_with_none(self):
         result = richtext(None)
-        self.assertEqual(result, '<div class="rich-text"></div>')
+        self.assertEqual(result, '')
 
     def test_call_with_invalid_value(self):
         with self.assertRaisesRegex(TypeError, "'richtext' template filter received an invalid value"):