diff --git a/src/components/common/CollapsibleSection.tsx b/src/components/common/CollapsibleSection.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1f31e21b79283c613ab6a5f0af2201350af49680
--- /dev/null
+++ b/src/components/common/CollapsibleSection.tsx
@@ -0,0 +1,52 @@
+import Details from "../ui/Details";
+import { State, store } from "../../redux";
+import { Action } from "../../redux/reducers";
+import { Children } from "../../types/Preact";
+import { ChevronDown } from "@styled-icons/boxicons-regular";
+
+interface Props {
+    id: string;
+    defaultValue: boolean;
+
+    sticky?: boolean;
+    large?: boolean;
+
+    summary: Children;
+    children: Children;
+}
+
+export default function CollapsibleSection({ id, defaultValue, summary, children, ...detailsProps }: Props) {
+    const state: State = store.getState();
+
+    function setState(state: boolean) {
+        if (state === defaultValue) {
+            store.dispatch({
+                type: 'SECTION_TOGGLE_UNSET',
+                id
+            } as Action);
+        } else {
+            store.dispatch({
+                type: 'SECTION_TOGGLE_SET',
+                id,
+                state
+            } as Action);
+        }
+    }
+
+    return (
+        <Details
+            open={state.sectionToggle[id] ?? defaultValue}
+            onToggle={e => setState(e.currentTarget.open)}
+            {...detailsProps}>
+            <summary>
+                <ChevronDown size={20} />
+                { summary }
+                {/*<Overline type="subtle" className="overline">*/}
+                    {/*<div className="title">*/}
+                    {/*</div>*/}
+                {/*</Overline>*/}
+            </summary>
+            { children }
+        </Details>
+    )
+}
diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx
index edde0aaa3fa717fba5a2850e2391cbf13d070298..16e586bbcfe6f93e451479596ac3a81faad160e0 100644
--- a/src/components/navigation/left/HomeSidebar.tsx
+++ b/src/components/navigation/left/HomeSidebar.tsx
@@ -1,4 +1,4 @@
-import { Localizer, Text } from "preact-i18n";
+import { Text } from "preact-i18n";
 import { useContext, useEffect } from "preact/hooks";
 import { Home, UserDetail, Wrench, Notepad } from "@styled-icons/boxicons-solid";
 
@@ -105,16 +105,9 @@ function HomeSidebar(props: Props) {
                         </ButtonItem>
                     </Link>
                 )}
-                <Localizer>
-                    <Category
-                        text={
-                            (
-                                <Text id="app.main.categories.conversations" />
-                            ) as any
-                        }
-                        action={() => openScreen({ id: "special_input", type: "create_group" })}
-                    />
-                </Localizer>
+                <Category
+                    text={<Text id="app.main.categories.conversations" />}
+                    action={() => openScreen({ id: "special_input", type: "create_group" })} />
                 {channelsArr.length === 0 && <img src={placeholderSVG} />}
                 {channelsArr.map(x => {
                     let user;
diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx
index 49e5795951f632cb783c283744640de2dc4ec939..3dd7aff9d37dddba1ef063fa6414b0978d4d8e27 100644
--- a/src/components/navigation/left/ServerSidebar.tsx
+++ b/src/components/navigation/left/ServerSidebar.tsx
@@ -14,6 +14,7 @@ import ServerHeader from "../../common/ServerHeader";
 import { useEffect } from "preact/hooks";
 import Category from "../../ui/Category";
 import ConditionalLink from "../../../lib/ConditionalLink";
+import CollapsibleSection from "../../common/CollapsibleSection";
 
 interface Props {
     unreads: Unreads;
@@ -69,6 +70,7 @@ function ServerSidebar(props: Props & WithDispatcher) {
 
     let uncategorised = new Set(server.channels);
     let elements = [];
+    
     function addChannel(id: string) {
         const entry = channels.find(x => x._id === id);
         if (!entry) return;
@@ -76,9 +78,8 @@ function ServerSidebar(props: Props & WithDispatcher) {
         const active = channel?._id === entry._id;
 
         return (
-            <ConditionalLink active={active} to={`/server/${server!._id}/channel/${entry._id}`}>
+            <ConditionalLink key={entry._id} active={active} to={`/server/${server!._id}/channel/${entry._id}`}>
                 <ChannelButton
-                    key={entry._id}
                     channel={entry}
                     active={active}
                     alert={entry.unread}
@@ -90,16 +91,24 @@ function ServerSidebar(props: Props & WithDispatcher) {
 
     if (server.categories) {
         for (let category of server.categories) {
-            elements.push(<Category text={category.title} />);
-
+            let channels = [];
             for (let id of category.channels) {
                 uncategorised.delete(id);
-                elements.push(addChannel(id));
+                channels.push(addChannel(id));
             }
+
+            elements.push(
+                <CollapsibleSection
+                    id={`category_${category.id}`}
+                    defaultValue
+                    summary={<Category text={category.title} />}>
+                    { channels }
+                </CollapsibleSection>
+            );
         }
     }
 
-    for (let id of uncategorised) {
+    for (let id of Array.from(uncategorised).reverse()) {
         elements.unshift(addChannel(id));
     }
 
diff --git a/src/components/ui/Category.tsx b/src/components/ui/Category.tsx
index ccdd4e91c3b8ddf6fbb7849d117d48a40dc757da..3f76d2846d585b5b5beaabdfe5b3e5d8e793b020 100644
--- a/src/components/ui/Category.tsx
+++ b/src/components/ui/Category.tsx
@@ -31,7 +31,7 @@ const CategoryBase = styled.div<Pick<Props, 'variant'>>`
     ` }
 `;
 
-type Props = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'children' | 'as'> & {
+type Props = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'children' | 'as' | 'action'> & {
     text: Children;
     action?: () => void;
     variant?: 'default' | 'uniform';
diff --git a/src/components/ui/Details.tsx b/src/components/ui/Details.tsx
index 8bf47059c2c4e544ea6ddddbdb36fddc1066be55..0a032022f26c639d8662c6ac3f3ab9554d3f3bc2 100644
--- a/src/components/ui/Details.tsx
+++ b/src/components/ui/Details.tsx
@@ -1,16 +1,29 @@
-import styled from "styled-components";
+import styled, { css } from "styled-components";
 
-export default styled.details`
+export default styled.details<{ sticky?: boolean, large?: boolean }>`
     summary {
+        ${ props => props.sticky && css`
+            top: -1px;
+            z-index: 10;
+            position: sticky;
+        ` }
+
+        ${ props => props.large && css`
+            padding: 5px 0;
+        ` }
+
         outline: none;
+        display: flex;
+        cursor: pointer;
         list-style: none;
+        align-items: center;
         transition: .2s opacity;
 
         &::marker, &::-webkit-details-marker {
             display: none;
         }
 
-        svg {
+        > svg {
             flex-shrink: 0;
             transition: .2s ease transform;
         }
diff --git a/src/components/ui/Overline.tsx b/src/components/ui/Overline.tsx
index 5a9f9d04ca4af9446550599bcb6de8ebd1b2513f..171590770c6ae480838b913a71f49260a60bea50 100644
--- a/src/components/ui/Overline.tsx
+++ b/src/components/ui/Overline.tsx
@@ -5,6 +5,7 @@ import { Text } from 'preact-i18n';
 type Props = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'children' | 'as'> & {
     error?: string;
     block?: boolean;
+    spaced?: boolean;
     children?: Children;
     type?: "default" | "subtle" | "error";
 }
@@ -12,7 +13,10 @@ type Props = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'children' | 'as'> & {
 const OverlineBase = styled.div<Omit<Props, "children" | "error">>`
     display: inline;
     margin: 0.4em 0;
-    margin-top: 0.8em;
+
+    ${ props => props.spaced && css`
+        margin-top: 0.8em;
+    ` }
 
     font-size: 14px;
     font-weight: 600;
diff --git a/src/context/Locale.tsx b/src/context/Locale.tsx
index 4afc9f5063e2049e67b3baa2c1019187e900a305..873d57a67fff81de7aa71512c5f825868813ecb3 100644
--- a/src/context/Locale.tsx
+++ b/src/context/Locale.tsx
@@ -150,12 +150,6 @@ function Locale({ children, locale }: Props) {
             return;
         }
 
-        if (lang.i18n === "hardcore") {
-            // eslint-disable-next-line @typescript-eslint/no-explicit-any
-            setDefinition({} as any);
-            return;
-        }
-
         import(`../../external/lang/${lang.i18n}.json`).then(
             async (lang_file) => {
                 const defn = transformLanguage(lang_file.default);
diff --git a/src/pages/friends/Friend.module.scss b/src/pages/friends/Friend.module.scss
index d6338774040befec417f3198580a57794bad8d65..ee9c6f90ef010b31c2b21ecfcdab3ee17473ad95 100644
--- a/src/pages/friends/Friend.module.scss
+++ b/src/pages/friends/Friend.module.scss
@@ -14,30 +14,6 @@
     padding: 0 10px 10px 10px;
     user-select: none;
     overflow-y: scroll;
-    
-    summary {
-        position: sticky;
-        z-index: 10;
-        top: -1px;
-    }
-
-    .overline {
-        display: flex;
-        align-items: center;
-        background: var(--primary-background);
-        padding: 5px 0;
-        cursor: pointer;
-
-        .title {
-            text-overflow: ellipsis;
-            overflow: hidden;
-            white-space: nowrap;
-        }
-        
-        svg {
-            margin-inline-end: 4px;
-        }
-    }
 
     &[data-empty="true"] {
         img {
diff --git a/src/pages/friends/Friends.tsx b/src/pages/friends/Friends.tsx
index 284641e734e10cedef688a03ad60a1a6a48b6555..01d36085c98039039dce8a121c0a406da29215be 100644
--- a/src/pages/friends/Friends.tsx
+++ b/src/pages/friends/Friends.tsx
@@ -15,6 +15,7 @@ import { UserDetail, MessageAdd, UserPlus } from "@styled-icons/boxicons-solid";
 import { TextReact } from "../../lib/i18n";
 import { Children } from "../../types/Preact";
 import Details from "../../components/ui/Details";
+import CollapsibleSection from "../../components/common/CollapsibleSection";
 
 export default function Friends() {
     const { openScreen } = useIntermediate();
@@ -30,17 +31,17 @@ export default function Friends() {
         ) ],
         [ 'app.special.friends.sent', users.filter(x =>
             x.relationship === Users.Relationship.Outgoing
-        ) ],
+        ), 'outgoing' ],
         [ 'app.status.online', friends.filter(x =>
             x.online && x.status?.presence !== Users.Presence.Invisible
-        ) ],
+        ), 'online' ],
         [ 'app.status.offline', friends.filter(x =>
             !x.online || x.status?.presence === Users.Presence.Invisible
-        ) ],
-        [ 'app.special.friends.blocked', friends.filter(x =>
+        ), 'offline' ],
+        [ 'app.special.friends.blocked', users.filter(x =>
             x.relationship === Users.Relationship.Blocked
-        ) ]
-    ] as [ string, User[] ][];
+        ), 'blocked' ]
+    ] as [ string, User[], string ][];
 
     const incoming = lists[0][1];
     const userlist: Children[] = incoming.map(x => <b>{ x.username }</b>);
@@ -108,22 +109,18 @@ export default function Friends() {
                 </div> }
 
                 {
-                    lists.map(([i18n, list], index) => {
+                    lists.map(([i18n, list, section_id], index) => {
                         if (index === 0) return;
                         if (list.length === 0) return;
 
                         return (
-                            <Details open>
-                                <summary>
-                                    <Overline className={styles.overline} type="subtle">
-                                        <ChevronDown size={20} />
-                                        <div className={styles.title}>
-                                            <Text id={i18n} /> — { list.length }
-                                        </div>
-                                    </Overline>
-                                </summary>
+                            <CollapsibleSection
+                                id={`friends_${section_id}`}
+                                defaultValue={true}
+                                sticky large
+                                summary={<Overline type="subtle" className="overline"><Text id={i18n} /> — { list.length }</Overline>}>
                                 { list.map(x => <Friend key={x._id} user={x} />) }
-                            </Details>
+                            </CollapsibleSection>
                         )
                     })
                 }
diff --git a/src/pages/settings/panes/Appearance.tsx b/src/pages/settings/panes/Appearance.tsx
index b0ed1dae921387160d26356b030181d0a732f7bd..6ddd2dc53ce1e933a9c9b3d2203ba9aa8869c8cf 100644
--- a/src/pages/settings/panes/Appearance.tsx
+++ b/src/pages/settings/panes/Appearance.tsx
@@ -22,6 +22,7 @@ import mutantSVG from '../assets/mutant_emoji.svg';
 import notoSVG from '../assets/noto_emoji.svg';
 import openmojiSVG from '../assets/openmoji_emoji.svg';
 import twemojiSVG from '../assets/twemoji_emoji.svg';
+import CollapsibleSection from "../../../components/common/CollapsibleSection";
 
 interface Props {
     settings: Settings;
@@ -171,11 +172,7 @@ export function Component(props: Props & WithDispatcher) {
                 </div>
             </div>
 
-            <details>
-                <summary>
-                    <Text id="app.settings.pages.appearance.advanced" />
-                    <div className={styles.divider}></div>
-                </summary>
+            <CollapsibleSection id="settings_advanced_appearance" defaultValue={false} summary={<Text id="app.settings.pages.appearance.advanced" />}>
                 <h3>
                     <Text id="app.settings.pages.appearance.overrides" />
                 </h3>
@@ -272,7 +269,7 @@ export function Component(props: Props & WithDispatcher) {
                     code
                     value={css}
                     onChange={ev => setCSS(ev.currentTarget.value)} />
-            </details>
+            </CollapsibleSection>
         </div>
     );
 }
diff --git a/src/redux/index.ts b/src/redux/index.ts
index dc90bec03a461bd90ec4a8730cc41942684a02c6..4bee0dcd667006f406e247a19cb1e8d3c7f1ab35 100644
--- a/src/redux/index.ts
+++ b/src/redux/index.ts
@@ -14,6 +14,7 @@ import { QueuedMessage } from "./reducers/queue";
 import { ExperimentOptions } from "./reducers/experiments";
 import { LastOpened } from "./reducers/last_opened";
 import { Notifications } from "./reducers/notifications";
+import { SectionToggle } from "./reducers/section_toggle";
 
 export type State = {
     config: Core.RevoltNodeConfiguration,
@@ -28,6 +29,7 @@ export type State = {
     experiments: ExperimentOptions;
     lastOpened: LastOpened;
     notifications: Notifications;
+    sectionToggle: SectionToggle;
 };
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -56,7 +58,8 @@ store.subscribe(() => {
         sync,
         experiments,
         lastOpened,
-        notifications
+        notifications,
+        sectionToggle
     } = store.getState() as State;
 
     localForage.setItem("state", {
@@ -70,6 +73,7 @@ store.subscribe(() => {
         sync,
         experiments,
         lastOpened,
-        notifications
+        notifications,
+        sectionToggle
     });
 });
diff --git a/src/redux/reducers/index.ts b/src/redux/reducers/index.ts
index fe47ccbdc3de006eed1f954b21004518c5e7516c..baf217dcf1dcc9c2252104e10f25309c5914b785 100644
--- a/src/redux/reducers/index.ts
+++ b/src/redux/reducers/index.ts
@@ -13,6 +13,7 @@ import { sync, SyncAction } from "./sync";
 import { experiments, ExperimentsAction } from "./experiments";
 import { lastOpened, LastOpenedAction } from "./last_opened";
 import { notifications, NotificationsAction } from "./notifications";
+import { sectionToggle, SectionToggleAction } from "./section_toggle";
 
 export default combineReducers({
     config,
@@ -26,7 +27,8 @@ export default combineReducers({
     sync,
     experiments,
     lastOpened,
-    notifications
+    notifications,
+    sectionToggle
 });
 
 export type Action =
@@ -42,6 +44,7 @@ export type Action =
     | ExperimentsAction
     | LastOpenedAction
     | NotificationsAction
+    | SectionToggleAction
     | { type: "__INIT"; state: State };
 
 export type WithDispatcher = { dispatcher: (action: Action) => void };
diff --git a/src/redux/reducers/section_toggle.ts b/src/redux/reducers/section_toggle.ts
new file mode 100644
index 0000000000000000000000000000000000000000..26b23dcac096f90985f5383bb7c64c18de4dfc49
--- /dev/null
+++ b/src/redux/reducers/section_toggle.ts
@@ -0,0 +1,37 @@
+export interface SectionToggle {
+    [key: string]: boolean
+}
+
+export type SectionToggleAction =
+    | { type: undefined }
+    | {
+        type: "SECTION_TOGGLE_SET";
+        id: string;
+        state: boolean;
+      }
+    | {
+        type: "SECTION_TOGGLE_UNSET";
+        id: string;
+      }
+    | {
+          type: "RESET";
+      };
+
+export function sectionToggle(state = {} as SectionToggle, action: SectionToggleAction): SectionToggle {
+    switch (action.type) {
+        case "SECTION_TOGGLE_SET": {
+            return {
+                ...state,
+                [action.id]: action.state
+            }
+        }
+        case "SECTION_TOGGLE_UNSET": {
+            const { [action.id]: _, ...newState } = state;
+            return newState;
+        }
+        case "RESET":
+            return {};
+        default:
+            return state;
+    }
+}