diff --git a/wagtail/admin/templates/wagtailadmin/tables/column_header.html b/wagtail/admin/templates/wagtailadmin/tables/column_header.html
index 3f662af46b..a3c02fcdf0 100644
--- a/wagtail/admin/templates/wagtailadmin/tables/column_header.html
+++ b/wagtail/admin/templates/wagtailadmin/tables/column_header.html
@@ -2,15 +2,15 @@
 <th {% if column.classname %}class="{{ column.classname }}"{% endif %}>
     {% if is_orderable %}
         {% if is_ascending %}
-            <a href="{{ table.base_url }}{% querystring p=None ordering='-'|add:column.sort_key %}" class="icon icon-arrow-up-after teal">
+            <a href="{{ table.base_url }}{% querystring p=None ordering='-'|add:column.sort_key %}" {% if descending_title_text %}title="{{ descending_title_text }}"{% endif %} class="icon icon-arrow-up-after teal">
                 {{ column.label }}
             </a>
         {% elif is_descending %}
-            <a href="{{ table.base_url }}{% querystring p=None ordering=column.sort_key %}" class="icon icon-arrow-down-after teal">
+            <a href="{{ table.base_url }}{% querystring p=None ordering=column.sort_key %}" {% if ascending_title_text %}title="{{ ascending_title_text }}"{% endif %} class="icon icon-arrow-down-after teal">
                 {{ column.label }}
             </a>
         {% else %}
-            <a href="{{ table.base_url }}{% querystring p=None ordering=column.sort_key %}" class="icon icon-arrow-down-after">
+            <a href="{{ table.base_url }}{% querystring p=None ordering=column.sort_key %}" {% if ascending_title_text %}title="{{ ascending_title_text }}"{% endif %} class="icon icon-arrow-down-after">
                 {{ column.label }}
             </a>
         {% endif %}
diff --git a/wagtail/admin/ui/tables/__init__.py b/wagtail/admin/ui/tables/__init__.py
index fb9100c280..8a25b02923 100644
--- a/wagtail/admin/ui/tables/__init__.py
+++ b/wagtail/admin/ui/tables/__init__.py
@@ -9,7 +9,7 @@ from django.template.loader import get_template
 from django.urls import reverse
 from django.utils.functional import cached_property
 from django.utils.text import capfirst
-from django.utils.translation import gettext as _
+from django.utils.translation import gettext, gettext_lazy
 
 from wagtail.admin.ui.components import Component
 from wagtail.coreutils import multigetattr
@@ -41,7 +41,15 @@ class BaseColumn(metaclass=MediaDefiningClass):
     cell_template_name = None
 
     def __init__(
-        self, name, label=None, accessor=None, classname=None, sort_key=None, width=None
+        self,
+        name,
+        label=None,
+        accessor=None,
+        classname=None,
+        sort_key=None,
+        width=None,
+        ascending_title_text=None,
+        descending_title_text=None,
     ):
         self.name = name
         self.accessor = accessor or name
@@ -53,6 +61,8 @@ class BaseColumn(metaclass=MediaDefiningClass):
         self.sort_key = sort_key
         self.header = Column.Header(self)
         self.width = width
+        self.ascending_title_text = ascending_title_text
+        self.descending_title_text = descending_title_text
 
     def get_header_context_data(self, parent_context):
         """
@@ -66,6 +76,10 @@ class BaseColumn(metaclass=MediaDefiningClass):
             "is_ascending": self.sort_key and table.ordering == self.sort_key,
             "is_descending": self.sort_key and table.ordering == ("-" + self.sort_key),
             "request": parent_context.get("request"),
+            "ascending_title_text": self.ascending_title_text
+            or table.get_ascending_title_text(self),
+            "descending_title_text": self.descending_title_text
+            or table.get_descending_title_text(self),
         }
 
     @cached_property
@@ -235,7 +249,7 @@ class LiveStatusTagColumn(StatusTagColumn):
     def __init__(self, **kwargs):
         super().__init__(
             "status_string",
-            label=kwargs.pop("label", _("Status")),
+            label=kwargs.pop("label", gettext("Status")),
             sort_key=kwargs.pop("sort_key", "live"),
             primary=lambda instance: instance.live,
             **kwargs,
@@ -254,7 +268,7 @@ class UpdatedAtColumn(DateColumn):
     def __init__(self, **kwargs):
         super().__init__(
             "_updated_at",
-            label=kwargs.pop("label", _("Updated")),
+            label=kwargs.pop("label", gettext("Updated")),
             sort_key=kwargs.pop("sort_key", "_updated_at"),
             **kwargs,
         )
@@ -327,6 +341,12 @@ class Table(Component):
     template_name = "wagtailadmin/tables/table.html"
     classname = "listing"
     header_row_classname = ""
+    ascending_title_text_format = gettext_lazy(
+        "Sort by '%(label)s' in ascending order."
+    )
+    descending_title_text_format = gettext_lazy(
+        "Sort by '%(label)s' in descending order."
+    )
 
     def __init__(
         self,
@@ -381,6 +401,14 @@ class Table(Component):
     def has_column_widths(self):
         return any(column.width for column in self.columns.values())
 
+    def get_ascending_title_text(self, column):
+        if self.ascending_title_text_format:
+            return self.ascending_title_text_format % {"label": column.label}
+
+    def get_descending_title_text(self, column):
+        if self.descending_title_text_format:
+            return self.descending_title_text_format % {"label": column.label}
+
     class Row(Mapping):
         # behaves as an OrderedDict whose items are the rendered results of
         # the corresponding column's format_cell method applied to the instance
diff --git a/wagtail/snippets/tests/test_snippets.py b/wagtail/snippets/tests/test_snippets.py
index 100c5db472..07e570bb83 100644
--- a/wagtail/snippets/tests/test_snippets.py
+++ b/wagtail/snippets/tests/test_snippets.py
@@ -330,13 +330,13 @@ class TestListViewOrdering(WagtailTestUtils, TestCase):
         # The Updated column header should be a link with the correct query param
         self.assertContains(
             response,
-            f'<th><a href="{sort_updated_url}" class="icon icon-arrow-down-after">Updated</a></th>',
+            f'<th><a href="{sort_updated_url}" title="Sort by &#x27;Updated&#x27; in ascending order." class="icon icon-arrow-down-after">Updated</a></th>',
             html=True,
         )
         # Should not contain the Status column header
         self.assertNotContains(
             response,
-            f'<th><a href="{sort_live_url}" class="icon icon-arrow-down-after">Status</a></th>',
+            f'<th><a href="{sort_live_url}" title="Sort by &#x27;Status&#x27; in ascending order." class="icon icon-arrow-down-after">Status</a></th>',
             html=True,
         )
 
@@ -352,13 +352,13 @@ class TestListViewOrdering(WagtailTestUtils, TestCase):
         # The Updated column header should be a link with the correct query param
         self.assertContains(
             response,
-            f'<th><a href="{sort_updated_url}" class="icon icon-arrow-down-after">Updated</a></th>',
+            f'<th><a href="{sort_updated_url}" title="Sort by &#x27;Updated&#x27; in ascending order." class="icon icon-arrow-down-after">Updated</a></th>',
             html=True,
         )
         # The Status column header should be a link with the correct query param
         self.assertContains(
             response,
-            f'<th><a href="{sort_live_url}" class="icon icon-arrow-down-after">Status</a></th>',
+            f'<th><a href="{sort_live_url}" title="Sort by &#x27;Status&#x27; in ascending order." class="icon icon-arrow-down-after">Status</a></th>',
             html=True,
         )