diff --git a/external/lang b/external/lang
index f3d13c09b6fa2f28f027ce32643caffadbb63cf1..5e57b0f203f1c03c2942222b967288257c218a4e 160000
--- a/external/lang
+++ b/external/lang
@@ -1 +1 @@
-Subproject commit f3d13c09b6fa2f28f027ce32643caffadbb63cf1
+Subproject commit 5e57b0f203f1c03c2942222b967288257c218a4e
diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx
index 92d6c3622b7c2880ba87dd89a535652c3b108deb..781c4e9508ec614a4f531280a72d62e3e363eb24 100644
--- a/src/components/common/messaging/Message.tsx
+++ b/src/components/common/messaging/Message.tsx
@@ -13,6 +13,7 @@ import Overline from "../../ui/Overline";
 import { useContext } from "preact/hooks";
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
 import { memo } from "preact/compat";
+import { MessageReply } from "./attachments/MessageReply";
 
 interface Props {
     attachContext?: boolean
@@ -30,33 +31,36 @@ function Message({ attachContext, message, contrast, content: replacement, head:
     const client = useContext(AppContext);
 
     const content = message.content as string;
-    const head = (message.replies && message.replies.length > 0) || preferHead;
+    const head = preferHead || (message.replies && message.replies.length > 0);
     return (
-        <MessageBase id={message._id}
-            head={head}
-            contrast={contrast}
-            sending={typeof queued !== 'undefined'}
-            mention={message.mentions?.includes(client.user!._id)}
-            failed={typeof queued?.error !== 'undefined'}
-            onContextMenu={attachContext ? attachContextMenu('Menu', { message, contextualChannel: message.channel, queued }) : undefined}>
-            <MessageInfo>
-                { head ?
-                    <UserIcon target={user} size={36} /> :
-                    <MessageDetail message={message} position="left" /> }
-            </MessageInfo>
-            <MessageContent>
-                { head && <span className="author">
-                    <Username user={user} />
-                    <MessageDetail message={message} position="top" />
-                </span> }
-                { replacement ?? <Markdown content={content} /> }
-                { queued?.error && <Overline type="error" error={queued.error} /> }
-                { message.attachments?.map((attachment, index) =>
-                    <Attachment key={index} attachment={attachment} hasContent={ index > 0 || content.length > 0 } />) }
-                { message.embeds?.map((embed, index) =>
-                    <Embed key={index} embed={embed} />) }
-            </MessageContent>
-        </MessageBase>
+        <>
+            { message.replies?.map((message_id, index) => <MessageReply index={index} id={message_id} channel={message.channel} />) }
+            <MessageBase id={message._id}
+                head={head && !message.replies}
+                contrast={contrast}
+                sending={typeof queued !== 'undefined'}
+                mention={message.mentions?.includes(client.user!._id)}
+                failed={typeof queued?.error !== 'undefined'}
+                onContextMenu={attachContext ? attachContextMenu('Menu', { message, contextualChannel: message.channel, queued }) : undefined}>
+                <MessageInfo>
+                    { head ?
+                        <UserIcon target={user} size={36} /> :
+                        <MessageDetail message={message} position="left" /> }
+                </MessageInfo>
+                <MessageContent>
+                    { head && <span className="author">
+                        <Username user={user} />
+                        <MessageDetail message={message} position="top" />
+                    </span> }
+                    { replacement ?? <Markdown content={content} /> }
+                    { queued?.error && <Overline type="error" error={queued.error} /> }
+                    { message.attachments?.map((attachment, index) =>
+                        <Attachment key={index} attachment={attachment} hasContent={ index > 0 || content.length > 0 } />) }
+                    { message.embeds?.map((embed, index) =>
+                        <Embed key={index} embed={embed} />) }
+                </MessageContent>
+            </MessageBase>
+        </>
     )
 }
 
diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index f678a2770808103c5fd49a80503c74decb670c78..a3c98c1e67c5153ccde0d5b3834fdacb24f9c599 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -4,8 +4,10 @@ import styled from "styled-components";
 import { defer } from "../../../lib/defer";
 import IconButton from "../../ui/IconButton";
 import { Send } from '@styled-icons/feather';
+import { debounce } from "../../../lib/debounce";
 import Axios, { CancelTokenSource } from "axios";
 import { useTranslation } from "../../../lib/i18n";
+import { Reply } from "../../../redux/reducers/queue";
 import { connectState } from "../../../redux/connector";
 import { WithDispatcher } from "../../../redux/reducers";
 import { takeError } from "../../../context/revoltjs/util";
@@ -18,8 +20,8 @@ import { useIntermediate } from "../../../context/intermediate/Intermediate";
 import { FileUploader, grabFiles, uploadFile } from "../../../context/revoltjs/FileUploads";
 import { SingletonMessageRenderer, SMOOTH_SCROLL_ON_RECEIVE } from "../../../lib/renderer/Singleton";
 
+import ReplyBar from "./bars/ReplyBar";
 import FilePreview from './bars/FilePreview';
-import { debounce } from "../../../lib/debounce";
 import AutoComplete, { useAutoComplete } from "../AutoComplete";
 
 type Props = WithDispatcher & {
@@ -55,7 +57,8 @@ export const CAN_UPLOAD_AT_ONCE = 5;
 
 function MessageBox({ channel, draft, dispatcher }: Props) {
     const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' });
-    const [typing, setTyping] = useState<boolean | number>(false);
+    const [ typing, setTyping ] = useState<boolean | number>(false);
+    const [ replies, setReplies ] = useState<Reply[]>([]);
     const { openScreen } = useIntermediate();
     const client = useContext(AppContext);
     const translate = useTranslation();
@@ -104,6 +107,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
         
         stopTyping();
         setMessage();
+        setReplies([]);
 
         const nonce = ulid();
         dispatcher({
@@ -114,7 +118,9 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
                 _id: nonce,
                 channel: channel._id,
                 author: client.user!._id,
-                content
+                
+                content,
+                replies
             }
         });
 
@@ -123,7 +129,8 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
         try {
             await client.channels.sendMessage(channel._id, {
                 content,
-                nonce
+                nonce,
+                replies
             });
         } catch (error) {
             dispatcher({
@@ -186,7 +193,8 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
             await client.channels.sendMessage(channel._id, {
                 content,
                 nonce,
-                attachments // ! FIXME: temp, allow multiple uploads on server
+                replies,
+                attachments
             });
         } catch (err) {
             setUploadState({
@@ -199,6 +207,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
         }
 
         setMessage();
+        setReplies([]);
 
         if (files.length > CAN_UPLOAD_AT_ONCE) {
             setUploadState({
@@ -257,6 +266,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
                         setUploadState({ type: 'attached', files: uploadState.files.filter((_, i) => index !== i) });
                     }
                 }} />
+            <ReplyBar channel={channel._id} replies={replies} setReplies={setReplies} />
             <Base>
                 <Action>
                     <FileUploader
diff --git a/src/components/common/messaging/attachments/MessageReply.tsx b/src/components/common/messaging/attachments/MessageReply.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0d7c0691c9653f6da80097087e168d1dd760fb8b
--- /dev/null
+++ b/src/components/common/messaging/attachments/MessageReply.tsx
@@ -0,0 +1,65 @@
+import { Text } from "preact-i18n";
+import UserShort from "../../user/UserShort";
+import styled, { css } from "styled-components";
+import Markdown from "../../../markdown/Markdown";
+import { CornerUpRight } from "@styled-icons/feather";
+import { useUser } from "../../../../context/revoltjs/hooks";
+import { useRenderState } from "../../../../lib/renderer/Singleton";
+
+interface Props {
+    channel: string
+    index: number
+    id: string
+}
+
+export const ReplyBase = styled.div<{ head?: boolean, fail?: boolean, preview?: boolean }>`
+    gap: 4px;
+    display: flex;
+    font-size: 0.8em;
+    margin-left: 30px;
+    user-select: none;
+    margin-bottom: 4px;
+    align-items: center;
+    color: var(--secondary-foreground);
+
+    svg {
+        color: var(--tertiary-foreground);
+    }
+
+    ${ props => props.fail && css`
+        color: var(--tertiary-foreground);
+    ` }
+
+    ${ props => props.head && css`
+        margin-top: 12px;
+    ` }
+
+    ${ props => props.preview && css`
+        margin-left: 0;
+    ` }
+`;
+
+export function MessageReply({ index, channel, id }: Props) {
+    const view = useRenderState(channel);
+    if (view?.type !== 'RENDER') return null;
+
+    const message = view.messages.find(x => x._id === id);
+    if (!message) {
+        return (
+            <ReplyBase head={index === 0} fail>
+                <CornerUpRight size={16} />
+                <span><Text id="app.main.channel.misc.failed_load" /></span>
+            </ReplyBase>
+        )
+    }
+
+    const user = useUser(message.author);
+
+    return (
+        <ReplyBase head={index === 0}>
+            <CornerUpRight size={16} />
+            <UserShort user={user} size={16} />
+            <Markdown disallowBigEmoji content={(message.content as string).split('\n').shift()} />
+        </ReplyBase>
+    )
+}
diff --git a/src/components/common/messaging/bars/ReplyBar.tsx b/src/components/common/messaging/bars/ReplyBar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..33a2da4f1f9b70cdefa5c47a98fa2d609cc9fa33
--- /dev/null
+++ b/src/components/common/messaging/bars/ReplyBar.tsx
@@ -0,0 +1,88 @@
+import styled from "styled-components";
+import UserShort from "../../user/UserShort";
+import Markdown from "../../../markdown/Markdown";
+import { AtSign, CornerUpRight, XCircle } from "@styled-icons/feather";
+import { StateUpdater, useEffect } from "preact/hooks";
+import { ReplyBase } from "../attachments/MessageReply";
+import { Reply } from "../../../../redux/reducers/queue";
+import { useUsers } from "../../../../context/revoltjs/hooks";
+import { internalSubscribe } from "../../../../lib/eventEmitter";
+import { useRenderState } from "../../../../lib/renderer/Singleton";
+import IconButton from "../../../ui/IconButton";
+
+interface Props {
+    channel: string,
+    replies: Reply[],
+    setReplies: StateUpdater<Reply[]>
+}
+
+const Base = styled.div`
+    display: flex;
+    padding: 0 22px;
+    user-select: none;
+    align-items: center;
+    background: var(--message-box);
+
+    div {
+        flex-grow: 1;
+    }
+
+    .actions {
+        gap: 12px;
+        display: flex;
+    }
+    
+    .toggle {
+        gap: 4px;
+        display: flex;
+        font-size: 0.7em;
+        align-items: center;
+    }
+`;
+
+// ! FIXME: Move to global config
+const MAX_REPLIES = 5;
+export default function ReplyBar({ channel, replies, setReplies }: Props) {
+    useEffect(() => {
+        return internalSubscribe("ReplyBar", "add", id => replies.length < MAX_REPLIES && !replies.find(x => x.id === id) && setReplies([ ...replies, { id, mention: false } ]));
+    }, [ replies ]);
+
+    const view = useRenderState(channel);
+    if (view?.type !== 'RENDER') return null;
+
+    const ids = replies.map(x => x.id);
+    const messages = view.messages.filter(x => ids.includes(x._id));
+    const users = useUsers(messages.map(x => x.author));
+    
+    return (
+        <div>
+            { replies.map((reply, index) => {
+                let message = messages.find(x => reply.id === x._id);
+                if (!message) return;
+
+                let user = users.find(x => message!.author === x?._id);
+                if (!user) return;
+
+                return (
+                    <Base key={reply.id}>
+                        <ReplyBase preview>
+                            <CornerUpRight size={22} />
+                            <UserShort user={user} size={16} />
+                            <Markdown disallowBigEmoji content={(message.content as string).split('\n').shift()} />
+                        </ReplyBase>
+                        <span class="actions">
+                            <IconButton onClick={() => setReplies(replies.map((_, i) => i === index ? { ..._, mention: !_.mention } : _))}>
+                                <span class="toggle">
+                                    <AtSign size={16} /> { reply.mention ? 'ON' : 'OFF' }
+                                </span>
+                            </IconButton>
+                            <IconButton onClick={() => setReplies(replies.filter((_, i) => i !== index))}>
+                                <XCircle size={16} />
+                            </IconButton>
+                        </span>
+                    </Base>
+                )
+            }) }
+        </div>
+    )
+}
diff --git a/src/components/common/user/UserShort.tsx b/src/components/common/user/UserShort.tsx
index de16edf4dace6f8d2558713b96a359d2a330864b..0dda8d3c6ac7ea22df5e63318b082ae604563fe2 100644
--- a/src/components/common/user/UserShort.tsx
+++ b/src/components/common/user/UserShort.tsx
@@ -6,9 +6,9 @@ export function Username({ user }: { user?: User }) {
     return <b>{ user?.username ?? <Text id="app.main.channel.unknown_user" /> }</b>;
 }
 
-export default function UserShort({ user }: { user?: User }) {
+export default function UserShort({ user, size }: { user?: User, size?: number }) {
     return <>
-        <UserIcon size={24} target={user} />
+        <UserIcon size={size ?? 24} target={user} />
         <Username user={user} />
     </>;
 }
diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx
index d5c84676ef20aeff7c348cca5072ef859ec2dc2b..4867ee15f8e563f8ac11ffd52793016f523e6a78 100644
--- a/src/lib/ContextMenus.tsx
+++ b/src/lib/ContextMenus.tsx
@@ -39,6 +39,7 @@ type Action =
     | { action: "retry_message"; message: QueuedMessage }
     | { action: "cancel_message"; message: QueuedMessage }
     | { action: "mention"; user: string }
+    | { action: "reply_message"; id: string }
     | { action: "quote_message"; content: string }
     | { action: "edit_message"; id: string }
     | { action: "delete_message"; target: Channels.Message }
@@ -120,8 +121,9 @@ function ContextMenus(props: WithDispatcher) {
                             .sendMessage(
                                 data.message.channel,
                                 {
+                                    nonce: data.message.id,
                                     content: data.message.data.content as string,
-                                    nonce
+                                    replies: data.message.data.replies
                                 }
                             )
                             .catch(fail);
@@ -156,6 +158,17 @@ function ContextMenus(props: WithDispatcher) {
                 case "copy_text":
                     writeClipboard(data.content);
                     break;
+
+                case "reply_message":
+                    {
+                        internalEmit(
+                            "ReplyBar",
+                            "add",
+                            data.id
+                        );
+                    }
+                    break;
+
                 case "quote_message":
                     {
                         internalEmit(
@@ -471,10 +484,16 @@ function ContextMenus(props: WithDispatcher) {
                             typeof message.content === "string" &&
                             message.content.length > 0
                         ) {
+                            generateAction({
+                                action: "reply_message",
+                                id: message._id
+                            });
+
                             generateAction({
                                 action: "quote_message",
                                 content: message.content
                             });
+
                             generateAction({
                                 action: "copy_text",
                                 content: message.content
diff --git a/src/lib/eventEmitter.ts b/src/lib/eventEmitter.ts
index 5e76841c4ca2470a8417c214dadec98b1003558a..669ad37afef1cfc0405b9efbd654da43eb1ec808 100644
--- a/src/lib/eventEmitter.ts
+++ b/src/lib/eventEmitter.ts
@@ -19,3 +19,4 @@ export function internalEmit(ns: string, event: string, ...args: any[]) {
 // - Intermediate/navigate
 // - MessageBox/append
 // - TextArea/focus
+// - ReplyBar/add
diff --git a/src/redux/reducers/queue.ts b/src/redux/reducers/queue.ts
index abf78f9b5338e1def33464ec847383ae76ef76b1..3bcbec5812c6728931b5191fa3a259770bc6f227 100644
--- a/src/redux/reducers/queue.ts
+++ b/src/redux/reducers/queue.ts
@@ -5,10 +5,20 @@ export enum QueueStatus {
     ERRORED = "errored",
 }
 
+export interface Reply {
+    id: string,
+    mention: boolean
+}
+
+export type QueuedMessageData = Omit<MessageObject, 'content' | 'replies'> & {
+    content: string;
+    replies: Reply[];
+}
+
 export interface QueuedMessage {
     id: string;
     channel: string;
-    data: MessageObject;
+    data: QueuedMessageData;
     status: QueueStatus;
     error?: string;
 }
@@ -19,7 +29,7 @@ export type QueueAction =
           type: "QUEUE_ADD";
           nonce: string;
           channel: string;
-          message: MessageObject;
+          message: QueuedMessageData;
       }
     | {
           type: "QUEUE_FAIL";