diff --git a/src/App.js b/src/App.js
index c4360af5..4a59f412 100644
--- a/src/App.js
+++ b/src/App.js
@@ -81,11 +81,7 @@ export default {
     },
     isMobileLayout () { return this.$store.state.interface.mobileLayout },
     privateMode () { return this.$store.state.instance.private },
-    sidebarAlign () {
-      return {
-        'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0
-      }
-    },
+    reverseLayout () { return this.$store.getters.mergedConfig.sidebarRight },
     ...mapGetters(['mergedConfig'])
   },
   methods: {
diff --git a/src/App.scss b/src/App.scss
index fa2c6a79..7e428407 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1,125 +1,21 @@
+// stylelint-disable rscss/class-format
 @import './_variables.scss';
 
-body {
-  overflow: hidden;
-}
-
-#app-loaded {
-  min-height: 100vh;
-  min-width: 100vw;
-  overflow: hidden;
-
-  --navbar-height: 50px;
-}
-
-.navbar {
-  height: var(--navbar-height);
-}
-
-.app-bg-wrapper {
-  position: fixed;
-  z-index: -1;
-  height: 100%;
-  left: 0;
-  right: -20px;
-  background-size: cover;
-  background-repeat: no-repeat;
-  background-color: var(--wallpaper);
-  background-image: var(--body-background-image);
-  background-position: 50%;
-}
-
-h4 {
-  margin: 0;
-}
-
-#content {
-  overflow-y: auto;
-  position: sticky;
-}
-
-.app-layout {
-  position: relative;
-  display: grid;
-  grid-template-columns: auto auto;
-  grid-template-rows: 1fr;
-  box-sizing: border-box;
-  margin: 0 auto;
-  height: 100vh;
-  align-content: flex-start;
-  flex-wrap: wrap;
-  padding: 0 10px 0 10px;
-  grid-template-columns: auto auto;
-  justify-content: center;
-}
-
-.underlay {
-  height: 100%;
-  width: 100%;
-  grid-column-start: 1;
-  grid-column-end: 3;
-  grid-row-start: 1;
-  grid-row-end: 1;
-  margin: -0.5em;
-  padding: 0.5em;
-}
-
-.column {
-  max-width: 615px;
-  padding-top: 10px;
-  grid-row-start: 1;
-  grid-row-end: 1;
-
-  &:nth-child(2) {
-    grid-column: 1;
-  }
-
-  &:nth-child(3) {
-    grid-column: 2;
-  }
-
-  &.-mini {
-    max-width: 345px;
-  }
-
-  &.-scrollable {
-    position: sticky;
-    top: 0;
-    margin-top: calc(-1 * var(--navbar-padding));
-    max-height: 100vh;
-    overflow-y: auto;
-  }
-}
-
-body,
-.column.-scrollable {
-  &::-webkit-scrollbar {
-    display: block;
-    width: 0;
-  }
-}
-
-.underlay {
-  background-color: rgba(0,0,0,0.15);
-  background-color: var(--underlay, rgba(0,0,0,0.15));
-}
-
-.text-center {
-  text-align: center;
-}
-
 html {
   font-size: 14px;
+  overflow: hidden;
+  max-height: 100vh;
 }
 
 body {
-  overscroll-behavior-y: none;
+  overflow: hidden;
+  max-height: 100vh;
+  max-width: 100vw;
   font-family: sans-serif;
   font-family: var(--interfaceFont, sans-serif);
   margin: 0;
   color: $fallback--text;
   color: var(--text, $fallback--text);
-  max-width: 100vw;
   overflow-x: hidden;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
@@ -135,6 +31,134 @@ a {
   color: var(--link, $fallback--link);
 }
 
+h4 {
+  margin: 0;
+}
+
+nav {
+  z-index: 1000;
+  color: var(--topBarText);
+  background-color: $fallback--fg;
+  background-color: var(--topBar, $fallback--fg);
+  color: $fallback--faint;
+  color: var(--faint, $fallback--faint);
+  box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
+  box-shadow: var(--topBarShadow);
+  box-sizing: border-box;
+  height: var(--navbar-height);
+}
+
+#app-loaded {
+  min-height: 100vh;
+  min-width: 100vw;
+  overflow: hidden;
+
+  --navbar-height: 50px;
+}
+
+#content {
+  overscroll-behavior-y: none;
+  overflow-y: auto;
+  position: sticky;
+}
+
+#sidebar {
+  display: grid;
+  grid-template-columns: 100%;
+  row-gap: 1em;
+  grid-area: sidebar;
+  align-content: start;
+}
+
+#main-scroller {
+  grid-area: content;
+}
+
+.app-bg-wrapper {
+  position: fixed;
+  height: 100%;
+  top: var(--navbar-height);
+  z-index: -1000;
+  left: 0;
+  right: -20px;
+  background-size: cover;
+  background-repeat: no-repeat;
+  background-color: var(--wallpaper);
+  background-image: var(--body-background-image);
+  background-position: 50%;
+}
+
+.app-layout {
+  position: relative;
+  display: grid;
+  grid-template-columns: auto minmax(auto, 1fr);
+  grid-template-areas: "sidebar content";
+  grid-template-rows: 1fr;
+  box-sizing: border-box;
+  margin: 0 auto;
+  height: calc(100vh - var(--navbar-height));
+  align-content: flex-start;
+  flex-wrap: wrap;
+  padding: 0 10px 0 10px;
+  justify-content: center;
+
+  &.-reverse {
+    grid-template-columns: minmax(auto, 1fr) auto;
+    grid-template-areas: "content sidebar";
+  }
+}
+
+.underlay {
+  grid-column-start: 1;
+  grid-column-end: span 2;
+  grid-row-start: 1;
+  grid-row-end: 1;
+  margin: 0 -0.5em;
+  padding: 0 0.5em;
+  pointer-events: none;
+  background-color: rgba(0, 0, 0, 0.15);
+  background-color: var(--underlay, rgba(0, 0, 0, 0.15));
+  z-index: -2000;
+}
+
+.column {
+  box-sizing: border-box;
+  max-width: 615px;
+  padding-top: 10px;
+  grid-row-start: 1;
+  grid-row-end: 1;
+  margin: 0 0.5em;
+
+  &.-mini {
+    max-width: 345px;
+  }
+
+  &.-scrollable {
+    padding-top: 10px;
+    position: sticky;
+    top: 0;
+    max-height: calc(100vh - var(--navbar-height));
+    overflow-y: auto;
+    overflow-x: hidden;
+
+    .panel-heading.-sticky {
+      top: -10px;
+    }
+  }
+}
+
+#content,
+.column.-scrollable {
+  &::-webkit-scrollbar {
+    display: block;
+    width: 0;
+  }
+}
+
+.text-center {
+  text-align: center;
+}
+
 .button-default {
   user-select: none;
   color: $fallback--text;
@@ -166,12 +190,12 @@ a {
   }
 
   &:hover {
-    box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3);
+    box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
     box-shadow: var(--buttonHoverShadow);
   }
 
   &:active {
-    box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
+    box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset;
     box-shadow: var(--buttonPressedShadow);
     color: $fallback--text;
     color: var(--btnPressedText, $fallback--text);
@@ -204,7 +228,7 @@ a {
     color: var(--btnToggledText, $fallback--text);
     background-color: $fallback--fg;
     background-color: var(--btnToggled, $fallback--fg);
-    box-shadow: 0px 0px 4px 0px rgba(255, 255, 255, 0.3), 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset;
+    box-shadow: 0 0 4px 0 rgba(255, 255, 255, 0.3), 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset;
     box-shadow: var(--buttonPressedShadow);
 
     svg,
@@ -254,8 +278,9 @@ a {
   }
 }
 
-input, textarea, .input {
-
+input,
+textarea,
+.input {
   &.unstyled {
     border-radius: 0;
     background: none;
@@ -266,7 +291,7 @@ input, textarea, .input {
   border: none;
   border-radius: $fallback--inputRadius;
   border-radius: var(--inputRadius, $fallback--inputRadius);
-  box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.2) inset, 0px -1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 0px 2px 0px rgba(0, 0, 0, 1) inset;
+  box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2) inset, 0 -1px 0 0 rgba(255, 255, 255, 0.2) inset, 0 0 2px 0 rgba(0, 0, 0, 1) inset;
   box-shadow: var(--inputShadow);
   background-color: $fallback--fg;
   background-color: var(--input, $fallback--fg);
@@ -282,9 +307,11 @@ input, textarea, .input {
   height: 28px;
   line-height: 16px;
   hyphens: none;
-  padding: 8px .5em;
+  padding: 8px 0.5em;
 
-  &:disabled, &[disabled=disabled], &.disabled {
+  &:disabled,
+  &[disabled=disabled],
+  &.disabled {
     cursor: not-allowed;
     opacity: 0.5;
   }
@@ -299,18 +326,21 @@ input, textarea, .input {
 
   &[type=radio] {
     display: none;
+
     &:checked + label::before {
-      box-shadow: 0px 0px 2px black inset, 0px 0px 0px 4px $fallback--fg inset;
-      box-shadow: var(--inputShadow), 0px 0px 0px 4px var(--fg, $fallback--fg) inset;
+      box-shadow: 0 0 2px black inset, 0 0 0 4px $fallback--fg inset;
+      box-shadow: var(--inputShadow), 0 0 0 4px var(--fg, $fallback--fg) inset;
       background-color: var(--accent, $fallback--link);
     }
+
     &:disabled {
       &,
       & + label,
       & + label::before {
-        opacity: .5;
+        opacity: 0.5;
       }
     }
+
     + label::before {
       flex-shrink: 0;
       display: inline-block;
@@ -319,9 +349,9 @@ input, textarea, .input {
       width: 1.1em;
       height: 1.1em;
       border-radius: 100%; // Radio buttons should always be circle
-      box-shadow: 0px 0px 2px black inset;
+      box-shadow: 0 0 2px black inset;
       box-shadow: var(--inputShadow);
-      margin-right: .5em;
+      margin-right: 0.5em;
       background-color: $fallback--fg;
       background-color: var(--input, $fallback--fg);
       vertical-align: top;
@@ -337,17 +367,20 @@ input, textarea, .input {
 
   &[type=checkbox] {
     display: none;
+
     &:checked + label::before {
       color: $fallback--text;
       color: var(--inputText, $fallback--text);
     }
+
     &:disabled {
       &,
       & + label,
       & + label::before {
-        opacity: .5;
+        opacity: 0.5;
       }
     }
+
     + label::before {
       flex-shrink: 0;
       display: inline-block;
@@ -357,9 +390,9 @@ input, textarea, .input {
       height: 1.1em;
       border-radius: $fallback--checkboxRadius;
       border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
-      box-shadow: 0px 0px 2px black inset;
+      box-shadow: 0 0 2px black inset;
       box-shadow: var(--inputShadow);
-      margin-right: .5em;
+      margin-right: 0.5em;
       background-color: $fallback--fg;
       background-color: var(--input, $fallback--fg);
       vertical-align: top;
@@ -387,6 +420,7 @@ option {
 
 .hide-number-spinner {
   -moz-appearance: textfield;
+
   &[type=number]::-webkit-inner-spin-button,
   &[type=number]::-webkit-outer-spin-button {
     opacity: 0;
@@ -394,7 +428,8 @@ option {
   }
 }
 
-i[class*=icon-], .svg-inline--fa  {
+i[class*=icon-],
+.svg-inline--fa {
   color: $fallback--icon;
   color: var(--icon, $fallback--icon);
 }
@@ -426,10 +461,6 @@ i[class*=icon-], .svg-inline--fa  {
 }
 
 .auto-size {
-  flex: 1
-}
-
-main-router {
   flex: 1;
 }
 
@@ -439,23 +470,21 @@ main-router {
 
   p {
     margin: 0;
-    font-size: 0.8em
+    font-size: 0.8em;
   }
 }
 
 /* Panel */
-
 .panel {
-  display: flex;
   position: relative;
-
+  display: flex;
   flex-direction: column;
-  margin: 0.5em;
-
+  z-index: 0;
   background-color: $fallback--bg;
   background-color: var(--bg, $fallback--bg);
 
-  &::after, & {
+  &::after,
+  & {
     border-radius: $fallback--panelRadius;
     border-radius: var(--panelRadius, $fallback--panelRadius);
   }
@@ -463,16 +492,14 @@ main-router {
   &::after {
     content: '';
     position: absolute;
-
     top: 0;
     bottom: 0;
     left: 0;
     right: 0;
-
-    pointer-events: none;
-
-    box-shadow: 1px 1px 4px rgba(0,0,0,.6);
+    z-index: 2;
+    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
     box-shadow: var(--panelShadow);
+    pointer-events: none;
   }
 }
 
@@ -484,19 +511,67 @@ main-router {
 }
 
 .panel-heading {
+  position: relative;
+  box-sizing: border-box;
   display: flex;
   flex: none;
   border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
   border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
   background-size: cover;
-  padding: .6em .6em;
+  padding: 0.6em 0.6em;
   text-align: left;
   line-height: 28px;
   color: var(--panelText);
-  background-color: $fallback--fg;
-  background-color: var(--panel, $fallback--fg);
+  background-color: $fallback--bg;
+  background-color: var(--bg, $fallback--bg);
   align-items: baseline;
-  box-shadow: var(--panelHeaderShadow);
+  height: var(--panelHeadingHeight);
+  z-index: -2;
+
+  --panelHeadingHeight: 45px;
+
+  &.-flexible-height {
+    --panelHeadingHeight: auto;
+
+    &::after,
+    &::before {
+      display: none;
+    }
+  }
+
+  &.-sticky {
+    position: sticky;
+    top: 0;
+    z-index: 2;
+  }
+
+  &::after,
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    left: 0;
+    pointer-events: none;
+  }
+
+  &::after {
+    background-color: $fallback--fg;
+    background-color: var(--panel, $fallback--fg);
+    z-index: -2;
+    border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
+    border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
+    box-shadow: var(--panelHeaderShadow);
+  }
+
+  &::before {
+    bottom: -20px;
+    z-index: -1;
+    border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
+    border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
+    mask: linear-gradient(to bottom, white var(--panelHeadingHeight), transparent var(--panelHeadingHeight));
+  }
 
   .title {
     flex: 1 0 auto;
@@ -527,7 +602,7 @@ main-router {
     min-height: 0;
     box-sizing: border-box;
     margin: 0;
-    margin-left: .5em;
+    margin-left: 0.5em;
     min-width: 1px;
     align-self: stretch;
   }
@@ -562,7 +637,7 @@ main-router {
   a,
   .-link {
     color: $fallback--link;
-    color: var(--panelLink, $fallback--link)
+    color: var(--panelLink, $fallback--link);
   }
 }
 
@@ -574,7 +649,7 @@ main-router {
 /* TODO Should remove timeline-footer from here when we refactor panels into
  * separate component and utilize slots
  */
-.panel-footer, .timeline-footer {
+.panel-footer {
   display: flex;
   border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
   border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
@@ -605,37 +680,18 @@ main-router {
   margin: 0;
 }
 
-.container > * {
-  min-width: 0px;
-}
-
 .fa {
   color: grey;
 }
 
-nav {
-  z-index: 1000;
-  color: var(--topBarText);
-  background-color: $fallback--fg;
-  background-color: var(--topBar, $fallback--fg);
-  color: $fallback--faint;
-  color: var(--faint, $fallback--faint);
-  box-shadow: 0px 0px 4px rgba(0,0,0,.6);
-  box-shadow: var(--topBarShadow);
-  box-sizing: border-box;
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.2s;
 }
 
-.fade-enter-active, .fade-leave-active {
-  transition: opacity .2s
-}
-.fade-enter-from, .fade-leave-active {
-  opacity: 0
-}
-
-.main {
-  flex-basis: 50%;
-  flex-grow: 1;
-  flex-shrink: 1;
+.fade-enter-from,
+.fade-leave-active {
+  opacity: 0;
 }
 
 .sidebar-bounds {
@@ -653,34 +709,6 @@ nav {
   display: none;
 }
 
-@media all and (min-width: 800px) {
-  .sidebar-bounds {
-    overflow: hidden;
-    max-height: 100vh;
-    width: 345px;
-    position: fixed;
-    margin-top: -10px;
-
-    .sidebar-scroller {
-      height: 96vh;
-      width: 365px;
-      padding-top: 10px;
-      padding-right: 50px;
-      overflow-x: hidden;
-      overflow-y: scroll;
-    }
-
-    .sidebar {
-      width: 345px;
-    }
-  }
-  .sidebar-flexer {
-    max-height: 96vh;
-    flex-shrink: 0;
-    flex-grow: 0;
-  }
-}
-
 .badge {
   box-sizing: border-box;
   display: inline-block;
@@ -764,7 +792,7 @@ nav {
 }
 
 .visibility-notice {
-  padding: .5em;
+  padding: 0.5em;
   border: 1px solid $fallback--faint;
   border: 1px solid var(--faint, $fallback--faint);
   border-radius: $fallback--inputRadius;
@@ -779,7 +807,7 @@ nav {
     position: absolute;
     top: 0;
     right: 0;
-    padding: .5em;
+    padding: 0.5em;
     color: inherit;
   }
 }
@@ -796,30 +824,57 @@ nav {
   }
 }
 
-@keyframes shakeError {
-  0% {
-    transform: translateX(0);
+.login-hint {
+  text-align: center;
+
+  @media all and (min-width: 801px) {
+    display: none;
   }
-  15% {
-    transform: translateX(0.375rem);
+
+  a {
+    display: inline-block;
+    padding: 1em 0;
+    width: 100%;
   }
-  30% {
-    transform: translateX(-0.375rem);
+}
+
+.btn.button-default {
+  min-height: 28px;
+}
+
+.new-status-notification {
+  position: relative;
+  font-size: 1.1em;
+  z-index: 1;
+  flex: 1;
+}
+
+@media all and (min-width: 800px) {
+  .sidebar-bounds {
+    overflow: hidden;
+    max-height: 100vh;
+    width: 345px;
+    position: fixed;
+    margin-top: -10px;
+
+    .sidebar-scroller {
+      height: 96vh;
+      width: 365px;
+      padding-top: 10px;
+      padding-right: 50px;
+      overflow-x: hidden;
+      overflow-y: scroll;
+    }
+
+    .sidebar {
+      width: 345px;
+    }
   }
-  45% {
-    transform: translateX(0.375rem);
-  }
-  60% {
-    transform: translateX(-0.375rem);
-  }
-  75% {
-    transform: translateX(0.375rem);
-  }
-  90% {
-    transform: translateX(-0.375rem);
-  }
-  100% {
-    transform: translateX(0);
+
+  .sidebar-flexer {
+    max-height: 96vh;
+    flex-shrink: 0;
+    flex-grow: 0;
   }
 }
 
@@ -832,14 +887,6 @@ nav {
     display: flex;
   }
 
-  .container {
-    padding: 0;
-  }
-
-  .panel {
-    margin: 0.5em 0 0.5em 0;
-  }
-
   .menu-button {
     display: block;
     margin-right: 0.8em;
@@ -850,41 +897,6 @@ nav {
   }
 }
 
-.setting-list,
-.option-list{
-  list-style-type: none;
-  padding-left: 2em;
-  li {
-    margin-bottom: 0.5em;
-  }
-  .suboptions {
-    margin-top: 0.3em
-  }
-}
-
-.login-hint {
-  text-align: center;
-
-  @media all and (min-width: 801px) {
-    display: none;
-  }
-
-  a {
-    display: inline-block;
-    padding: 1em 0px;
-    width: 100%;
-  }
-}
-
-.btn.button-default {
-  min-height: 28px;
-}
-
-.animate-spin {
-  animation: spin 2s infinite linear;
-  display: inline-block;
-}
-
 @keyframes spin {
   0% {
     transform: rotate(0deg);
@@ -895,45 +907,36 @@ nav {
   }
 }
 
-.new-status-notification {
-  position: relative;
-  font-size: 1.1em;
-  z-index: 1;
-  flex: 1;
-}
+@keyframes shakeError {
+  0% {
+    transform: translateX(0);
+  }
 
-.chat-layout {
-  // Needed for smoother chat navigation in the desktop Safari (otherwise the chat layout "jumps" as the chat opens).
-  overflow: hidden;
-  height: 100%;
+  15% {
+    transform: translateX(0.375rem);
+  }
 
-  // Get rid of scrollbar on body as scrolling happens on different element
-  // Ensures the fixed position of the mobile browser bars on scroll up / down events.
-  // Prevents the mobile browser bars from overlapping or hiding the message posting form.
-  @media all and (max-width: 800px) {
-    body {
-      height: 100%;
-    }
+  30% {
+    transform: translateX(-0.375rem);
+  }
 
-    #app {
-      height: 100%;
-      overflow: hidden;
-      min-height: auto;
-    }
+  45% {
+    transform: translateX(0.375rem);
+  }
 
-    #app_bg_wrapper {
-      overflow: hidden;
-    }
+  60% {
+    transform: translateX(-0.375rem);
+  }
 
-    .main {
-      overflow: hidden;
-      height: 100%;
-    }
+  75% {
+    transform: translateX(0.375rem);
+  }
 
-    #content {
-      padding-top: 0;
-      height: 100%;
-      overflow: visible;
-    }
+  90% {
+    transform: translateX(-0.375rem);
+  }
+
+  100% {
+    transform: translateX(0);
   }
 }
diff --git a/src/App.vue b/src/App.vue
index dc2359f3..71de2a36 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -7,28 +7,28 @@
       id="app_bg_wrapper"
       class="app-bg-wrapper"
     />
-    <MobileNav class="navbar" v-if="isMobileLayout" />
-    <DesktopNav class="navbar" v-else />
-    <div class="app-bg-wrapper app-container-wrapper" />
+    <MobileNav v-if="isMobileLayout" />
+    <DesktopNav v-else />
     <div
       id="content"
       class="app-layout container"
+      :class="{ '-reverse': reverseLayout }"
     >
       <div class="underlay"/>
       <div
+        id="sidebar"
         class="column -scrollable -mini mobile-hidden"
-        :style="sidebarAlign"
       >
         <user-panel />
-        <div v-if="!isMobileLayout">
+        <template v-if="!isMobileLayout">
           <nav-panel />
           <instance-specific-panel v-if="showInstanceSpecificPanel" />
           <features-panel v-if="!currentUser && showFeaturesPanel" />
           <who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
           <notifications v-if="currentUser" />
-        </div>
+        </template>
       </div>
-      <div class="column main">
+      <div id="main-scroller" class="column main">
         <div
           v-if="!currentUser"
           class="login-hint panel panel-default"
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
index aef11712..59e884c4 100644
--- a/src/components/chat/chat.js
+++ b/src/components/chat/chat.js
@@ -6,7 +6,7 @@ import PostStatusForm from '../post_status_form/post_status_form.vue'
 import ChatTitle from '../chat_title/chat_title.vue'
 import chatService from '../../services/chat_service/chat_service.js'
 import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
-import { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight, isScrollable } from './chat_layout_utils.js'
+import { getScrollPosition, getNewTopPosition, isBottomedOut } from './chat_layout_utils.js'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faChevronDown,
@@ -20,7 +20,7 @@ library.add(
 )
 
 const BOTTOMED_OUT_OFFSET = 10
-const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 150
+const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
 const SAFE_RESIZE_TIME_OFFSET = 100
 const MARK_AS_READ_DELAY = 1500
 const MAX_RETRIES = 10
@@ -52,7 +52,6 @@ const Chat = {
     }
 
     this.$nextTick(() => {
-      this.updateScrollableContainerHeight()
       this.handleResize()
     })
     this.setChatLayout()
@@ -132,7 +131,6 @@ const Chat = {
     onFilesDropped () {
       this.$nextTick(() => {
         this.handleResize()
-        this.updateScrollableContainerHeight()
       })
     },
     handleVisibilityChange () {
@@ -154,10 +152,6 @@ const Chat = {
       if (html) {
         html.classList.add('chat-layout')
       }
-
-      this.$nextTick(() => {
-        this.updateScrollableContainerHeight()
-      })
     },
     unsetChatLayout () {
       let html = document.querySelector('html')
@@ -167,17 +161,9 @@ const Chat = {
     },
     handleLayoutChange () {
       this.$nextTick(() => {
-        this.updateScrollableContainerHeight()
         this.scrollDown()
       })
     },
-    // Ensures the proper position of the posting form in the mobile layout (the mobile browser panel does not overlap or hide it)
-    updateScrollableContainerHeight () {
-      const header = this.$refs.header
-      const footer = this.$refs.footer
-      const inner = this.mobileLayout ? window.document.body : this.$refs.inner
-      this.scrollableContainerHeight = scrollableContainerHeight(inner, header, footer) + 'px'
-    },
     // Preserves the scroll position when OSK appears or the posting form changes its height.
     handleResize (opts = {}) {
       const { expand = false, delayed = false } = opts
@@ -190,17 +176,14 @@ const Chat = {
       }
 
       this.$nextTick(() => {
-        this.updateScrollableContainerHeight()
-
         const { offsetHeight = undefined } = this.lastScrollPosition
-        this.lastScrollPosition = getScrollPosition(this.$refs.scrollable)
+        this.lastScrollPosition = getScrollPosition(document.getElementById('content'))
 
         const diff = this.lastScrollPosition.offsetHeight - offsetHeight
         if (diff < 0 || (!this.bottomedOut() && expand)) {
           this.$nextTick(() => {
-            this.updateScrollableContainerHeight()
-            this.$refs.scrollable.scrollTo({
-              top: this.$refs.scrollable.scrollTop - diff,
+            document.getElementById('content').scrollTo({
+              top: document.getElementById('content').scrollTop - diff,
               left: 0
             })
           })
@@ -209,7 +192,7 @@ const Chat = {
     },
     scrollDown (options = {}) {
       const { behavior = 'auto', forceRead = false } = options
-      const scrollable = this.$refs.scrollable
+      const scrollable = document.getElementById('content')
       if (!scrollable) { return }
       this.$nextTick(() => {
         scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior })
@@ -228,10 +211,10 @@ const Chat = {
       })
     },
     bottomedOut (offset) {
-      return isBottomedOut(this.$refs.scrollable, offset)
+      return isBottomedOut(document.getElementById('content'), offset)
     },
     reachedTop () {
-      const scrollable = this.$refs.scrollable
+      const scrollable = document.getElementById('content')
       return scrollable && scrollable.scrollTop <= 0
     },
     cullOlderCheck () {
@@ -263,7 +246,7 @@ const Chat = {
       }
     }, 200),
     handleScrollUp (positionBeforeLoading) {
-      const positionAfterLoading = getScrollPosition(this.$refs.scrollable)
+      const positionAfterLoading = getScrollPosition(document.getElementById('content'))
       this.$refs.scrollable.scrollTo({
         top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
         left: 0
@@ -285,22 +268,18 @@ const Chat = {
             chatService.clear(chatMessageService)
           }
 
-          const positionBeforeUpdate = getScrollPosition(this.$refs.scrollable)
+          const positionBeforeUpdate = getScrollPosition(document.getElementById('content'))
           this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
             this.$nextTick(() => {
               if (fetchOlderMessages) {
                 this.handleScrollUp(positionBeforeUpdate)
               }
 
-              if (isFirstFetch) {
-                this.updateScrollableContainerHeight()
-              }
-
               // In vertical screens, the first batch of fetched messages may not always take the
               // full height of the scrollable container.
               // If this is the case, we want to fetch the messages until the scrollable container
               // is fully populated so that the user has the ability to scroll up and load the history.
-              if (!isScrollable(this.$refs.scrollable) && messages.length > 0) {
+              if (messages.length > 0) {
                 this.fetchChat({ maxId: this.currentChatMessageService.minId })
               }
             })
@@ -336,9 +315,6 @@ const Chat = {
         this.handleResize()
         // When the posting form size changes because of a media attachment, we need an extra resize
         // to account for the potential delay in the DOM update.
-        setTimeout(() => {
-          this.updateScrollableContainerHeight()
-        }, SAFE_RESIZE_TIME_OFFSET)
         this.scrollDown({ forceRead: true })
       })
     },
diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss
index 3a26686c..c1f2dcf2 100644
--- a/src/components/chat/chat.scss
+++ b/src/components/chat/chat.scss
@@ -1,19 +1,12 @@
 .chat-view {
   display: flex;
-  height: calc(100vh - 60px);
-  width: 100%;
-
-  .chat-title {
-    // prevents chat header jumping on when the user avatar loads
-    height: 28px;
-  }
+  height: 100%;
 
   .chat-view-inner {
     height: auto;
     width: 100%;
     overflow: visible;
     display: flex;
-    margin: 0.5em 0.5em 0 0.5em;
   }
 
   .chat-view-body {
@@ -32,11 +25,9 @@
     }
   }
 
-  .scrollable-message-list {
+  .message-list {
     padding: 0 0.8em;
     height: 100%;
-    overflow-y: scroll;
-    overflow-x: hidden;
     display: flex;
     flex-direction: column;
   }
@@ -44,12 +35,13 @@
   .footer {
     position: sticky;
     bottom: 0;
+    background-color: $fallback--bg;
+    background-color: var(--bg, $fallback--bg);
   }
 
   .chat-view-heading {
     align-items: center;
     justify-content: space-between;
-    top: 50px;
     display: flex;
     z-index: 2;
     position: sticky;
diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue
index 493c5d5a..3b0129fa 100644
--- a/src/components/chat/chat.vue
+++ b/src/components/chat/chat.vue
@@ -8,7 +8,7 @@
       >
         <div
           ref="header"
-          class="panel-heading chat-view-heading mobile-hidden"
+          class="panel-heading -sticky chat-view-heading mobile-hidden"
         >
           <a
             class="go-back-button"
@@ -27,10 +27,8 @@
           </div>
         </div>
         <div
-          ref="scrollable"
-          class="scrollable-message-list"
+          class="message-list"
           :style="{ height: scrollableContainerHeight }"
-          @scroll="handleScroll"
         >
           <template v-if="!errorLoadingChat">
             <ChatMessage
diff --git a/src/components/chat/chat_layout_utils.js b/src/components/chat/chat_layout_utils.js
index 50a933ac..5c4aa5bf 100644
--- a/src/components/chat/chat_layout_utils.js
+++ b/src/components/chat/chat_layout_utils.js
@@ -1,5 +1,6 @@
 // Captures a scroll position
 export const getScrollPosition = (el) => {
+  console.log(el)
   return {
     scrollTop: el.scrollTop,
     scrollHeight: el.scrollHeight,
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index 229f3006..1c28ef77 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -181,7 +181,7 @@
   .mobile-notifications {
     margin-top: 50px;
     width: 100vw;
-    height: calc(100vh - 50px);
+    height: calc(100vh - var(--navbar-height));
     overflow-x: hidden;
     overflow-y: scroll;
 
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 2ce5d56f..9d8fa059 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -6,7 +6,7 @@
     <div :class="mainClass">
       <div
         v-if="!noHeading"
-        class="panel-heading"
+        class="notifications-heading panel-heading -sticky"
       >
         <div class="title">
           {{ $t('notifications.notifications') }}
diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue
index 3ffa5425..e0579568 100644
--- a/src/components/password_reset/password_reset.vue
+++ b/src/components/password_reset/password_reset.vue
@@ -91,6 +91,9 @@
     flex-direction: column;
     margin-top: 0.6em;
     max-width: 18rem;
+    > * {
+      min-width: 0;
+    }
   }
 
   .form-group {
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 3d409109..1e735c69 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -271,7 +271,10 @@ $validations-cRed: #f04124;
   .container {
     display: flex;
     flex-direction: row;
-    //margin-bottom: 1em;
+
+    > * {
+      min-width: 0;
+    }
   }
 
   .terms-of-service {
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index 2f7649a9..f3738a8c 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -2,6 +2,18 @@
 .settings-modal {
   overflow: hidden;
 
+  .setting-list,
+  .option-list {
+    list-style-type: none;
+    padding-left: 2em;
+    li {
+      margin-bottom: 0.5em;
+    }
+    .suboptions {
+      margin-top: 0.3em
+    }
+  }
+
   &.peek {
     .settings-modal-panel {
       /* Explanation:
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
index 5ed43e0d..e38e2a53 100644
--- a/src/components/status/status.scss
+++ b/src/components/status/status.scss
@@ -42,6 +42,10 @@
     display: flex;
     padding: var(--status-margin, $status-margin);
 
+    > * {
+      min-width: 0;
+    }
+
     &.-repeat {
       padding-top: 0;
     }
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 8ec5d1e5..9056319b 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -64,7 +64,7 @@ const Timeline = {
       if (this.blockingClicks) rootClasses = rootClasses.concat(['-blocked', '_misclick-prevention'])
       return {
         root: rootClasses,
-        header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading'] : []),
+        header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading', '-sticky'] : []),
         body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
         footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
       }
@@ -89,7 +89,7 @@ const Timeline = {
     const credentials = store.state.users.currentUser.credentials
     const showImmediately = this.timeline.visibleStatuses.length === 0
 
-    window.addEventListener('scroll', this.handleScroll)
+    document.getElementById('content').addEventListener('scroll', this.handleScroll)
 
     if (store.state.api.fetchers[this.timelineName]) { return false }
 
@@ -111,7 +111,7 @@ const Timeline = {
     setTimeout(this.determineVisibleStatuses, 250)
   },
   unmounted () {
-    window.removeEventListener('scroll', this.handleScroll)
+    document.getElementById('content').removeEventListener('scroll', this.handleScroll)
     window.removeEventListener('keydown', this.handleShortKey)
     if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
     this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss
index 2c5a67e2..c10375d4 100644
--- a/src/components/timeline/timeline.scss
+++ b/src/components/timeline/timeline.scss
@@ -13,7 +13,6 @@
     max-width: 100%;
     flex-wrap: nowrap;
     align-items: center;
-    position: relative;
 
     .loadmore-button {
       flex-shrink: 0;
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 14b4643a..6e044bb6 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -8,7 +8,7 @@
       :style="style"
       class="background-image"
     />
-    <div class="panel-heading">
+    <div class="panel-heading -flexible-height">
       <div class="user-info">
         <div class="container">
           <a
@@ -331,6 +331,7 @@
     border-top-left-radius: calc(var(--panelRadius) - 1px);
     border-top-right-radius: calc(var(--panelRadius) - 1px);
     background-color: var(--profileBg);
+    z-index: -2;
 
     &.hide-bio {
       mask-size: 100% 40px;
@@ -385,11 +386,16 @@
   padding: 0 26px;
 
   .container {
+    min-width: 0;
     padding: 16px 0 6px;
     display: flex;
     align-items: flex-start;
     max-height: 56px;
 
+    > * {
+      min-width: 0;
+    }
+
     .Avatar {
       --_avatarShadowBox: var(--avatarShadow);
       --_avatarShadowFilter: var(--avatarShadowFilter);