diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index b98e20bad6e9b0a7d868daa90c11045b2f40e735..b5b23386e71458f0836632fb78625d6fdbf09727 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -776,6 +776,10 @@ function focusAfter() {
 	focusNext(el.value);
 }
 
+function scrollIntoView() {
+	el.value.scrollIntoView();
+}
+
 function readPromo() {
 	os.api('promo/read', {
 		noteId: appearNote.value.id,
@@ -790,6 +794,12 @@ function emitUpdReaction(emoji: string, delta: number) {
 		emit('reaction', emoji);
 	}
 }
+
+defineExpose({
+	focus,
+	blur,
+	scrollIntoView,
+});
 </script>
 
 <style lang="scss" module>
@@ -824,7 +834,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 			margin: auto;
 			width: calc(100% - 8px);
 			height: calc(100% - 8px);
-			border: dashed 1px var(--focus);
+			border: solid 1px var(--focus);
 			border-radius: var(--radius);
 			box-sizing: border-box;
 		}
@@ -894,7 +904,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	position: relative;
 	display: flex;
 	align-items: center;
-	padding: 24px 32px 16px calc(32px + var(--avatar) + 14px);
+	padding: 24px 32px 0 calc(32px + var(--avatar) + 14px);
 	line-height: 28px;
 	white-space: pre;
 	color: var(--renote);
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index ca3e8ca3f776df44b4997e36dc9a27130d93d3bd..4a06e8f56ab2ad00c67ca31a29dc2ed8e0eeef9f 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<SkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note" :expandAllCws="props.expandAllCws"/>
 	</template>
 	<SkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo" :expandAllCws="props.expandAllCws"/>
-	<article :class="$style.note" @contextmenu.stop="onContextmenu">
+	<article :id="appearNote.id" ref="noteEl" :class="$style.note" tabindex="-1" @contextmenu.stop="onContextmenu">
 		<header :class="$style.noteHeader">
 			<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
 			<div style="display: flex; align-items: center; white-space: nowrap; overflow: hidden;">
@@ -228,7 +228,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue';
+import { computed, inject, onMounted, onUnmounted, onUpdated, provide, ref, shallowRef, watch } from 'vue';
 import * as mfm from '@sharkey/sfm-js';
 import * as Misskey from 'misskey-js';
 import SkNoteSub from '@/components/SkNoteSub.vue';
@@ -301,6 +301,7 @@ const isRenote = (
 );
 
 const el = shallowRef<HTMLElement>();
+const noteEl = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
 const menuVersionsButton = shallowRef<HTMLElement>();
 const renoteButton = shallowRef<HTMLElement>();
@@ -731,11 +732,11 @@ function showRenoteMenu(viaKeyboard = false): void {
 }
 
 function focus() {
-	el.value.focus();
+	noteEl.value?.focus();
 }
 
 function blur() {
-	el.value.blur();
+	noteEl.value?.blur();
 }
 
 const repliesLoaded = ref(false);
@@ -776,6 +777,7 @@ function loadConversation() {
 		noteId: appearNote.value.replyId,
 	}).then(res => {
 		conversation.value = res.reverse();
+		focus();
 	});
 }
 
@@ -792,6 +794,31 @@ function animatedMFM() {
 		}).then((res) => { if (!res.canceled) allowAnim.value = true; });
 	}
 }
+
+let isScrolling = false;
+
+function setScrolling() {
+	isScrolling = true;
+}
+
+onMounted(() => {
+	document.addEventListener('wheel', setScrolling);
+	isScrolling = false;
+	noteEl.value?.scrollIntoView({ block: 'center' });
+});
+
+onUpdated(() => {
+	if (!isScrolling) {
+		noteEl.value?.scrollIntoView({ block: 'center' });
+		if (location.hash) {
+			location.replace(location.hash); // Jump to highlighted reply
+		}
+	}
+});
+
+onUnmounted(() => {
+	document.removeEventListener('wheel', setScrolling);
+});
 </script>
 
 <style lang="scss" module>
@@ -863,6 +890,7 @@ function animatedMFM() {
 }
 
 .note {
+	position: relative;
 	padding: 32px;
 	font-size: 1.2em;
 	overflow: hidden;
@@ -870,6 +898,28 @@ function animatedMFM() {
 	&:hover > .main > .footer > .button {
 		opacity: 1;
 	}
+
+	&:focus-visible {
+		outline: none;
+
+		&:after {
+			content: "";
+			pointer-events: none;
+			display: block;
+			position: absolute;
+			z-index: 10;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			margin: auto;
+			width: calc(100% - 8px);
+			height: calc(100% - 8px);
+			border: solid 1px var(--focus);
+			border-radius: var(--radius);
+			box-sizing: border-box;
+		}
+	}
 }
 
 .noteHeader {
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index 79e171f34c47d3eb94a9ef507aa0af12cca340b1..8b3ced37618a3e1ae0af0f34ef3be1a9b71e0e07 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -461,7 +461,27 @@ if (props.detail) {
 }
 
 .main {
-	display: flex;
+	position: relative;
+	display:  flex;
+
+	&::after {
+		content: "";
+		position: absolute;
+		top: -12px;
+		right: -12px;
+		left: -12px;
+		bottom: -12px;
+		background: var(--panelHighlight);
+		border-radius: var(--radius);
+		opacity: 0;
+		transition: opacity .2s, background .2s;
+		z-index: -1;
+	}
+	
+	&:hover::after,
+	&:focus-within::after {
+		opacity: 1;
+	}
 }
 
 .colorBar {
diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts
index 9755bdcb18fefb559c672be258e6a4aaa2874ca4..9564a754f03ef89552c79ea9c9a569447ff3a1ca 100644
--- a/packages/frontend/src/nirax.ts
+++ b/packages/frontend/src/nirax.ts
@@ -78,7 +78,7 @@ export class Router extends EventEmitter<{
 	public current: Resolved;
 	public currentRef: ShallowRef<Resolved> = shallowRef();
 	public currentRoute: ShallowRef<RouteDef> = shallowRef();
-	private currentPath: string;
+	private currentPath = '';
 	private isLoggedIn: boolean;
 	private notFoundPageComponent: Component;
 	private currentKey = Date.now().toString();
@@ -89,7 +89,7 @@ export class Router extends EventEmitter<{
 		super();
 
 		this.routes = routes;
-		this.currentPath = currentPath;
+		//this.currentPath = currentPath;
 		this.isLoggedIn = isLoggedIn;
 		this.notFoundPageComponent = notFoundPageComponent;
 		this.navigate(currentPath, null, false);
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index b861afa9a37fbba13513fe18e31028bc61e2dd2e..f12f7bde79241dcbfe2bbfc165f3b3a14ab6188a 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -545,12 +545,48 @@ export const mainRouter = new Router(routes, location.pathname + location.search
 
 window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);
 
+const scrollPosStore = new Map<string, number>();
+let restoring = false;
+
+window.setInterval(() => {
+	if (!restoring) {
+		scrollPosStore.set(window.history.state?.key, window.scrollY);
+	}
+}, 1000);
+
 mainRouter.addListener('push', ctx => {
 	window.history.pushState({ key: ctx.key }, '', ctx.path);
+
+	restoring = true;
+	const scrollPos = scrollPosStore.get(ctx.key) ?? 0;
+	window.scroll({ top: scrollPos, behavior: 'instant' });
+
+	if (scrollPos !== 0) {
+		window.setTimeout(() => {
+			// 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
+			window.scroll({ top: scrollPos, behavior: 'instant' });
+		}, 100);
+		restoring = false;
+	} else {
+		restoring = false;
+	}
+});
+
+mainRouter.addListener('same', () => {
+	window.scroll({ top: 0, behavior: 'smooth' });
 });
 
 window.addEventListener('popstate', (event) => {
 	mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
+
+	restoring = true;
+	const scrollPos = scrollPosStore.get(event.state?.key) ?? 0;
+	window.scroll({ top: scrollPos, behavior: 'instant' });
+	window.setTimeout(() => {
+		// 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
+		window.scroll({ top: scrollPos, behavior: 'instant' });
+		restoring = false;
+	}, 100);
 });
 
 export function useRouter(): Router {