diff --git a/app/assets/javascripts/rapid_diffs/adapters.js b/app/assets/javascripts/rapid_diffs/adapters.js
index fb9c404263e149b63b9e3a287b7aa9e5b84bea3f..d5c5691c0282c49898caae0e412c18a6d176ae42 100644
--- a/app/assets/javascripts/rapid_diffs/adapters.js
+++ b/app/assets/javascripts/rapid_diffs/adapters.js
@@ -1,4 +1,5 @@
 import { ExpandLinesAdapter } from '~/rapid_diffs/expand_lines/adapter';
+import { OptionsMenuAdapter } from '~/rapid_diffs/options_menu/adapter';
 import { ToggleFileAdapter } from '~/rapid_diffs/toggle_file/adapter';
 
 const RAPID_DIFFS_VIEWERS = {
@@ -6,7 +7,7 @@ const RAPID_DIFFS_VIEWERS = {
   text_parallel: 'text_parallel',
 };
 
-const COMMON_ADAPTERS = [ExpandLinesAdapter, ToggleFileAdapter];
+const COMMON_ADAPTERS = [ExpandLinesAdapter, OptionsMenuAdapter, ToggleFileAdapter];
 
 export const VIEWER_ADAPTERS = {
   [RAPID_DIFFS_VIEWERS.text_inline]: COMMON_ADAPTERS,
diff --git a/app/assets/javascripts/rapid_diffs/options_menu/adapter.js b/app/assets/javascripts/rapid_diffs/options_menu/adapter.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7cc7f9d2bc784139c9ec4edb6d8f47b69e38d6b
--- /dev/null
+++ b/app/assets/javascripts/rapid_diffs/options_menu/adapter.js
@@ -0,0 +1,27 @@
+import Vue from 'vue';
+import { GlDisclosureDropdown } from '@gitlab/ui';
+
+export const OptionsMenuAdapter = {
+  clicks: {
+    toggleOptionsMenu(event) {
+      const button = event.target.closest('.js-options-button');
+
+      if (!this.sink.optionsMenu) {
+        this.sink.optionsMenu = new Vue({
+          el: Vue.version.startsWith('2') ? button : button.parentElement,
+          name: 'GlDisclosureDropdown',
+          render: (createElement = Vue.h) =>
+            createElement(GlDisclosureDropdown, {
+              props: {
+                icon: 'ellipsis_v',
+                startOpened: true,
+                noCaret: true,
+                category: 'tertiary',
+                size: 'small',
+              },
+            }),
+        });
+      }
+    },
+  },
+};
diff --git a/app/components/rapid_diffs/diff_file_header_component.html.haml b/app/components/rapid_diffs/diff_file_header_component.html.haml
index 22e81aa36aa073cc8bfb58dc9f5830e3cb878a29..02b5ef07ab278448131ee29a8434a170f210d221 100644
--- a/app/components/rapid_diffs/diff_file_header_component.html.haml
+++ b/app/components/rapid_diffs/diff_file_header_component.html.haml
@@ -48,3 +48,6 @@
     %span.rd-lines-removed
       %span>= "−".html_safe
       %span{ "data-testid" => "js-file-deletion-line" }= @diff_file.removed_lines
+  .rd-diff-file-options-menu.gl-ml-2
+    .js-options-menu
+      = render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, icon: 'ellipsis_v', button_options: { class: 'js-options-button', data: { click: 'toggleOptionsMenu' }, aria: { label: _('Options') } })
diff --git a/spec/frontend/rapid_diffs/options_menu/adapter_spec.js b/spec/frontend/rapid_diffs/options_menu/adapter_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..27a9c790ce12fbf49d827644dd8b7bc9d30319b4
--- /dev/null
+++ b/spec/frontend/rapid_diffs/options_menu/adapter_spec.js
@@ -0,0 +1,64 @@
+import { DiffFile } from '~/rapid_diffs/diff_file';
+import { OptionsMenuAdapter } from '~/rapid_diffs/options_menu/adapter';
+
+describe('Diff File Options Menu', () => {
+  const html = `
+    <diff-file data-viewer="any">
+      <div class="rd-diff-file">
+        <div class="rd-diff-file-header" data-testid="rd-diff-file-header">
+        <div class="rd-diff-file-options-menu gl-ml-2">
+          <div class="js-options-menu">
+            <button class="js-options-button" data-click="toggleOptionsMenu" type="button"></button>
+          </div>
+        </div>
+      </div>
+      <div data-file-body=""><!-- body content --></div>
+      <diff-file-mounted></diff-file-mounted>
+    </diff-file>
+  `;
+
+  function get(element) {
+    const elements = {
+      file: () => document.querySelector('diff-file'),
+      container: () => get('file').querySelector('.js-options-menu'),
+      serverButton: () => get('container').querySelector('.js-options-button'),
+      vueButton: () => get('container').querySelector('[data-testid="base-dropdown-toggle"]'),
+    };
+
+    return elements[element]?.();
+  }
+
+  function assignAdapter(customAdapter) {
+    get('file').adapterConfig = { any: [customAdapter] };
+  }
+
+  beforeAll(() => {
+    customElements.define('diff-file', DiffFile);
+  });
+
+  beforeEach(() => {
+    document.body.innerHTML = html;
+    assignAdapter(OptionsMenuAdapter);
+    get('file').mount();
+  });
+
+  it('starts with the server-rendered button', () => {
+    expect(get('serverButton')).not.toBeNull();
+  });
+
+  it('replaces the server-rendered button with a Vue GlDisclosureDropdown when the button is clicked', () => {
+    const button = get('serverButton');
+
+    expect(get('vueButton')).toBeNull();
+    expect(button).not.toBeNull();
+
+    button.click();
+
+    expect(get('vueButton')).not.toBeNull();
+    /*
+     * This button being replaced also means this replacement can only
+     * happen once (desireable!), so testing that it's no longer present is good
+     */
+    expect(get('serverButton')).toBeNull();
+  });
+});