diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 425de914c176d5739477dfc3180560ed383a03de..d73e1cc43b061a8511c04c69d6084c48e4c70238 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -63,6 +63,7 @@ export default () => {
     const isMarkdown = editBlobForm.data('is-markdown');
     const previewMarkdownPath = editBlobForm.data('previewMarkdownPath');
     const commitButton = $('.js-commit-button');
+    const commitButtonLoading = $('.js-commit-button-loading');
     const cancelLink = $('#cancel-changes');
 
     import('./edit_blob')
@@ -88,6 +89,8 @@ export default () => {
     });
 
     commitButton.on('click', () => {
+      commitButton.addClass('gl-display-none');
+      commitButtonLoading.removeClass('gl-display-none');
       window.onbeforeunload = null;
     });
 
diff --git a/app/components/pajamas/button_component.rb b/app/components/pajamas/button_component.rb
index 4233e446d5b79768e567ac6710d0524458bd6c62..b2dd798b718a39aaf45d7972140b2d3fe5710e0b 100644
--- a/app/components/pajamas/button_component.rb
+++ b/app/components/pajamas/button_component.rb
@@ -112,7 +112,7 @@ def link?
     def base_attributes
       attributes = {}
 
-      attributes['disabled'] = '' if @disabled || @loading
+      attributes['disabled'] = 'disabled' if @disabled || @loading
       attributes['aria-disabled'] = true if @disabled || @loading
       attributes['type'] = @type unless @href
 
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index 952c6daf4158dd99a5703a9e939620069895bb65..9962e03995bb6df2912988327118a02480e1e794 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -1,5 +1,8 @@
 .form-actions.gl-display-flex
-  = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { id: 'commit-changes', class: 'js-commit-button', data: { qa_selector: 'commit_button' } }) do
+  - submit_button_options = { type: :submit, variant: :confirm, button_options: { id: 'commit-changes', class: 'js-commit-button', data: { qa_selector: 'commit_button' } } }
+  = render Pajamas::ButtonComponent.new(**submit_button_options) do
+    = _('Commit changes')
+  = render Pajamas::ButtonComponent.new(loading: true, disabled: true, **submit_button_options.merge({ button_options: { class: 'js-commit-button-loading gl-display-none' } })) do
     = _('Commit changes')
 
   = render Pajamas::ButtonComponent.new(href: cancel_path, button_options: { class: 'gl-ml-3', id: 'cancel-changes', aria: { label: _('Discard changes') }, data: { confirm: leave_edit_message, confirm_btn_variant: "danger" } }) do
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index d7460538be9a2b71630f7d2262870cab3bce9477..ad96f6b62390da90c4d18c098405d552e098291b 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -102,6 +102,21 @@
       expect(page).to have_content('*.rbca')
     end
 
+    it 'shows loader on commit changes' do
+      set_default_button('edit')
+      click_link('.gitignore')
+      click_link_or_button('Edit')
+
+      # why: We don't want the form to actually submit, so that we can assert the button's changed state
+      page.execute_script("document.querySelector('.js-edit-blob-form').addEventListener('submit', e => e.preventDefault())")
+
+      find('.file-editor', match: :first)
+      editor_set_value('*.rbca')
+      click_button('Commit changes')
+
+      expect(page).to have_button('Commit changes', disabled: true, class: 'js-commit-button-loading')
+    end
+
     it 'shows the diff of an edited file' do
       set_default_button('edit')
       click_link('.gitignore')