diff --git a/package-lock.json b/package-lock.json
index b299967eba..d73fc5df5a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,6 +26,7 @@
         "esbuild-loader": "4.0.2",
         "escape-goat": "4.0.0",
         "fast-glob": "3.3.2",
+        "htmx.org": "1.9.10",
         "jquery": "3.7.1",
         "katex": "0.16.9",
         "license-checker-webpack-plugin": "0.2.1",
@@ -6158,6 +6159,11 @@
         "url": "https://github.com/fb55/entities?sponsor=1"
       }
     },
+    "node_modules/htmx.org": {
+      "version": "1.9.10",
+      "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.10.tgz",
+      "integrity": "sha512-UgchasltTCrTuU2DQLom3ohHrBvwr7OqpwyAVJ9VxtNBng4XKkVsqrv0Qr3srqvM9ZNI3f1MmvVQQqK7KW/bTA=="
+    },
     "node_modules/http-proxy-agent": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
diff --git a/package.json b/package.json
index 801e85db83..5c6de1dd62 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
     "esbuild-loader": "4.0.2",
     "escape-goat": "4.0.0",
     "fast-glob": "3.3.2",
+    "htmx.org": "1.9.10",
     "jquery": "3.7.1",
     "katex": "0.16.9",
     "license-checker-webpack-plugin": "0.2.1",
diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index 1cb5cc7162..1f51ceba5e 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -8,10 +8,15 @@ import (
 	"strconv"
 
 	issues_model "code.gitea.io/gitea/models/issues"
+	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
 )
 
+const (
+	tplWatching base.TplName = "repo/issue/view_content/watching"
+)
+
 // IssueWatch sets issue watching
 func IssueWatch(ctx *context.Context) {
 	issue := GetActionIssue(ctx)
@@ -52,5 +57,7 @@ func IssueWatch(ctx *context.Context) {
 		return
 	}
 
-	ctx.Redirect(issue.Link())
+	ctx.Data["Issue"] = issue
+	ctx.Data["IssueWatch"] = &issues_model.IssueWatch{IsWatching: watch}
+	ctx.HTML(http.StatusOK, tplWatching)
 }
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl
index 4334e4bcbd..6c13eef023 100644
--- a/templates/repo/issue/view_content/sidebar.tmpl
+++ b/templates/repo/issue/view_content/sidebar.tmpl
@@ -270,19 +270,7 @@
 		<div class="ui watching">
 			<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
 			<div class="gt-mt-3">
-				<form method="post" action="{{.Issue.Link}}/watch">
-					<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}">
-					{{$.CsrfTokenHtml}}
-					<button class="fluid ui button">
-						{{if $.IssueWatch.IsWatching}}
-							{{svg "octicon-mute" 16 "gt-mr-3"}}
-							{{ctx.Locale.Tr "repo.issues.unsubscribe"}}
-						{{else}}
-							{{svg "octicon-unmute" 16 "gt-mr-3"}}
-							{{ctx.Locale.Tr "repo.issues.subscribe"}}
-						{{end}}
-					</button>
-				</form>
+				{{template "repo/issue/view_content/watching" .}}
 			</div>
 		</div>
 	{{end}}
diff --git a/templates/repo/issue/view_content/watching.tmpl b/templates/repo/issue/view_content/watching.tmpl
new file mode 100644
index 0000000000..06b9d9af33
--- /dev/null
+++ b/templates/repo/issue/view_content/watching.tmpl
@@ -0,0 +1,13 @@
+<form hx-boost="true" hx-sync="this:replace" hx-target="this" hx-push-url="false" hx-swap="show:no-scroll" method="post" action="{{.Issue.Link}}/watch">
+	<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}">
+	{{$.CsrfTokenHtml}}
+	<button class="fluid ui button">
+		{{if $.IssueWatch.IsWatching}}
+			{{svg "octicon-mute" 16 "gt-mr-3"}}
+			{{ctx.Locale.Tr "repo.issues.unsubscribe"}}
+		{{else}}
+			{{svg "octicon-unmute" 16 "gt-mr-3"}}
+			{{ctx.Locale.Tr "repo.issues.subscribe"}}
+		{{end}}
+	</button>
+</form>
diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js
index 0b00eb8e8e..ffa0434cff 100644
--- a/web_src/js/features/common-global.js
+++ b/web_src/js/features/common-global.js
@@ -12,6 +12,7 @@ import {showTemporaryTooltip} from '../modules/tippy.js';
 import {confirmModal} from './comp/ConfirmModal.js';
 import {showErrorToast} from '../modules/toast.js';
 import {request, POST} from '../modules/fetch.js';
+import 'htmx.org';
 
 const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
 
diff --git a/webpack.config.js b/webpack.config.js
index 448dc64003..0d8418938f 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -214,6 +214,7 @@ export default {
       },
       override: {
         'khroma@*': {licenseName: 'MIT'}, // https://github.com/fabiospampinato/khroma/pull/33
+        'htmx.org@1.9.10': {licenseName: 'BSD-2-Clause'}, // "BSD 2-Clause" -> "BSD-2-Clause"
       },
       emitError: true,
       allow: '(Apache-2.0 OR BSD-2-Clause OR BSD-3-Clause OR MIT OR ISC OR CPAL-1.0 OR Unlicense OR EPL-1.0 OR EPL-2.0)',