diff --git a/src/client/app.vue b/src/client/app.vue
index 673949ce12..34c7543ace 100644
--- a/src/client/app.vue
+++ b/src/client/app.vue
@@ -87,8 +87,9 @@
 		</main>
 
 		<template v-if="isDesktop">
-			<div class="widgets" :class="{ edit: widgetsEditMode, fixed: $store.state.device.fixedWidgetsPosition }" v-for="place in ['left', 'right']" :key="place">
-				<template v-if="widgetsEditMode">
+			<div v-for="place in ['left', 'right']" ref="widgets" class="widgets" :class="{ edit: widgetsEditMode, fixed: $store.state.device.fixedWidgetsPosition, empty: widgets[place].length === 0 && !widgetsEditMode }" :key="place">
+				<div class="spacer"></div>
+				<div class="container" v-if="widgetsEditMode">
 					<mk-button primary @click="addWidget(place)" class="add"><fa :icon="faPlus"/></mk-button>
 					<x-draggable
 						:list="widgets[place]"
@@ -106,8 +107,10 @@
 							</div>
 						</div>
 					</x-draggable>
-				</template>
-				<component v-else class="_widget" v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget"/>
+				</div>
+				<div class="container" v-else>
+					<component class="_widget" v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget"/>
+				</div>
 			</div>
 		</template>
 	</div>
@@ -134,6 +137,7 @@ import { ResizeObserver } from '@juggle/resize-observer';
 import { v4 as uuid } from 'uuid';
 import { host, instanceName } from './config';
 import { search } from './scripts/search';
+import { StickySidebar } from './scripts/sticky-sidebar';
 
 const DESKTOP_THRESHOLD = 1100;
 
@@ -232,6 +236,12 @@ export default Vue.extend({
 			this.showNav = false;
 			this.canBack = (window.history.length > 0 && !['index'].includes(to.name));
 		},
+
+		isDesktop() {
+			this.$nextTick(() => {
+				this.attachSticky();
+			});
+		}
 	},
 
 	created() {
@@ -277,9 +287,24 @@ export default Vue.extend({
 				if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
 			}, { passive: true });
 		}
+
+		// widget follow
+		this.attachSticky();
 	},
 
 	methods: {
+		attachSticky() {
+			if (!this.isDesktop) return;
+			if (this.$store.state.device.fixedWidgetsPosition) return;
+
+			const stickyWidgetColumns = this.$refs.widgets.map(w => new StickySidebar(w.children[1], w.children[0], w.offsetTop));
+			window.addEventListener('scroll', () => {
+				for (const stickyWidgetColumn of stickyWidgetColumns) {
+					stickyWidgetColumn.calc(window.scrollY);
+				}
+			}, { passive: true });
+		},
+
 		top() {
 			window.scroll({ top: 0, behavior: 'smooth' });
 		},
@@ -988,15 +1013,14 @@ export default Vue.extend({
 		}
 
 		> .widgets {
-			top: $header-height;
-			min-height: calc(100vh - #{$header-height});
 			padding: 0 var(--margin);
 			box-shadow: 1px 0 0 0 var(--divider), -1px 0 0 0 var(--divider);
 
 			&.fixed {
 				position: sticky;
-				height: calc(100vh - #{$header-height});
 				overflow: auto;
+				height: calc(100vh - #{$header-height});
+				top: $header-height;
 			}
 
 			&:first-of-type {
@@ -1007,7 +1031,7 @@ export default Vue.extend({
 				}
 			}
 
-			&:empty {
+			&.empty {
 				display: none;
 			}
 
@@ -1015,9 +1039,16 @@ export default Vue.extend({
 				display: none;
 			}
 
-			> * {
-				margin: var(--margin) 0;
-				width: 300px;
+			> .container {
+				position: sticky;
+				height: min-content;
+				min-height: calc(100vh - #{$header-height});
+				overflow: hidden;
+
+				> * {
+					margin: var(--margin) 0;
+					width: 300px;
+				}
 			}
 
 			> .add {
diff --git a/src/client/pages/preferences/index.vue b/src/client/pages/preferences/index.vue
index f3a6f1db29..3dba0a7438 100644
--- a/src/client/pages/preferences/index.vue
+++ b/src/client/pages/preferences/index.vue
@@ -265,6 +265,10 @@ export default Vue.extend({
 			}
 			location.reload();
 		},
+
+		fixedWidgetsPosition() {
+			location.reload()
+		},
 	},
 
 	methods: {
diff --git a/src/client/scripts/sticky-sidebar.ts b/src/client/scripts/sticky-sidebar.ts
new file mode 100644
index 0000000000..872e162d2b
--- /dev/null
+++ b/src/client/scripts/sticky-sidebar.ts
@@ -0,0 +1,43 @@
+export class StickySidebar {
+	private lastScrollTop = 0;
+	private el: HTMLElement;
+	private spacer: HTMLElement;
+	private marginTop: number;
+	private isTop = false;
+	private isBottom = false;
+
+	constructor(el: StickySidebar['el'], spacer: StickySidebar['spacer'], marginTop = 0) {
+		this.el = el;
+		this.spacer = spacer;
+		this.marginTop = marginTop;
+	}
+
+	public calc(scrollTop: number) {
+		if (scrollTop > this.lastScrollTop) { // downscroll
+			const overflow = this.el.clientHeight - window.innerHeight;
+			this.el.style.bottom = null;
+			this.el.style.top = `${-overflow}px`;
+
+			this.isBottom = (scrollTop + window.innerHeight) >= (this.el.offsetTop + this.el.clientHeight);
+
+			if (this.isTop) {
+				this.isTop = false;
+				this.spacer.style.marginTop = `${scrollTop}px`;
+			}
+		} else { // upscroll
+			const overflow = this.el.clientHeight - window.innerHeight;
+			this.el.style.top = null;
+			this.el.style.bottom = `${-overflow - this.marginTop}px`;
+
+			this.isTop = scrollTop <= this.el.offsetTop;
+
+			if (this.isBottom) {
+				this.isBottom = false;
+				const overflow = this.el.clientHeight - window.innerHeight;
+				this.spacer.style.marginTop = `${scrollTop - (overflow + this.marginTop)}px`;
+			}
+		}
+
+		this.lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
+	}
+}
diff --git a/src/client/store.ts b/src/client/store.ts
index bf1bdf1a84..eee3f59618 100644
--- a/src/client/store.ts
+++ b/src/client/store.ts
@@ -57,7 +57,7 @@ export const defaultDeviceSettings = {
 	showFixedPostForm: false,
 	disablePagesScript: true,
 	enableInfiniteScroll: true,
-	fixedWidgetsPosition: true,
+	fixedWidgetsPosition: false,
 	roomGraphicsQuality: 'medium',
 	roomUseOrthographicCamera: true,
 	sfxVolume: 0.3,