diff --git a/client/src/includes/panels.ts b/client/src/includes/panels.ts
index b75286bb55..ea49b30137 100644
--- a/client/src/includes/panels.ts
+++ b/client/src/includes/panels.ts
@@ -1,3 +1,5 @@
+import { getElementByContentPath } from '../utils/contentPath';
+
 /**
  * Switches a collapsible panel from expanded to collapsed, or vice versa.
  * Updates the DOM and fires custom events for other code to hook into.
@@ -110,7 +112,10 @@ export function initCollapsiblePanels(
 export function initAnchoredPanels(
   anchorTarget = document.getElementById(window.location.hash.slice(1)),
 ) {
-  const target = anchorTarget?.matches('[data-panel]') ? anchorTarget : null;
+  const target = anchorTarget?.matches('[data-panel]')
+    ? anchorTarget
+    : getElementByContentPath();
+
   if (target) {
     setTimeout(() => {
       target.scrollIntoView({ behavior: 'smooth' });
diff --git a/client/src/includes/tabs.js b/client/src/includes/tabs.js
index 2631c75c9d..a47bfe3736 100644
--- a/client/src/includes/tabs.js
+++ b/client/src/includes/tabs.js
@@ -1,3 +1,4 @@
+import { getElementByContentPath } from '../utils/contentPath';
 /**
  *  All tabs and tab content must be nested in an element with the data-tab attribute
  *  All tab buttons need the role="tab" attr and an href with the tab content ID
@@ -271,9 +272,10 @@ class Tabs {
   selectTabByURLHash() {
     if (window.location.hash) {
       const anchorId = window.location.hash.slice(1);
+      const anchoredElement =
+        document.getElementById(anchorId) || getElementByContentPath();
       // Support linking straight to a tab, or to an element within a tab.
-      const tabID = document
-        .getElementById(anchorId)
+      const tabID = anchoredElement
         ?.closest('[role="tabpanel"]')
         ?.getAttribute('aria-labelledby');
       const tab = document.getElementById(tabID);
diff --git a/client/src/includes/tabs.test.js b/client/src/includes/tabs.test.js
index 1644af84a1..454d6c7d8d 100644
--- a/client/src/includes/tabs.test.js
+++ b/client/src/includes/tabs.test.js
@@ -118,6 +118,23 @@ describe('tabs', () => {
       expect(promoteTabLabel.getAttribute('aria-selected')).toEqual('true');
     });
 
+    it('should select the correct tab where the element pointed by the contentpath directive lives', async () => {
+      window.location.hash = '#:w:contentpath=search_description';
+      initTabs();
+      await Promise.resolve();
+
+      const contentTab = document.getElementById('tab-content');
+      const promoteTab = document.getElementById('tab-promote');
+      const contentTabLabel = document.getElementById('tab-label-content');
+      const promoteTabLabel = document.getElementById('tab-label-promote');
+
+      expect(contentTab.hasAttribute('hidden')).toBe(true);
+      expect(promoteTab.hasAttribute('hidden')).toBe(false);
+
+      expect(contentTabLabel.getAttribute('aria-selected')).toEqual('false');
+      expect(promoteTabLabel.getAttribute('aria-selected')).toEqual('true');
+    });
+
     it('should not throw an error if the URL hash begins with a number', async () => {
       window.location.hash = '#123abcd';
       initTabs();
diff --git a/client/src/utils/contentPath.test.js b/client/src/utils/contentPath.test.js
new file mode 100644
index 0000000000..3894f362e7
--- /dev/null
+++ b/client/src/utils/contentPath.test.js
@@ -0,0 +1,91 @@
+import {
+  getWagtailDirectives,
+  getContentPathSelector,
+  getElementByContentPath,
+} from './contentPath';
+
+describe('getWagtailDirectives', () => {
+  afterEach(() => {
+    window.location.hash = '';
+  });
+
+  it('should return the directive after the delimiter as-is', () => {
+    window.location.hash = '#:w:contentpath=abc1.d2e.3f';
+    expect(getWagtailDirectives()).toEqual('contentpath=abc1.d2e.3f');
+  });
+
+  it('should allow a normal anchor in front of the delimiter', () => {
+    window.location.hash = '#an-anchor:w:contentpath=abc1.d2e.3f';
+    expect(getWagtailDirectives()).toEqual('contentpath=abc1.d2e.3f');
+  });
+
+  it('should allow multiple values for the same directive', () => {
+    window.location.hash =
+      '#hello:w:contentpath=abc1.d2e.3f&unknown=123&unknown=456';
+    expect(getWagtailDirectives()).toEqual(
+      'contentpath=abc1.d2e.3f&unknown=123&unknown=456',
+    );
+  });
+});
+
+describe('getContentPathSelector', () => {
+  it('should return a selector string for a single content path', () => {
+    expect(getContentPathSelector('abc1')).toEqual('[data-contentpath="abc1"]');
+  });
+  it('should allow dotted content path', () => {
+    expect(getContentPathSelector('abc1.d2e.3f')).toEqual(
+      '[data-contentpath="abc1"] [data-contentpath="d2e"] [data-contentpath="3f"]',
+    );
+  });
+
+  it('should ignore leading, trailing, and extra dots', () => {
+    expect(getContentPathSelector('.abc1...d2e..3f.')).toEqual(
+      '[data-contentpath="abc1"] [data-contentpath="d2e"] [data-contentpath="3f"]',
+    );
+  });
+
+  it('should return an empty string if content path is an empty string', () => {
+    expect(getContentPathSelector('')).toEqual('');
+  });
+});
+
+describe('getElementByContentPath', () => {
+  beforeEach(() => {
+    document.body.innerHTML = /* html */ `
+      <div id="one" data-contentpath="abc1">
+        <div id="two" data-contentpath="d2e">
+          <div id="three" data-contentpath="3f"></div>
+        </div>
+        <div id="four" data-contentpath="g4h"></div>
+      </div>
+    `;
+  });
+
+  afterEach(() => {
+    window.location.hash = '';
+  });
+
+  it('should return the element for a single content path', () => {
+    const element = getElementByContentPath('abc1');
+    expect(element).toBeTruthy();
+    expect(element.id).toEqual('one');
+  });
+
+  it('should return the element for a dotted content path', () => {
+    const element = getElementByContentPath('abc1.d2e.3f');
+    expect(element).toBeTruthy();
+    expect(element.id).toEqual('three');
+  });
+
+  it('should read from the contentpath directive if there is one', () => {
+    window.location.hash = '#:w:contentpath=abc1.d2e.3f';
+    const element = getElementByContentPath();
+    expect(element).toBeTruthy();
+    expect(element.id).toEqual('three');
+  });
+
+  it('should return null if it cannot find the element', () => {
+    expect(getElementByContentPath('abc1.d2e.3f.g4h')).toBeNull();
+    expect(getElementByContentPath()).toBeNull();
+  });
+});
diff --git a/client/src/utils/contentPath.ts b/client/src/utils/contentPath.ts
new file mode 100644
index 0000000000..b32c0cbae6
--- /dev/null
+++ b/client/src/utils/contentPath.ts
@@ -0,0 +1,78 @@
+const WAGTAIL_DIRECTIVE_DELIMITER = ':w:';
+
+/**
+ * Extract the Wagtail directives from the URL fragment.
+ *
+ * This follows the algorithm described in
+ * https://wicg.github.io/scroll-to-text-fragment/#extracting-the-fragment-directive
+ * for extracting the fragment directive from the URL fragment, with a few
+ * differences:
+ * - We use a :w: delimiter instead of the proposed :~: delimiter.
+ * - We don't remove our directive from the URL fragment.
+ *
+ * @param rawFragment The raw fragment (hash) from the URL,
+ * @returns a string of Wagtail directives, if any, in the style of URL search parameters.
+ *
+ * @example window.location.hash = '#:w:contentpath=abc1.d2e.3f'
+ * // getWagtailDirectives() === 'contentpath=abc1.d2e.3f'
+ *
+ * @example window.location.hash = '#an-anchor:w:contentpath=abc1.d2e.3f'
+ * // getWagtailDirectives() === 'contentpath=abc1.d2e.3f'
+ *
+ * @example window.location.hash = '#hello:w:contentpath=abc1.d2e.3f&unknown=123&unknown=456'
+ * // getWagtailDirectives() === 'contentpath=abc1.d2e.3f&unknown=123&unknown=456'
+ */
+export function getWagtailDirectives() {
+  const rawFragment = window.location.hash;
+  const position = rawFragment.indexOf(WAGTAIL_DIRECTIVE_DELIMITER);
+  if (position === -1) return '';
+  return rawFragment.slice(position + WAGTAIL_DIRECTIVE_DELIMITER.length);
+}
+
+/**
+ * Compose a selector string to find the content element based on the dotted
+ * content path.
+ *
+ * @param contentPath dotted path to the content element.
+ * @returns a selector string to find the content element.
+ *
+ * @example getContentPathSelector('abc1.d2e.3f')
+ * // returns '[data-contentpath="abc1"] [data-contentpath="d2e"] [data-contentpath="3f"]'
+ */
+export function getContentPathSelector(contentPath: string) {
+  const pathSegments = contentPath.split('.');
+  const selector = pathSegments.reduce((acc, segment) => {
+    // In some cases the segment can be empty, e.g. when the path ends with
+    // a trailing dot, which may be the case with inline panels.
+    if (!segment) return acc;
+
+    const segmentSelector = `[data-contentpath="${segment}"]`;
+    return acc ? `${acc} ${segmentSelector}` : segmentSelector;
+  }, '');
+  return selector;
+}
+
+/**
+ * Get the content element based on a given content path (or one extracted from
+ * the URL hash fragment).
+ *
+ * @param contentPath (optional) content path to the content element. If not
+ * provided, it will be extracted from the URL fragment.
+ * @returns the content element, if found, otherwise `null`.
+ *
+ * @example getElementByContentPath('abc1.d2e.3f')
+ * // returns <div data-contentpath="3f">...</div>
+ *
+ * @example getElementByContentPath()
+ * // with an URL e.g. https://example.com/#:w:contentpath=abc1.d2e.3f
+ * // returns <div data-contentpath="3f">...</div>
+ */
+export function getElementByContentPath(contentPath?: string) {
+  const path =
+    contentPath ||
+    new URLSearchParams(getWagtailDirectives()).get('contentpath');
+
+  return path
+    ? document.querySelector<HTMLElement>(getContentPathSelector(path))
+    : null;
+}
diff --git a/wagtail/admin/templates/wagtailadmin/tables/references_cell.html b/wagtail/admin/templates/wagtailadmin/tables/references_cell.html
index 45d4fd7e7f..a53427ba07 100644
--- a/wagtail/admin/templates/wagtailadmin/tables/references_cell.html
+++ b/wagtail/admin/templates/wagtailadmin/tables/references_cell.html
@@ -3,7 +3,7 @@
         {% for reference in value %}
             <li>
                 {% if edit_url %}
-                    <a href="{{ edit_url }}#content-path-{{ reference.content_path }}">
+                    <a href="{{ edit_url }}#:w:contentpath={{ reference.content_path }}">
                 {% endif %}
                 {{ reference.describe_source_field }}{% if edit_url %}</a>{% endif %}{% if describe_on_delete %}: {{ reference.describe_on_delete }}{% endif %}
             </li>
diff --git a/wagtail/admin/tests/pages/test_page_usage.py b/wagtail/admin/tests/pages/test_page_usage.py
index ae7ffcaf84..a0241417a6 100644
--- a/wagtail/admin/tests/pages/test_page_usage.py
+++ b/wagtail/admin/tests/pages/test_page_usage.py
@@ -103,7 +103,9 @@ class TestPageUsage(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
 
         self.assertContains(response, "Contact us")
         self.assertContains(
-            response, reverse("wagtailadmin_pages:edit", args=(form_page.id,))
+            response,
+            reverse("wagtailadmin_pages:edit", args=(form_page.id,))
+            + "#:w:contentpath=thank_you_redirect_page",
         )
         self.assertContains(response, "Thank you redirect page")
         self.assertContains(response, "<td>Form page with redirect</td>", html=True)
diff --git a/wagtail/admin/tests/viewsets/test_model_viewset.py b/wagtail/admin/tests/viewsets/test_model_viewset.py
index e7e7651a5f..529dfc3e88 100644
--- a/wagtail/admin/tests/viewsets/test_model_viewset.py
+++ b/wagtail/admin/tests/viewsets/test_model_viewset.py
@@ -1241,6 +1241,11 @@ class TestUsageView(WagtailTestUtils, TestCase):
         link = tds[0].select_one("a")
         self.assertIsNotNone(link)
         self.assertEqual(link.attrs.get("href"), tbx_edit_url)
+        content_path_link = tds[-1].select_one("a")
+        self.assertEqual(
+            content_path_link.attrs.get("href"),
+            tbx_edit_url + "#:w:contentpath=cascading_toy",
+        )
 
         # Link to referrer's edit view with parameters for the specific field
         link = tds[2].select_one("a")
diff --git a/wagtail/snippets/tests/test_usage.py b/wagtail/snippets/tests/test_usage.py
index 4b390115f7..cc344d0b94 100644
--- a/wagtail/snippets/tests/test_usage.py
+++ b/wagtail/snippets/tests/test_usage.py
@@ -97,6 +97,11 @@ class TestSnippetUsageView(WagtailTestUtils, TestCase):
         self.assertContains(response, "<th>Field</th>", html=True)
         self.assertNotContains(response, "<th>If you confirm deletion</th>", html=True)
         self.assertContains(response, "Snippet content object")
+        self.assertContains(
+            response,
+            reverse("wagtailadmin_pages:edit", args=[gfk_page.id])
+            + "#:w:contentpath=snippet_content_object",
+        )
 
     def test_usage_without_edit_permission_on_snippet(self):
         # Create a user with basic admin backend access