diff --git a/core/modules/ckeditor5/js/ckeditor5.es6.js b/core/modules/ckeditor5/js/ckeditor5.es6.js
index 36788b71a296c4dd46c566733c0dd8add50fb0d7..a200557fe74d52bca9a682b5f28691247d6e3bb7 100644
--- a/core/modules/ckeditor5/js/ckeditor5.es6.js
+++ b/core/modules/ckeditor5/js/ckeditor5.es6.js
@@ -322,6 +322,16 @@
             element.removeAttribute('required');
           }
 
+          // Integrate CKEditor 5 viewport offset with Drupal displace.
+          // @see \Drupal\Tests\ckeditor5\FunctionalJavascript\CKEditor5ToolbarTest
+          // @see https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editorui-EditorUI.html#member-viewportOffset
+          $(document).on(
+            `drupalViewportOffsetChange.ckeditor5.${id}`,
+            (event, offsets) => {
+              editor.ui.viewportOffset = offsets;
+            },
+          );
+
           editor.model.document.on('change:data', () => {
             const callback = callbacks.get(id);
             if (callback) {
@@ -372,6 +382,9 @@
       if (!editor) {
         return;
       }
+
+      $(document).off(`drupalViewportOffsetChange.ckeditor5.${id}`);
+
       if (trigger === 'serialize') {
         editor.updateSourceElement();
       } else {
diff --git a/core/modules/ckeditor5/js/ckeditor5.js b/core/modules/ckeditor5/js/ckeditor5.js
index d154b257685ed69657392b2cdf8c8810911df382..59e014ae78b8ac367a285026470dce97c1a3e5b3 100644
--- a/core/modules/ckeditor5/js/ckeditor5.js
+++ b/core/modules/ckeditor5/js/ckeditor5.js
@@ -162,6 +162,9 @@
           element.removeAttribute('required');
         }
 
+        $(document).on(`drupalViewportOffsetChange.ckeditor5.${id}`, (event, offsets) => {
+          editor.ui.viewportOffset = offsets;
+        });
         editor.model.document.on('change:data', () => {
           const callback = callbacks.get(id);
 
@@ -194,6 +197,8 @@
         return;
       }
 
+      $(document).off(`drupalViewportOffsetChange.ckeditor5.${id}`);
+
       if (trigger === 'serialize') {
         editor.updateSourceElement();
       } else {
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5ToolbarTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5ToolbarTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa2dbd1471a27533355f1da23b23a08532371c0b
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5ToolbarTest.php
@@ -0,0 +1,101 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
+
+use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
+use Drupal\editor\Entity\Editor;
+use Drupal\filter\Entity\FilterFormat;
+use Symfony\Component\Validator\ConstraintViolation;
+
+/**
+ * Tests for CKEditor 5 editor UI with Toolbar module.
+ *
+ * @group ckeditor5
+ * @internal
+ */
+class CKEditor5ToolbarTest extends CKEditor5TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'toolbar',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    FilterFormat::create([
+      'format' => 'test_format',
+      'name' => 'Test format',
+      'filters' => [],
+    ])->save();
+    Editor::create([
+      'editor' => 'ckeditor5',
+      'format' => 'test_format',
+      'settings' => [],
+    ])->save();
+    $this->assertSame([], array_map(
+      function (ConstraintViolation $v) {
+        return (string) $v->getMessage();
+      },
+      iterator_to_array(CKEditor5::validatePair(
+        Editor::load('test_format'),
+        FilterFormat::load('test_format')
+      ))
+    ));
+
+    $this->drupalCreateContentType([
+      'type' => 'article',
+      'name' => 'Article',
+    ]);
+
+    $this->user = $this->drupalCreateUser([
+      'use text format test_format',
+      'access toolbar',
+      'edit any article content',
+      'administer site configuration',
+    ]);
+    $this->drupalLogin($this->user);
+  }
+
+  /**
+   * Ensures that CKEditor 5 toolbar renders below Drupal Toolbar.
+   */
+  public function test(): void {
+    $assert_session = $this->assertSession();
+
+    // Create test content to ensure that CKEditor 5 text editor can be
+    // scrolled.
+    $body = '';
+    for ($i = 0; $i < 10; $i++) {
+      $body .= '<p>' . $this->randomMachineName(32) . '</p>';
+    }
+    $edit_url = $this->drupalCreateNode(['type' => 'article', 'body' => ['value' => $body, 'format' => 'test_format']])->toUrl('edit-form');
+    $this->drupalGet($edit_url);
+    $this->assertNotEmpty($assert_session->waitForElement('css', '#toolbar-bar'));
+    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-editor'));
+
+    // Ensure the body has enough height to enable scrolling. Scroll 110px from
+    // top of body field to ensure CKEditor 5 toolbar is sticky.
+    $this->getSession()->evaluateScript('document.body.style.height = "10000px";');
+    $this->getSession()->evaluateScript('location.hash = "#edit-body-0-value";');
+    $this->getSession()->evaluateScript('scroll(0, document.documentElement.scrollTop + 110);');
+    // Focus CKEditor 5 text editor.
+    $javascript = <<<JS
+      Drupal.CKEditor5Instances.get(document.getElementById("edit-body-0-value").dataset["ckeditor5Id"]).editing.view.focus();
+JS;
+    $this->getSession()->evaluateScript($javascript);
+
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.ck-sticky-panel__placeholder'));
+    $toolbar_height = (int) $this->getSession()->evaluateScript('document.getElementById("toolbar-bar").offsetHeight');
+    $ckeditor5_toolbar_position = (int) $this->getSession()->evaluateScript("document.querySelector('.ck-toolbar').getBoundingClientRect().top");
+    $this->assertEqualsWithDelta($toolbar_height, $ckeditor5_toolbar_position, 2);
+  }
+
+}