diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3fb8fa0aeba02a50491d9dbcfc9a1c0723a43b06..fae5881f7419cc42d649821bc24cfcc1e480b741 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -16,7 +16,8 @@
 	"typescript.tsdk": "./node_modules/typescript/lib",
 	"cSpell.words": [
 		"autotranslate",
-    "fname",
+		"fname",
+		"Gazzodown",
 		"katex",
 		"listbox",
 		"livechat",
@@ -25,6 +26,6 @@
 		"photoswipe",
 		"searchbox",
 		"tmid",
-    "tshow"
+		"tshow"
 	]
 }
diff --git a/apps/meteor/app/ui-message/client/ActionManager.js b/apps/meteor/app/ui-message/client/ActionManager.js
index a3387693955b18e640ec6c3e66b560d7bfa47943..2b5c4926163f27be876c52520c4b85aaebf6d851 100644
--- a/apps/meteor/app/ui-message/client/ActionManager.js
+++ b/apps/meteor/app/ui-message/client/ActionManager.js
@@ -11,7 +11,7 @@ import { APIClient, t } from '../../utils/client';
 import * as banners from '../../../client/lib/banners';
 import { dispatchToastMessage } from '../../../client/lib/toast';
 import { imperativeModal } from '../../../client/lib/imperativeModal';
-import ConnectedModalBlock from '../../../client/views/blocks/ConnectedModalBlock';
+import UiKitModal from '../../../client/views/modal/uikit/UiKitModal';
 
 const events = new Emitter();
 
@@ -90,7 +90,7 @@ const handlePayloadUserInteraction = (type, { /* appId,*/ triggerId, ...data })
 
 	if ([UIKitInteractionTypes.MODAL_OPEN].includes(type)) {
 		const instance = imperativeModal.open({
-			component: ConnectedModalBlock,
+			component: UiKitModal,
 			props: {
 				triggerId,
 				viewId,
diff --git a/apps/meteor/client/components/message/content/UiKitSurface.tsx b/apps/meteor/client/components/message/content/UiKitSurface.tsx
index 48d49ca221705d8d01aa6b2cd39d671db49a9993..6dfc921bee8e56b2acdd358d77022af887f571cd 100644
--- a/apps/meteor/client/components/message/content/UiKitSurface.tsx
+++ b/apps/meteor/client/components/message/content/UiKitSurface.tsx
@@ -2,7 +2,7 @@ import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/
 import type { IMessage, IRoom } from '@rocket.chat/core-typings';
 import { MessageBlock } from '@rocket.chat/fuselage';
 import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
-import { UiKitComponent, UiKitMessage, kitContext, messageParser } from '@rocket.chat/fuselage-ui-kit';
+import { UiKitComponent, UiKitMessage, kitContext } from '@rocket.chat/fuselage-ui-kit';
 import type { MessageSurfaceLayout } from '@rocket.chat/ui-kit';
 import type { ContextType, ReactElement } from 'react';
 import React from 'react';
@@ -17,7 +17,6 @@ import {
 	useVideoConfSetPreferences,
 } from '../../../contexts/VideoConfContext';
 import { useVideoConfWarning } from '../../../views/room/contextualBar/VideoConference/hooks/useVideoConfWarning';
-import ParsedText from './uikit/ParsedText';
 
 let patched = false;
 const patchMessageParser = () => {
@@ -26,18 +25,6 @@ const patchMessageParser = () => {
 	}
 
 	patched = true;
-
-	// TODO: move this to fuselage-ui-kit itself
-	messageParser.text = ({ text, type }) => {
-		if (type !== 'mrkdwn') {
-			return <>{text}</>;
-		}
-
-		return <ParsedText text={text} />;
-	};
-
-	// TODO: move this to fuselage-ui-kit itself
-	messageParser.mrkdwn = ({ text }) => <ParsedText text={text} />;
 };
 
 type UiKitSurfaceProps = {
diff --git a/apps/meteor/client/components/message/content/uikit/ParsedText.tsx b/apps/meteor/client/components/message/content/uikit/ParsedText.tsx
deleted file mode 100644
index e14a586e11e42d7d074a9c6aa90ed35ea0e7b767..0000000000000000000000000000000000000000
--- a/apps/meteor/client/components/message/content/uikit/ParsedText.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { IRoom } from '@rocket.chat/core-typings';
-import type { Options } from '@rocket.chat/message-parser';
-import { parse } from '@rocket.chat/message-parser';
-import React, { memo, useMemo } from 'react';
-
-import GazzodownText from '../../../GazzodownText';
-
-type ParsedTextProps = {
-	text: string;
-	mentions?: {
-		type: 'user' | 'team';
-		_id: string;
-		username?: string;
-		name?: string;
-	}[];
-	channels?: Pick<IRoom, '_id' | 'name'>[];
-	searchText?: string;
-};
-
-const ParsedText = ({ text, mentions, channels, searchText }: ParsedTextProps) => {
-	const tokens = useMemo(() => {
-		if (!text) {
-			return undefined;
-		}
-
-		const parseOptions: Options = {
-			emoticons: true,
-		};
-
-		return parse(text, parseOptions);
-	}, [text]);
-
-	if (!tokens) {
-		return null;
-	}
-
-	return <GazzodownText tokens={tokens} mentions={mentions} channels={channels} searchText={searchText} />;
-};
-
-export default memo(ParsedText);
diff --git a/apps/meteor/client/lib/normalizeThreadMessage.tsx b/apps/meteor/client/lib/normalizeThreadMessage.tsx
index af8a3921c8ee5edaada2b8894902afb8f6224412..70be7cd49b2e62f34e6ae6e69aa19d8904081da0 100644
--- a/apps/meteor/client/lib/normalizeThreadMessage.tsx
+++ b/apps/meteor/client/lib/normalizeThreadMessage.tsx
@@ -1,15 +1,23 @@
 import type { IMessage } from '@rocket.chat/core-typings';
+import { parse } from '@rocket.chat/message-parser';
 import type { ReactElement } from 'react';
 import React from 'react';
 
 import { filterMarkdown } from '../../app/markdown/lib/markdown';
-import ParsedText from '../components/message/content/uikit/ParsedText';
+import GazzodownText from '../components/GazzodownText';
 
 export function normalizeThreadMessage({ ...message }: Readonly<Pick<IMessage, 'msg' | 'mentions' | 'attachments'>>): ReactElement | null {
 	if (message.msg) {
 		message.msg = filterMarkdown(message.msg);
 		delete message.mentions;
-		return <ParsedText text={message.msg} />;
+
+		const tokens = message.msg ? parse(message.msg, { emoticons: true }) : undefined;
+
+		if (!tokens) {
+			return null;
+		}
+
+		return <GazzodownText tokens={tokens} />;
 	}
 
 	if (message.attachments) {
diff --git a/apps/meteor/client/views/blocks/ConnectedModalBlock.js b/apps/meteor/client/views/blocks/ConnectedModalBlock.js
deleted file mode 100644
index 03c4f4b786ba9f3496788aae78102048c2826f3e..0000000000000000000000000000000000000000
--- a/apps/meteor/client/views/blocks/ConnectedModalBlock.js
+++ /dev/null
@@ -1,196 +0,0 @@
-import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/definition/uikit/UIKitIncomingInteractionContainer';
-import { useDebouncedCallback, useMutableCallback } from '@rocket.chat/fuselage-hooks';
-import { kitContext } from '@rocket.chat/fuselage-ui-kit';
-import React, { useEffect, useReducer, useState } from 'react';
-
-import * as ActionManager from '../../../app/ui-message/client/ActionManager';
-import ModalBlock from './ModalBlock';
-import './textParsers';
-
-const useActionManagerState = (initialState) => {
-	const [state, setState] = useState(initialState);
-
-	const { viewId } = state;
-
-	useEffect(() => {
-		const handleUpdate = ({ type, ...data }) => {
-			if (type === 'errors') {
-				const { errors } = data;
-				setState((state) => ({ ...state, errors }));
-				return;
-			}
-
-			setState(data);
-		};
-
-		ActionManager.on(viewId, handleUpdate);
-
-		return () => {
-			ActionManager.off(viewId, handleUpdate);
-		};
-	}, [viewId]);
-
-	return state;
-};
-
-const useValues = (view) => {
-	const reducer = useMutableCallback((values, { actionId, payload }) => ({
-		...values,
-		[actionId]: payload,
-	}));
-
-	const initializer = useMutableCallback(() => {
-		const filterInputFields = ({ element, elements = [] }) => {
-			if (element && element.initialValue) {
-				return true;
-			}
-
-			if (elements.length && elements.map((element) => ({ element })).filter(filterInputFields).length) {
-				return true;
-			}
-		};
-
-		const mapElementToState = ({ element, blockId, elements = [] }) => {
-			if (elements.length) {
-				return elements
-					.map((element) => ({ element, blockId }))
-					.filter(filterInputFields)
-					.map(mapElementToState);
-			}
-			return [element.actionId, { value: element.initialValue, blockId }];
-		};
-
-		return view.blocks
-			.filter(filterInputFields)
-			.map(mapElementToState)
-			.reduce((obj, el) => {
-				if (Array.isArray(el[0])) {
-					return { ...obj, ...Object.fromEntries(el) };
-				}
-
-				const [key, value] = el;
-				return { ...obj, [key]: value };
-			}, {});
-	});
-
-	return useReducer(reducer, null, initializer);
-};
-
-function ConnectedModalBlock(props) {
-	const state = useActionManagerState(props);
-
-	const { appId, viewId, mid: _mid, errors, view } = state;
-
-	const [values, updateValues] = useValues(view);
-
-	const groupStateByBlockId = (obj) =>
-		Object.entries(obj).reduce((obj, [key, { blockId, value }]) => {
-			obj[blockId] = obj[blockId] || {};
-			obj[blockId][key] = value;
-			return obj;
-		}, {});
-
-	const prevent = (e) => {
-		if (e) {
-			(e.nativeEvent || e).stopImmediatePropagation();
-			e.stopPropagation();
-			e.preventDefault();
-		}
-	};
-
-	const debouncedBlockAction = useDebouncedCallback((actionId, appId, value, blockId, mid) => {
-		ActionManager.triggerBlockAction({
-			container: {
-				type: UIKitIncomingInteractionContainerType.VIEW,
-				id: viewId,
-			},
-			actionId,
-			appId,
-			value,
-			blockId,
-			mid,
-		});
-	}, 700);
-
-	const context = {
-		action: ({ actionId, appId, value, blockId, mid = _mid, dispatchActionConfig }) => {
-			if (Array.isArray(dispatchActionConfig) && dispatchActionConfig.includes('on_character_entered')) {
-				debouncedBlockAction(actionId, appId, value, blockId, mid);
-			} else {
-				ActionManager.triggerBlockAction({
-					container: {
-						type: UIKitIncomingInteractionContainerType.VIEW,
-						id: viewId,
-					},
-					actionId,
-					appId,
-					value,
-					blockId,
-					mid,
-				});
-			}
-		},
-
-		state: ({ actionId, value, /* ,appId, */ blockId = 'default' }) => {
-			updateValues({
-				actionId,
-				payload: {
-					blockId,
-					value,
-				},
-			});
-		},
-		...state,
-		values,
-	};
-
-	const handleSubmit = useMutableCallback((e) => {
-		prevent(e);
-		ActionManager.triggerSubmitView({
-			viewId,
-			appId,
-			payload: {
-				view: {
-					...view,
-					id: viewId,
-					state: groupStateByBlockId(values),
-				},
-			},
-		});
-	});
-
-	const handleCancel = useMutableCallback((e) => {
-		prevent(e);
-		return ActionManager.triggerCancel({
-			appId,
-			viewId,
-			view: {
-				...view,
-				id: viewId,
-				state: groupStateByBlockId(values),
-			},
-		});
-	});
-
-	const handleClose = useMutableCallback((e) => {
-		prevent(e);
-		return ActionManager.triggerCancel({
-			appId,
-			viewId,
-			view: {
-				...view,
-				id: viewId,
-				state: groupStateByBlockId(values),
-			},
-			isCleared: true,
-		});
-	});
-
-	return (
-		<kitContext.Provider value={context}>
-			<ModalBlock view={view} errors={errors} appId={appId} onSubmit={handleSubmit} onCancel={handleCancel} onClose={handleClose} />
-		</kitContext.Provider>
-	);
-}
-
-export default ConnectedModalBlock;
diff --git a/apps/meteor/client/views/blocks/textParsers.js b/apps/meteor/client/views/blocks/textParsers.js
deleted file mode 100644
index f75f076759956aba67f97d291537d5b2a0149f4a..0000000000000000000000000000000000000000
--- a/apps/meteor/client/views/blocks/textParsers.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { modalParser } from '@rocket.chat/fuselage-ui-kit';
-import React from 'react';
-
-import ParsedText from '../../components/message/content/uikit/ParsedText';
-
-// TODO: move this to fuselage-ui-kit itself
-modalParser.plainText = ({ text } = {}) => text;
-
-// TODO: move this to fuselage-ui-kit itself
-modalParser.mrkdwn = ({ text }) => <ParsedText text={text} />;
diff --git a/apps/meteor/client/views/blocks/ModalBlock.js b/apps/meteor/client/views/modal/uikit/ModalBlock.tsx
similarity index 55%
rename from apps/meteor/client/views/blocks/ModalBlock.js
rename to apps/meteor/client/views/modal/uikit/ModalBlock.tsx
index 9c1cf0a5cc93698528c79825b57bbcdb7f4c224c..fa34cb11ad2a2f5d7a6cf0ac4486933ddac93d5e 100644
--- a/apps/meteor/client/views/blocks/ModalBlock.js
+++ b/apps/meteor/client/views/modal/uikit/ModalBlock.tsx
@@ -1,12 +1,14 @@
+import type { IUIKitSurface } from '@rocket.chat/apps-engine/definition/uikit';
 import { Modal, AnimatedVisibility, Button, Box } from '@rocket.chat/fuselage';
 import { useUniqueId } from '@rocket.chat/fuselage-hooks';
 import { UiKitComponent, UiKitModal, modalParser } from '@rocket.chat/fuselage-ui-kit';
+import type { LayoutBlock } from '@rocket.chat/ui-kit';
+import type { FormEventHandler, ReactElement } from 'react';
 import React, { useCallback, useEffect, useMemo, useRef } from 'react';
 import { FocusScope } from 'react-aria';
 
-import { getURL } from '../../../app/utils/lib/getURL';
+import { getURL } from '../../../../app/utils/lib/getURL';
 import { getButtonStyle } from './getButtonStyle';
-import './textParsers';
 
 const focusableElementsString = `
 	a[href]:not([tabindex="-1"]),
@@ -34,79 +36,101 @@ const focusableElementsStringInvalid = `
 	[tabindex]:not([tabindex="-1"]):invalid,
 	[contenteditable]:invalid`;
 
-function ModalBlock({ view, errors, appId, onSubmit, onClose, onCancel }) {
+type ModalBlockParams = {
+	view: IUIKitSurface & { showIcon?: boolean };
+	errors: any;
+	appId: string;
+	onSubmit: FormEventHandler<HTMLElement>;
+	onClose: () => void;
+	onCancel: FormEventHandler<HTMLElement>;
+};
+
+const isFocusable = (element: Element | null): element is HTMLElement =>
+	element !== null && 'focus' in element && typeof element.focus === 'function';
+
+const KeyboardCode = new Map<string, number>([
+	['ENTER', 13],
+	['ESC', 27],
+	['TAB', 9],
+]);
+
+const ModalBlock = ({ view, errors, appId, onSubmit, onClose, onCancel }: ModalBlockParams): ReactElement => {
 	const id = `modal_id_${useUniqueId()}`;
-	const ref = useRef();
+	const ref = useRef<HTMLElement>(null);
 
-	// Auto focus
 	useEffect(() => {
 		if (!ref.current) {
 			return;
 		}
 
 		if (errors && Object.keys(errors).length) {
-			const element = ref.current.querySelector(focusableElementsStringInvalid);
-			element && element.focus();
+			const element = ref.current.querySelector<HTMLElement>(focusableElementsStringInvalid);
+			element?.focus();
 		} else {
-			const element = ref.current.querySelector(focusableElementsString);
-			element && element.focus();
+			const element = ref.current.querySelector<HTMLElement>(focusableElementsString);
+			element?.focus();
 		}
 	}, [errors]);
-	// save focus to restore after close
+
 	const previousFocus = useMemo(() => document.activeElement, []);
-	// restore the focus after the component unmount
-	useEffect(() => () => previousFocus && previousFocus.focus(), [previousFocus]);
-	// Handle Tab, Shift + Tab, Enter and Escape
-	const handleKeyDown = useCallback(
-		(event) => {
-			if (event.keyCode === 13) {
-				// ENTER
-				if (event?.target?.nodeName !== 'TEXTAREA') {
-					return onSubmit(event);
-				}
-			}
 
-			if (event.keyCode === 27) {
-				// ESC
-				event.stopPropagation();
-				event.preventDefault();
-				onClose();
-				return false;
+	useEffect(
+		() => () => {
+			if (previousFocus && isFocusable(previousFocus)) {
+				return previousFocus.focus();
 			}
+		},
+		[previousFocus],
+	);
 
-			if (event.keyCode === 9) {
-				// TAB
-				const elements = Array.from(ref.current.querySelectorAll(focusableElementsString));
-				const [first] = elements;
-				const last = elements.pop();
+	const handleKeyDown = useCallback(
+		(event) => {
+			switch (event.keyCode) {
+				case KeyboardCode.get('ENTER'):
+					if (event?.target?.nodeName !== 'TEXTAREA') {
+						return onSubmit(event);
+					}
+					return;
+				case KeyboardCode.get('ESC'):
+					event.stopPropagation();
+					event.preventDefault();
+					onClose();
+					return;
+				case KeyboardCode.get('TAB'):
+					if (!ref.current) {
+						return;
+					}
+					const elements = Array.from(ref.current.querySelectorAll(focusableElementsString)) as HTMLElement[];
+					const [first] = elements;
+					const last = elements.pop();
 
-				if (!ref.current.contains(document.activeElement)) {
-					return first.focus();
-				}
+					if (!ref.current.contains(document.activeElement)) {
+						return first.focus();
+					}
 
-				if (event.shiftKey) {
-					if (!first || first === document.activeElement) {
-						last.focus();
+					if (event.shiftKey) {
+						if (!first || first === document.activeElement) {
+							last?.focus();
+							event.stopPropagation();
+							event.preventDefault();
+						}
+						return;
+					}
+
+					if (!last || last === document.activeElement) {
+						first.focus();
 						event.stopPropagation();
 						event.preventDefault();
 					}
-					return;
-				}
-
-				if (!last || last === document.activeElement) {
-					first.focus();
-					event.stopPropagation();
-					event.preventDefault();
-				}
 			}
 		},
 		[onClose, onSubmit],
 	);
-	// Clean the events
+
 	useEffect(() => {
-		const element = document.querySelector('#modal-root');
-		const container = element.querySelector('.rcx-modal__content');
-		const close = (e) => {
+		const element = document.querySelector('#modal-root') as HTMLElement;
+		const container = element.querySelector('.rcx-modal__content') as HTMLElement;
+		const close = (e: Event) => {
 			if (e.target !== element) {
 				return;
 			}
@@ -116,17 +140,21 @@ function ModalBlock({ view, errors, appId, onSubmit, onClose, onCancel }) {
 			return false;
 		};
 
-		const ignoreIfnotContains = (e) => {
-			if (!container.contains(e.target)) {
+		const ignoreIfNotContains = (e: Event) => {
+			if (e.target !== element) {
+				return;
+			}
+
+			if (!container.contains(e.target as HTMLElement)) {
 				return;
 			}
 			return handleKeyDown(e);
 		};
 
-		document.addEventListener('keydown', ignoreIfnotContains);
+		document.addEventListener('keydown', ignoreIfNotContains);
 		element.addEventListener('click', close);
 		return () => {
-			document.removeEventListener('keydown', ignoreIfnotContains);
+			document.removeEventListener('keydown', ignoreIfNotContains);
 			element.removeEventListener('click', close);
 		};
 	}, [handleKeyDown, onClose]);
@@ -142,7 +170,7 @@ function ModalBlock({ view, errors, appId, onSubmit, onClose, onCancel }) {
 					</Modal.Header>
 					<Modal.Content>
 						<Box is='form' method='post' action='#' onSubmit={onSubmit}>
-							<UiKitComponent render={UiKitModal} blocks={view.blocks} />
+							<UiKitComponent render={UiKitModal} blocks={view.blocks as LayoutBlock[]} />
 						</Box>
 					</Modal.Content>
 					<Modal.Footer>
@@ -163,7 +191,6 @@ function ModalBlock({ view, errors, appId, onSubmit, onClose, onCancel }) {
 			</FocusScope>
 		</AnimatedVisibility>
 	);
-}
+};
 
 export default ModalBlock;
-export { modalParser };
diff --git a/apps/meteor/client/views/modal/uikit/UiKitModal.tsx b/apps/meteor/client/views/modal/uikit/UiKitModal.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6bdefce4ff9e345e85a02e67c6f79e800ff1fc07
--- /dev/null
+++ b/apps/meteor/client/views/modal/uikit/UiKitModal.tsx
@@ -0,0 +1,141 @@
+import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/definition/uikit/UIKitIncomingInteractionContainer';
+import { useDebouncedCallback, useMutableCallback } from '@rocket.chat/fuselage-hooks';
+import { kitContext } from '@rocket.chat/fuselage-ui-kit';
+import { MarkupInteractionContext } from '@rocket.chat/gazzodown';
+import type { LayoutBlock } from '@rocket.chat/ui-kit';
+import type { ContextType, ReactElement, ReactEventHandler } from 'react';
+import React from 'react';
+
+import * as ActionManager from '../../../../app/ui-message/client/ActionManager';
+import { detectEmoji } from '../../../lib/utils/detectEmoji';
+import ModalBlock from './ModalBlock';
+import type { ActionManagerState } from './hooks/useActionManagerState';
+import { useActionManagerState } from './hooks/useActionManagerState';
+import { useValues } from './hooks/useValues';
+
+const UiKitModal = (props: ActionManagerState): ReactElement => {
+	const state = useActionManagerState(props);
+
+	const { appId, viewId, mid: _mid, errors, view } = state;
+
+	const [values, updateValues] = useValues(view.blocks as LayoutBlock[]);
+
+	const groupStateByBlockId = (values: { value: unknown; blockId: string }[]) =>
+		Object.entries(values).reduce<any>((obj, [key, { blockId, value }]) => {
+			obj[blockId] = obj[blockId] || {};
+			obj[blockId][key] = value;
+
+			return obj;
+		}, {});
+
+	const prevent: ReactEventHandler = (e) => {
+		if (e) {
+			(e.nativeEvent || e).stopImmediatePropagation();
+			e.stopPropagation();
+			e.preventDefault();
+		}
+	};
+
+	const debouncedBlockAction = useDebouncedCallback((actionId, appId, value, blockId, mid) => {
+		ActionManager.triggerBlockAction({
+			container: {
+				type: UIKitIncomingInteractionContainerType.VIEW,
+				id: viewId,
+			},
+			actionId,
+			appId,
+			value,
+			blockId,
+			mid,
+		});
+	}, 700);
+
+	// TODO: this structure is atrociously wrong; we should revisit this
+	const context: ContextType<typeof kitContext> = {
+		// @ts-expect-error Property 'mid' does not exist on type 'ActionParams'.
+		action: ({ actionId, appId, value, blockId, mid = _mid, dispatchActionConfig }) => {
+			if (Array.isArray(dispatchActionConfig) && dispatchActionConfig.includes('on_character_entered')) {
+				debouncedBlockAction(actionId, appId, value, blockId, mid);
+			} else {
+				ActionManager.triggerBlockAction({
+					container: {
+						type: UIKitIncomingInteractionContainerType.VIEW,
+						id: viewId,
+					},
+					actionId,
+					appId,
+					value,
+					blockId,
+					mid,
+				});
+			}
+		},
+
+		state: ({ actionId, value, /* ,appId, */ blockId = 'default' }) => {
+			updateValues({
+				actionId,
+				payload: {
+					blockId,
+					value,
+				},
+			});
+		},
+		...state,
+		values,
+	};
+
+	const handleSubmit = useMutableCallback((e) => {
+		prevent(e);
+		ActionManager.triggerSubmitView({
+			viewId,
+			appId,
+			payload: {
+				view: {
+					...view,
+					id: viewId,
+					state: groupStateByBlockId(values),
+				},
+			},
+		});
+	});
+
+	const handleCancel = useMutableCallback((e) => {
+		prevent(e);
+		ActionManager.triggerCancel({
+			viewId,
+			appId,
+			view: {
+				...view,
+				id: viewId,
+				state: groupStateByBlockId(values),
+			},
+		});
+	});
+
+	const handleClose = useMutableCallback(() => {
+		ActionManager.triggerCancel({
+			viewId,
+			appId,
+			view: {
+				...view,
+				id: viewId,
+				state: groupStateByBlockId(values),
+			},
+			isCleared: true,
+		});
+	});
+
+	return (
+		<kitContext.Provider value={context}>
+			<MarkupInteractionContext.Provider
+				value={{
+					detectEmoji,
+				}}
+			>
+				<ModalBlock view={view} errors={errors} appId={appId} onSubmit={handleSubmit} onCancel={handleCancel} onClose={handleClose} />
+			</MarkupInteractionContext.Provider>
+		</kitContext.Provider>
+	);
+};
+
+export default UiKitModal;
diff --git a/apps/meteor/client/views/blocks/getButtonStyle.ts b/apps/meteor/client/views/modal/uikit/getButtonStyle.ts
similarity index 88%
rename from apps/meteor/client/views/blocks/getButtonStyle.ts
rename to apps/meteor/client/views/modal/uikit/getButtonStyle.ts
index ce98fda0305002a94e8d182073284d3ed793caf4..4a78cb5e250ab12278c9a3f2473936d46a31a671 100644
--- a/apps/meteor/client/views/blocks/getButtonStyle.ts
+++ b/apps/meteor/client/views/modal/uikit/getButtonStyle.ts
@@ -1,5 +1,6 @@
 import type { IUIKitSurface } from '@rocket.chat/apps-engine/definition/uikit';
 
+// TODO: Move to fuselage-ui-kit
 export const getButtonStyle = (view: IUIKitSurface): { danger: boolean } | { primary: boolean } => {
 	return view.submit?.style === 'danger' ? { danger: true } : { primary: true };
 };
diff --git a/apps/meteor/client/views/modal/uikit/hooks/useActionManagerState.ts b/apps/meteor/client/views/modal/uikit/hooks/useActionManagerState.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8f6ee7bb26d2bcaac06a9609e0c17c770c24c4fb
--- /dev/null
+++ b/apps/meteor/client/views/modal/uikit/hooks/useActionManagerState.ts
@@ -0,0 +1,38 @@
+import type { IUIKitSurface } from '@rocket.chat/apps-engine/definition/uikit';
+import { useEffect, useState } from 'react';
+
+import * as ActionManager from '../../../../../app/ui-message/client/ActionManager';
+
+export type ActionManagerState = {
+	viewId: string;
+	type: 'errors' | string;
+	appId: string;
+	mid: string;
+	errors: Record<string, string>;
+	view: IUIKitSurface;
+};
+
+export const useActionManagerState = (initialState: ActionManagerState) => {
+	const [state, setState] = useState(initialState);
+
+	const { viewId } = state;
+
+	useEffect(() => {
+		const handleUpdate = ({ type, errors, ...data }: ActionManagerState) => {
+			if (type === 'errors') {
+				setState((state) => ({ ...state, errors, type }));
+				return;
+			}
+
+			setState({ ...data, type, errors });
+		};
+
+		ActionManager.on(viewId, handleUpdate);
+
+		return () => {
+			ActionManager.off(viewId, handleUpdate);
+		};
+	}, [viewId]);
+
+	return state;
+};
diff --git a/apps/meteor/client/views/modal/uikit/hooks/useValues.ts b/apps/meteor/client/views/modal/uikit/hooks/useValues.ts
new file mode 100644
index 0000000000000000000000000000000000000000..34a8eb0c5ae2d5fda977d8e918720fe8adbcf7da
--- /dev/null
+++ b/apps/meteor/client/views/modal/uikit/hooks/useValues.ts
@@ -0,0 +1,48 @@
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+import type { LayoutBlock } from '@rocket.chat/ui-kit';
+import { useReducer } from 'react';
+
+type LayoutBlockWithElement = Extract<LayoutBlock, { element: unknown }>;
+type LayoutBlockWithElements = Extract<LayoutBlock, { elements: readonly unknown[] }>;
+type ElementFromLayoutBlock = LayoutBlockWithElement['element'] | LayoutBlockWithElements['elements'][number];
+
+const hasElementInBlock = (block: LayoutBlock): block is LayoutBlockWithElement => 'element' in block;
+const hasElementsInBlock = (block: LayoutBlock): block is LayoutBlockWithElements => 'elements' in block;
+const hasInitialValueAndActionId = (
+	element: ElementFromLayoutBlock,
+): element is Extract<ElementFromLayoutBlock, { actionId: string }> & { initialValue: unknown } =>
+	'initialValue' in element && 'actionId' in element && typeof element.actionId === 'string' && !!element?.initialValue;
+
+const extractValue = (element: ElementFromLayoutBlock, obj: Record<string, { value: unknown; blockId?: string }>, blockId?: string) => {
+	if (hasInitialValueAndActionId(element)) {
+		obj[element.actionId] = { value: element.initialValue, blockId };
+	}
+};
+
+const reduceBlocks = (obj: Record<string, { value: unknown; blockId?: string }>, block: LayoutBlock) => {
+	if (hasElementInBlock(block)) {
+		extractValue(block.element, obj, block.blockId);
+	}
+	if (hasElementsInBlock(block)) {
+		for (const element of block.elements) {
+			extractValue(element, obj, block.blockId);
+		}
+	}
+
+	return obj;
+};
+
+export const useValues = (blocks: LayoutBlock[]) => {
+	const reducer = useMutableCallback((values, { actionId, payload }) => ({
+		...values,
+		[actionId]: payload,
+	}));
+
+	const initializer = useMutableCallback((blocks: LayoutBlock[]) => {
+		const obj: Record<string, { value: unknown; blockId?: string }> = {};
+
+		return blocks.reduce(reduceBlocks, obj);
+	});
+
+	return useReducer(reducer, blocks, initializer);
+};
diff --git a/apps/meteor/client/views/room/contextualBar/Apps/Apps.tsx b/apps/meteor/client/views/room/contextualBar/Apps/Apps.tsx
index bf1fcc37c4cbd1a15f1ac73c7a6e6fe628bf6cf1..01bbcda2419173356203dbe161ceb3fe6632209e 100644
--- a/apps/meteor/client/views/room/contextualBar/Apps/Apps.tsx
+++ b/apps/meteor/client/views/room/contextualBar/Apps/Apps.tsx
@@ -1,13 +1,12 @@
 import type { IUIKitSurface } from '@rocket.chat/apps-engine/definition/uikit';
 import { ButtonGroup, Button, Box, Avatar } from '@rocket.chat/fuselage';
-import { UiKitComponent, UiKitModal } from '@rocket.chat/fuselage-ui-kit';
+import { UiKitComponent, UiKitModal, modalParser } from '@rocket.chat/fuselage-ui-kit';
 import type { LayoutBlock } from '@rocket.chat/ui-kit';
 import React from 'react';
 
 import { getURL } from '../../../../../app/utils/lib/getURL';
 import VerticalBar from '../../../../components/VerticalBar';
-import { modalParser } from '../../../blocks/ModalBlock';
-import { getButtonStyle } from '../../../blocks/getButtonStyle';
+import { getButtonStyle } from '../../../modal/uikit/getButtonStyle';
 
 type AppsProps = {
 	view: IUIKitSurface;
diff --git a/packages/fuselage-ui-kit/src/surfaces/FuselageModalSurfaceRenderer.tsx b/packages/fuselage-ui-kit/src/surfaces/FuselageModalSurfaceRenderer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4236d0b625c5aab24d9d234c45ec673adbe23663
--- /dev/null
+++ b/packages/fuselage-ui-kit/src/surfaces/FuselageModalSurfaceRenderer.tsx
@@ -0,0 +1,34 @@
+import { Markup } from '@rocket.chat/gazzodown';
+import type { Markdown, PlainText, TextObject } from '@rocket.chat/ui-kit';
+import { parse } from '@rocket.chat/message-parser';
+import type { JSXElementConstructor, ReactElement } from 'react';
+import { Fragment } from 'react';
+
+import type { FuselageSurfaceRendererProps } from './FuselageSurfaceRenderer';
+import { FuselageSurfaceRenderer } from './FuselageSurfaceRenderer';
+
+export class FuselageModalSurfaceRenderer extends FuselageSurfaceRenderer {
+  public constructor(allowedBlocks?: FuselageSurfaceRendererProps) {
+    super(allowedBlocks);
+  }
+
+  public plainText = ({ text = '' }: PlainText) => <Fragment>{text}</Fragment>;
+
+  public text({
+    text,
+    type,
+  }: TextObject): ReactElement<
+    any,
+    string | JSXElementConstructor<any>
+  > | null {
+    if (type !== 'mrkdwn') {
+      return this.plainText({ text, type });
+    }
+
+    return this.mrkdwn({ text, type });
+  }
+
+  public mrkdwn({ text = '' }: Markdown): ReactElement | null {
+    return text ? <Markup tokens={parse(text, { emoticons: false })} /> : null;
+  }
+}
diff --git a/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx b/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx
index 9760d8ae55c8a32d87b6a3de60a9d74bd1e40d13..8a5109972731ec7fcd963a83737082632b94151a 100644
--- a/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx
+++ b/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx
@@ -18,7 +18,7 @@ import OverflowElement from '../elements/OverflowElement';
 import PlainTextInputElement from '../elements/PlainTextInputElement';
 import StaticSelectElement from '../elements/StaticSelectElement';
 
-type FuselageSurfaceRendererProps = ConstructorParameters<
+export type FuselageSurfaceRendererProps = ConstructorParameters<
   typeof UiKit.SurfaceRenderer
 >[0];
 
diff --git a/packages/fuselage-ui-kit/src/surfaces/index.ts b/packages/fuselage-ui-kit/src/surfaces/index.ts
index 49eff1a923a5c34375fb3a0a8d41eecb1ec815f0..25114be862d0f5fa99786bfdc251bb9695bc060d 100644
--- a/packages/fuselage-ui-kit/src/surfaces/index.ts
+++ b/packages/fuselage-ui-kit/src/surfaces/index.ts
@@ -4,11 +4,12 @@ import MessageSurface from './MessageSurface';
 import ModalSurface from './ModalSurface';
 import { createSurfaceRenderer } from './createSurfaceRenderer';
 import { FuselageMessageSurfaceRenderer } from './MessageSurfaceRenderer';
+import { FuselageModalSurfaceRenderer } from './FuselageModalSurfaceRenderer';
 
 // export const attachmentParser = new FuselageSurfaceRenderer();
 export const bannerParser = new FuselageSurfaceRenderer();
 export const messageParser = new FuselageMessageSurfaceRenderer();
-export const modalParser = new FuselageSurfaceRenderer();
+export const modalParser = new FuselageModalSurfaceRenderer();
 
 // export const UiKitAttachment = createSurfaceRenderer(AttachmentSurface, attachmentParser);
 export const UiKitBanner = createSurfaceRenderer(BannerSurface, bannerParser);