diff --git a/models/fixtures/webhook.yml b/models/fixtures/webhook.yml
index ebc4062b60..ec282914b8 100644
--- a/models/fixtures/webhook.yml
+++ b/models/fixtures/webhook.yml
@@ -1,7 +1,7 @@
 -
   id: 1
   repo_id: 1
-  url: www.example.com/url1
+  url: https://www.example.com/url1
   content_type: 1 # json
   events: '{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}'
   is_active: true
@@ -9,7 +9,7 @@
 -
   id: 2
   repo_id: 1
-  url: www.example.com/url2
+  url: https://www.example.com/url2
   content_type: 1 # json
   events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
   is_active: false
@@ -18,7 +18,7 @@
   id: 3
   owner_id: 3
   repo_id: 3
-  url: www.example.com/url3
+  url: https://www.example.com/url3
   content_type: 1 # json
   events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
   is_active: true
@@ -26,7 +26,7 @@
 -
   id: 4
   repo_id: 2
-  url: www.example.com/url4
+  url: https://www.example.com/url4
   content_type: 1 # json
   events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
   is_active: true
@@ -35,7 +35,7 @@
   id: 5
   repo_id: 0
   owner_id: 0
-  url: www.example.com/url5
+  url: https://www.example.com/url5
   content_type: 1 # json
   events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
   is_active: true
@@ -45,7 +45,7 @@
   id: 6
   repo_id: 0
   owner_id: 0
-  url: www.example.com/url6
+  url: https://www.example.com/url6
   content_type: 1 # json
   events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
   is_active: true
diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go
index 29f7735d09..e8a2547c65 100644
--- a/models/webhook/webhook_test.go
+++ b/models/webhook/webhook_test.go
@@ -90,7 +90,7 @@ func TestWebhook_EventsArray(t *testing.T) {
 func TestCreateWebhook(t *testing.T) {
 	hook := &Webhook{
 		RepoID:      3,
-		URL:         "www.example.com/unit_test",
+		URL:         "https://www.example.com/unit_test",
 		ContentType: ContentTypeJSON,
 		Events:      `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`,
 	}
diff --git a/modules/repository/create.go b/modules/repository/create.go
index a53632bb57..a75598a84b 100644
--- a/modules/repository/create.go
+++ b/modules/repository/create.go
@@ -8,17 +8,9 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
-	"strings"
 
-	activities_model "code.gitea.io/gitea/models/activities"
-	"code.gitea.io/gitea/models/db"
 	git_model "code.gitea.io/gitea/models/git"
-	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
-	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
-	"code.gitea.io/gitea/modules/log"
-	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
 )
 
 const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
@@ -63,97 +55,3 @@ func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
 
 	return repo_model.UpdateRepoSize(ctx, repo.ID, size, lfsSize)
 }
-
-// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...
-func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error {
-	if err := repo.LoadOwner(ctx); err != nil {
-		return err
-	}
-
-	// Create/Remove git-daemon-export-ok for git-daemon...
-	daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`)
-
-	isExist, err := util.IsExist(daemonExportFile)
-	if err != nil {
-		log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
-		return err
-	}
-
-	isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic
-	if !isPublic && isExist {
-		if err = util.Remove(daemonExportFile); err != nil {
-			log.Error("Failed to remove %s: %v", daemonExportFile, err)
-		}
-	} else if isPublic && !isExist {
-		if f, err := os.Create(daemonExportFile); err != nil {
-			log.Error("Failed to create %s: %v", daemonExportFile, err)
-		} else {
-			f.Close()
-		}
-	}
-
-	return nil
-}
-
-// UpdateRepository updates a repository with db context
-func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) {
-	repo.LowerName = strings.ToLower(repo.Name)
-
-	e := db.GetEngine(ctx)
-
-	if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
-		return fmt.Errorf("update: %w", err)
-	}
-
-	if err = UpdateRepoSize(ctx, repo); err != nil {
-		log.Error("Failed to update size for repository: %v", err)
-	}
-
-	if visibilityChanged {
-		if err = repo.LoadOwner(ctx); err != nil {
-			return fmt.Errorf("LoadOwner: %w", err)
-		}
-		if repo.Owner.IsOrganization() {
-			// Organization repository need to recalculate access table when visibility is changed.
-			if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
-				return fmt.Errorf("recalculateTeamAccesses: %w", err)
-			}
-		}
-
-		// If repo has become private, we need to set its actions to private.
-		if repo.IsPrivate {
-			_, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{
-				IsPrivate: true,
-			})
-			if err != nil {
-				return err
-			}
-
-			if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
-				return err
-			}
-		}
-
-		// Create/Remove git-daemon-export-ok for git-daemon...
-		if err := CheckDaemonExportOK(ctx, repo); err != nil {
-			return err
-		}
-
-		forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
-		if err != nil {
-			return fmt.Errorf("getRepositoriesByForkID: %w", err)
-		}
-		for i := range forkRepos {
-			forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate
-			if err = UpdateRepository(ctx, forkRepos[i], true); err != nil {
-				return fmt.Errorf("updateRepository[%d]: %w", forkRepos[i].ID, err)
-			}
-		}
-
-		// If visibility is changed, we need to update the issue indexer.
-		// Since the data in the issue indexer have field to indicate if the repo is public or not.
-		issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
-	}
-
-	return nil
-}
diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go
index e1f981ba3c..b85a10adad 100644
--- a/modules/repository/create_test.go
+++ b/modules/repository/create_test.go
@@ -6,7 +6,6 @@ package repository
 import (
 	"testing"
 
-	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
@@ -14,26 +13,6 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestUpdateRepositoryVisibilityChanged(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	// Get sample repo and change visibility
-	repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 9)
-	assert.NoError(t, err)
-	repo.IsPrivate = true
-
-	// Update it
-	err = UpdateRepository(db.DefaultContext, repo, true)
-	assert.NoError(t, err)
-
-	// Check visibility of action has become private
-	act := activities_model.Action{}
-	_, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act)
-
-	assert.NoError(t, err)
-	assert.True(t, act.IsPrivate)
-}
-
 func TestGetDirectorySize(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 1)
diff --git a/services/repository/adopt.go b/services/repository/adopt.go
index 6d5505c42c..7f1954145c 100644
--- a/services/repository/adopt.go
+++ b/services/repository/adopt.go
@@ -196,7 +196,7 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr
 			return fmt.Errorf("setDefaultBranch: %w", err)
 		}
 	}
-	if err = repo_module.UpdateRepository(ctx, repo, false); err != nil {
+	if err = updateRepository(ctx, repo, false); err != nil {
 		return fmt.Errorf("updateRepository: %w", err)
 	}
 
diff --git a/services/repository/create.go b/services/repository/create.go
index 5a9b2ecd2a..897f2a1d14 100644
--- a/services/repository/create.go
+++ b/services/repository/create.go
@@ -464,7 +464,7 @@ func cleanupRepository(doer *user_model.User, repoID int64) {
 }
 
 func updateGitRepoAfterCreate(ctx context.Context, repo *repo_model.Repository) error {
-	if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil {
+	if err := checkDaemonExportOK(ctx, repo); err != nil {
 		return fmt.Errorf("checkDaemonExportOK: %w", err)
 	}
 
diff --git a/services/repository/fork.go b/services/repository/fork.go
index 1794cc18ab..c16c3d598a 100644
--- a/services/repository/fork.go
+++ b/services/repository/fork.go
@@ -227,7 +227,7 @@ func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Reposit
 		repo.IsFork = false
 		repo.ForkID = 0
 
-		if err := repo_module.UpdateRepository(ctx, repo, false); err != nil {
+		if err := updateRepository(ctx, repo, false); err != nil {
 			log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err)
 			return err
 		}
diff --git a/services/repository/repository.go b/services/repository/repository.go
index cd56010546..90aad4f9d8 100644
--- a/services/repository/repository.go
+++ b/services/repository/repository.go
@@ -7,20 +7,27 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
 
+	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/git"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/graceful"
+	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/queue"
 	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/util"
 	notify_service "code.gitea.io/gitea/services/notify"
 	pull_service "code.gitea.io/gitea/services/pull"
 )
@@ -109,42 +116,32 @@ func Init(ctx context.Context) error {
 
 // UpdateRepository updates a repository
 func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) {
-	ctx, committer, err := db.TxContext(ctx)
-	if err != nil {
-		return err
-	}
-	defer committer.Close()
-
-	if err = repo_module.UpdateRepository(ctx, repo, visibilityChanged); err != nil {
-		return fmt.Errorf("updateRepository: %w", err)
-	}
-
-	return committer.Commit()
-}
-
-func UpdateRepositoryVisibility(ctx context.Context, repo *repo_model.Repository, isPrivate bool) (err error) {
-	ctx, committer, err := db.TxContext(ctx)
-	if err != nil {
-		return err
-	}
-
-	defer committer.Close()
-
-	repo.IsPrivate = isPrivate
-
-	if err = repo_module.UpdateRepository(ctx, repo, true); err != nil {
-		return fmt.Errorf("UpdateRepositoryVisibility: %w", err)
-	}
-
-	return committer.Commit()
+	return db.WithTx(ctx, func(ctx context.Context) error {
+		if err = updateRepository(ctx, repo, visibilityChanged); err != nil {
+			return fmt.Errorf("updateRepository: %w", err)
+		}
+		return nil
+	})
 }
 
 func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error) {
-	return UpdateRepositoryVisibility(ctx, repo, false)
+	return db.WithTx(ctx, func(ctx context.Context) error {
+		repo.IsPrivate = false
+		if err = updateRepository(ctx, repo, true); err != nil {
+			return fmt.Errorf("MakeRepoPublic: %w", err)
+		}
+		return nil
+	})
 }
 
 func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) {
-	return UpdateRepositoryVisibility(ctx, repo, true)
+	return db.WithTx(ctx, func(ctx context.Context) error {
+		repo.IsPrivate = true
+		if err = updateRepository(ctx, repo, true); err != nil {
+			return fmt.Errorf("MakeRepoPrivate: %w", err)
+		}
+		return nil
+	})
 }
 
 // LinkedRepository returns the linked repo if any
@@ -170,3 +167,97 @@ func LinkedRepository(ctx context.Context, a *repo_model.Attachment) (*repo_mode
 	}
 	return nil, -1, nil
 }
+
+// checkDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...
+func checkDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error {
+	if err := repo.LoadOwner(ctx); err != nil {
+		return err
+	}
+
+	// Create/Remove git-daemon-export-ok for git-daemon...
+	daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`)
+
+	isExist, err := util.IsExist(daemonExportFile)
+	if err != nil {
+		log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err)
+		return err
+	}
+
+	isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic
+	if !isPublic && isExist {
+		if err = util.Remove(daemonExportFile); err != nil {
+			log.Error("Failed to remove %s: %v", daemonExportFile, err)
+		}
+	} else if isPublic && !isExist {
+		if f, err := os.Create(daemonExportFile); err != nil {
+			log.Error("Failed to create %s: %v", daemonExportFile, err)
+		} else {
+			f.Close()
+		}
+	}
+
+	return nil
+}
+
+// updateRepository updates a repository with db context
+func updateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) {
+	repo.LowerName = strings.ToLower(repo.Name)
+
+	e := db.GetEngine(ctx)
+
+	if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil {
+		return fmt.Errorf("update: %w", err)
+	}
+
+	if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
+		log.Error("Failed to update size for repository: %v", err)
+	}
+
+	if visibilityChanged {
+		if err = repo.LoadOwner(ctx); err != nil {
+			return fmt.Errorf("LoadOwner: %w", err)
+		}
+		if repo.Owner.IsOrganization() {
+			// Organization repository need to recalculate access table when visibility is changed.
+			if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
+				return fmt.Errorf("recalculateTeamAccesses: %w", err)
+			}
+		}
+
+		// If repo has become private, we need to set its actions to private.
+		if repo.IsPrivate {
+			_, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{
+				IsPrivate: true,
+			})
+			if err != nil {
+				return err
+			}
+
+			if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil {
+				return err
+			}
+		}
+
+		// Create/Remove git-daemon-export-ok for git-daemon...
+		if err := checkDaemonExportOK(ctx, repo); err != nil {
+			return err
+		}
+
+		forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID)
+		if err != nil {
+			return fmt.Errorf("getRepositoriesByForkID: %w", err)
+		}
+		for i := range forkRepos {
+			forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == structs.VisibleTypePrivate
+			if err = updateRepository(ctx, forkRepos[i], true); err != nil {
+				return fmt.Errorf("updateRepository[%d]: %w", forkRepos[i].ID, err)
+			}
+		}
+
+		// If visibility is changed, we need to update the issue indexer.
+		// Since the data in the issue indexer have field to indicate if the repo is public or not.
+		issue_indexer.UpdateRepoIndexer(ctx, repo.ID)
+	}
+
+	return nil
+}
diff --git a/services/repository/repository_test.go b/services/repository/repository_test.go
index 892a11a23e..8f9fdf8fa1 100644
--- a/services/repository/repository_test.go
+++ b/services/repository/repository_test.go
@@ -6,6 +6,7 @@ package repository
 import (
 	"testing"
 
+	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
@@ -40,3 +41,23 @@ func TestLinkedRepository(t *testing.T) {
 		})
 	}
 }
+
+func TestUpdateRepositoryVisibilityChanged(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	// Get sample repo and change visibility
+	repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 9)
+	assert.NoError(t, err)
+	repo.IsPrivate = true
+
+	// Update it
+	err = updateRepository(db.DefaultContext, repo, true)
+	assert.NoError(t, err)
+
+	// Check visibility of action has become private
+	act := activities_model.Action{}
+	_, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act)
+
+	assert.NoError(t, err)
+	assert.True(t, act.IsPrivate)
+}
diff --git a/web_src/css/base.css b/web_src/css/base.css
index 37ee7f5832..353ae851ad 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -747,6 +747,14 @@ overflow-menu .overflow-menu-button {
   padding: 0;
 }
 
+/* match the styles of ".ui.secondary.pointing.menu .active.item" */
+overflow-menu.ui.secondary.pointing.menu .overflow-menu-button.active {
+  padding: 2px 0 0;
+  border-bottom: 2px solid currentcolor;
+  background-color: transparent;
+  font-weight: var(--font-weight-medium);
+}
+
 overflow-menu .overflow-menu-button:hover {
   color: var(--color-text-dark);
 }
diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts
index b3debfde9e..98e5170a2b 100644
--- a/web_src/js/utils/dom.ts
+++ b/web_src/js/utils/dom.ts
@@ -360,7 +360,7 @@ export function querySingleVisibleElem<T extends HTMLElement>(parent: Element, s
 export function addDelegatedEventListener<T extends HTMLElement, E extends Event>(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => Promisable<void>, options?: boolean | AddEventListenerOptions) {
   parent.addEventListener(type, (e: Event) => {
     const elem = (e.target as HTMLElement).closest(selector);
-    if (!elem) return;
+    if (!elem || !parent.contains(elem)) return;
     listener(elem as T, e as E);
   }, options);
 }
diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts
index 4e729a268a..ae93f2b758 100644
--- a/web_src/js/webcomponents/overflow-menu.ts
+++ b/web_src/js/webcomponents/overflow-menu.ts
@@ -1,6 +1,6 @@
 import {throttle} from 'throttle-debounce';
 import {createTippy} from '../modules/tippy.ts';
-import {isDocumentFragmentOrElementNode} from '../utils/dom.ts';
+import {addDelegatedEventListener, isDocumentFragmentOrElementNode} from '../utils/dom.ts';
 import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg';
 
 window.customElements.define('overflow-menu', class extends HTMLElement {
@@ -12,10 +12,14 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
   mutationObserver: MutationObserver;
   lastWidth: number;
 
+  updateButtonActivationState() {
+    if (!this.button || !this.tippyContent) return;
+    this.button.classList.toggle('active', Boolean(this.tippyContent.querySelector('.item.active')));
+  }
+
   updateItems = throttle(100, () => {
     if (!this.tippyContent) {
       const div = document.createElement('div');
-      div.classList.add('tippy-target');
       div.tabIndex = -1; // for initial focus, programmatic focus only
       div.addEventListener('keydown', (e) => {
         if (e.key === 'Tab') {
@@ -64,9 +68,10 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
           }
         }
       });
-      this.append(div);
+      div.classList.add('tippy-target');
+      this.handleItemClick(div, '.tippy-target > .item');
       this.tippyContent = div;
-    }
+    } // end if: no tippyContent and create a new one
 
     const itemFlexSpace = this.menuItemsEl.querySelector<HTMLSpanElement>('.item-flex-space');
     const itemOverFlowMenuButton = this.querySelector<HTMLButtonElement>('.overflow-menu-button');
@@ -88,7 +93,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
     const menuRight = this.offsetLeft + this.offsetWidth;
     const menuItems = this.menuItemsEl.querySelectorAll<HTMLElement>('.item, .item-flex-space');
     let afterFlexSpace = false;
-    for (const item of menuItems) {
+    for (const [idx, item] of menuItems.entries()) {
       if (item.classList.contains('item-flex-space')) {
         afterFlexSpace = true;
         continue;
@@ -96,7 +101,10 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
       if (afterFlexSpace) item.setAttribute('data-after-flex-space', 'true');
       const itemRight = item.offsetLeft + item.offsetWidth;
       if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button with some extra space
-        this.tippyItems.push(item);
+        const onlyLastItem = idx === menuItems.length - 1 && this.tippyItems.length === 0;
+        const lastItemFit = onlyLastItem && menuRight - itemRight > 0;
+        const moveToPopup = !onlyLastItem || !lastItemFit;
+        if (moveToPopup) this.tippyItems.push(item);
       }
     }
     itemFlexSpace?.style.removeProperty('display');
@@ -107,6 +115,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
       const btn = this.querySelector('.overflow-menu-button');
       btn?._tippy?.destroy();
       btn?.remove();
+      this.button = null;
       return;
     }
 
@@ -126,18 +135,17 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
     // update existing tippy
     if (this.button?._tippy) {
       this.button._tippy.setContent(this.tippyContent);
+      this.updateButtonActivationState();
       return;
     }
 
     // create button initially
-    const btn = document.createElement('button');
-    btn.classList.add('overflow-menu-button');
-    btn.setAttribute('aria-label', window.config.i18n.more_items);
-    btn.innerHTML = octiconKebabHorizontal;
-    this.append(btn);
-    this.button = btn;
-
-    createTippy(btn, {
+    this.button = document.createElement('button');
+    this.button.classList.add('overflow-menu-button');
+    this.button.setAttribute('aria-label', window.config.i18n.more_items);
+    this.button.innerHTML = octiconKebabHorizontal;
+    this.append(this.button);
+    createTippy(this.button, {
       trigger: 'click',
       hideOnClick: true,
       interactive: true,
@@ -151,6 +159,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
         }, 0);
       },
     });
+    this.updateButtonActivationState();
   });
 
   init() {
@@ -187,6 +196,14 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
       }
     });
     this.resizeObserver.observe(this);
+    this.handleItemClick(this, '.overflow-menu-items > .item');
+  }
+
+  handleItemClick(el: Element, selector: string) {
+    addDelegatedEventListener(el, 'click', selector, () => {
+      this.button?._tippy?.hide();
+      this.updateButtonActivationState();
+    });
   }
 
   connectedCallback() {