diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 2ee1363b9ed8..040302efb0b5 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -930,6 +930,11 @@ const ROUTES = {
             `create/${iouType as string}/start/${transactionID}/${reportID}/per-diem/${backToReport ?? ''}` as const,
     },
 
+    MONEY_REQUEST_RECEIPT_VIEW_MODAL: {
+        route: 'receipt-view-modal/:transactionID',
+        getRoute: (transactionID: string, backTo: string) => getUrlWithBackToParam(`receipt-view-modal/${transactionID}`, backTo),
+    } as const,
+
     MONEY_REQUEST_STATE_SELECTOR: {
         route: 'submit/state',
 
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 703c0333709d..926e5490ddff 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -253,6 +253,7 @@ const SCREENS = {
         STEP_WAYPOINT: 'Money_Request_Step_Waypoint',
         STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount',
         STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate',
+        RECEIPT_VIEW_MODAL: 'Money_Request_Receipt_View_Modal',
         STEP_SPLIT_PAYER: 'Money_Request_Step_Split_Payer',
         STEP_SEND_FROM: 'Money_Request_Step_Send_From',
         STEP_COMPANY_INFO: 'Money_Request_Step_Company_Info',
diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.native.tsx b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.native.tsx
new file mode 100644
index 000000000000..85dcc5927ec6
--- /dev/null
+++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.native.tsx
@@ -0,0 +1,118 @@
+import React, {useCallback, useRef, useState} from 'react';
+import {Keyboard, View} from 'react-native';
+import CarouselButtons from '@components/Attachments/AttachmentCarousel/CarouselButtons';
+import AttachmentCarouselPager from '@components/Attachments/AttachmentCarousel/Pager';
+import type {AttachmentCarouselPagerHandle} from '@components/Attachments/AttachmentCarousel/Pager';
+import type {AttachmentSource} from '@components/Attachments/types';
+import BlockingView from '@components/BlockingViews/BlockingView';
+import * as Illustrations from '@components/Icon/Illustrations';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities';
+import variables from '@styles/variables';
+import type AttachmentCarouselViewProps from './types';
+
+function AttachmentCarouselView({
+    page,
+    attachments,
+    shouldShowArrows,
+    source,
+    report,
+    autoHideArrows,
+    cancelAutoHideArrow,
+    setShouldShowArrows,
+    onAttachmentError,
+    onNavigate,
+    onClose,
+    setPage,
+    attachmentID,
+}: AttachmentCarouselViewProps) {
+    const {translate} = useLocalize();
+    const canUseTouchScreen = canUseTouchScreenUtil();
+    const styles = useThemeStyles();
+    const [activeAttachmentID, setActiveAttachmentID] = useState<AttachmentSource>(attachmentID ?? source);
+
+    const pagerRef = useRef<AttachmentCarouselPagerHandle>(null);
+
+    /** Updates the page state when the user navigates between attachments */
+    const updatePage = useCallback(
+        (newPageIndex: number) => {
+            Keyboard.dismiss();
+            setShouldShowArrows(true);
+
+            const item = attachments.at(newPageIndex);
+
+            setPage(newPageIndex);
+            if (newPageIndex >= 0 && item) {
+                setActiveAttachmentID(item.attachmentID ?? item.source);
+                if (onNavigate) {
+                    onNavigate(item);
+                }
+                onNavigate?.(item);
+            }
+        },
+        [setShouldShowArrows, attachments, setPage, onNavigate],
+    );
+
+    /**
+     * Increments or decrements the index to get another selected item
+     * @param {Number} deltaSlide
+     */
+    const cycleThroughAttachments = useCallback(
+        (deltaSlide: number) => {
+            if (page === undefined) {
+                return;
+            }
+            const nextPageIndex = page + deltaSlide;
+            updatePage(nextPageIndex);
+            pagerRef.current?.setPage(nextPageIndex);
+
+            autoHideArrows();
+        },
+        [autoHideArrows, page, updatePage],
+    );
+
+    return (
+        <View
+            style={[styles.flex1, styles.attachmentCarouselContainer]}
+            onMouseEnter={() => !canUseTouchScreen && setShouldShowArrows(true)}
+            onMouseLeave={() => !canUseTouchScreen && setShouldShowArrows(false)}
+        >
+            {page === -1 ? (
+                <BlockingView
+                    icon={Illustrations.ToddBehindCloud}
+                    iconWidth={variables.modalTopIconWidth}
+                    iconHeight={variables.modalTopIconHeight}
+                    title={translate('notFound.notHere')}
+                />
+            ) : (
+                <>
+                    <CarouselButtons
+                        page={page}
+                        attachments={attachments}
+                        shouldShowArrows={shouldShowArrows}
+                        onBack={() => cycleThroughAttachments(-1)}
+                        onForward={() => cycleThroughAttachments(1)}
+                        autoHideArrow={autoHideArrows}
+                        cancelAutoHideArrow={cancelAutoHideArrow}
+                    />
+                    <AttachmentCarouselPager
+                        items={attachments}
+                        initialPage={page}
+                        onAttachmentError={onAttachmentError}
+                        activeAttachmentID={activeAttachmentID}
+                        setShouldShowArrows={setShouldShowArrows}
+                        onPageSelected={({nativeEvent: {position: newPage}}) => updatePage(newPage)}
+                        onClose={onClose}
+                        ref={pagerRef}
+                        reportID={report?.reportID}
+                    />
+                </>
+            )}
+        </View>
+    );
+}
+
+AttachmentCarouselView.displayName = 'AttachmentCarouselView';
+
+export default AttachmentCarouselView;
diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx
new file mode 100644
index 000000000000..40d78b1104ea
--- /dev/null
+++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx
@@ -0,0 +1,292 @@
+import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
+import type {RefObject} from 'react';
+import type {ListRenderItemInfo} from 'react-native';
+import {Keyboard, PixelRatio, View} from 'react-native';
+import {Gesture, GestureDetector} from 'react-native-gesture-handler';
+import type {ComposedGesture, GestureType} from 'react-native-gesture-handler';
+import Animated, {scrollTo, useAnimatedRef, useSharedValue} from 'react-native-reanimated';
+import CarouselActions from '@components/Attachments/AttachmentCarousel/CarouselActions';
+import CarouselButtons from '@components/Attachments/AttachmentCarousel/CarouselButtons';
+import CarouselItem from '@components/Attachments/AttachmentCarousel/CarouselItem';
+import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext';
+import type {UpdatePageProps} from '@components/Attachments/AttachmentCarousel/types';
+import useCarouselContextEvents from '@components/Attachments/AttachmentCarousel/useCarouselContextEvents';
+import type {Attachment, AttachmentSource} from '@components/Attachments/types';
+import BlockingView from '@components/BlockingViews/BlockingView';
+import * as Illustrations from '@components/Icon/Illustrations';
+import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext';
+import useLocalize from '@hooks/useLocalize';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+import type AttachmentCarouselViewProps from './types';
+
+const viewabilityConfig = {
+    // To facilitate paging through the attachments, we want to consider an item "viewable" when it is
+    // more than 95% visible. When that happens we update the page index in the state.
+    itemVisiblePercentThreshold: 95,
+};
+
+const MIN_FLING_VELOCITY = 500;
+
+type DeviceAwareGestureDetectorProps = {
+    canUseTouchScreen: boolean;
+    gesture: ComposedGesture | GestureType;
+    children: React.ReactNode;
+};
+
+function DeviceAwareGestureDetector({canUseTouchScreen, gesture, children}: DeviceAwareGestureDetectorProps) {
+    // Don't render GestureDetector on non-touchable devices to prevent unexpected pointer event capture.
+    // This issue is left out on touchable devices since finger touch works fine.
+    // See: https://github.com/Expensify/App/issues/51246
+    return canUseTouchScreen ? <GestureDetector gesture={gesture}>{children}</GestureDetector> : children;
+}
+
+function AttachmentCarouselView({
+    page,
+    attachments,
+    shouldShowArrows,
+    source,
+    report,
+    autoHideArrows,
+    cancelAutoHideArrow,
+    setShouldShowArrows,
+    onAttachmentError,
+    onNavigate,
+    onClose,
+    setPage,
+    attachmentID,
+}: AttachmentCarouselViewProps) {
+    const {translate} = useLocalize();
+    const styles = useThemeStyles();
+    const canUseTouchScreen = canUseTouchScreenUtil();
+    const {isFullScreenRef} = useFullScreenContext();
+    const isPagerScrolling = useSharedValue(false);
+    const {handleTap, handleScaleChange, isScrollEnabled} = useCarouselContextEvents(setShouldShowArrows);
+
+    const [activeAttachmentID, setActiveAttachmentID] = useState<AttachmentSource | null>(attachmentID ?? source);
+
+    const pagerRef = useRef<GestureType>(null);
+    const scrollRef = useAnimatedRef<Animated.FlatList<ListRenderItemInfo<Attachment>>>();
+
+    const {shouldUseNarrowLayout} = useResponsiveLayout();
+    const modalStyles = styles.centeredModalStyles(shouldUseNarrowLayout, true);
+    const {windowWidth} = useWindowDimensions();
+
+    const cellWidth = useMemo(
+        () => PixelRatio.roundToNearestPixel(windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2),
+        [modalStyles.borderWidth, modalStyles.marginHorizontal, windowWidth],
+    );
+
+    /** Updates the page state when the user navigates between attachments */
+    const updatePage = useCallback(
+        ({viewableItems}: UpdatePageProps) => {
+            if (isFullScreenRef.current) {
+                return;
+            }
+
+            Keyboard.dismiss();
+
+            // Since we can have only one item in view at a time, we can use the first item in the array
+            // to get the index of the current page
+            const entry = viewableItems.at(0);
+            if (!entry) {
+                setActiveAttachmentID(null);
+                return;
+            }
+
+            const item = entry.item as Attachment;
+            if (entry.index !== null) {
+                setPage(entry.index);
+                setActiveAttachmentID(item.attachmentID ?? item.source);
+            }
+
+            if (onNavigate) {
+                onNavigate(item);
+            }
+        },
+        [isFullScreenRef, onNavigate, setPage, setActiveAttachmentID],
+    );
+
+    /** Increments or decrements the index to get another selected item */
+    const cycleThroughAttachments = useCallback(
+        (deltaSlide: number) => {
+            if (isFullScreenRef.current) {
+                return;
+            }
+
+            const nextIndex = page + deltaSlide;
+            const nextItem = attachments.at(nextIndex);
+
+            if (!nextItem || nextIndex < 0 || !scrollRef.current) {
+                return;
+            }
+
+            scrollRef.current.scrollToIndex({index: nextIndex, animated: canUseTouchScreen});
+        },
+        [attachments, canUseTouchScreen, isFullScreenRef, page, scrollRef],
+    );
+
+    const extractItemKey = useCallback(
+        (item: Attachment) =>
+            !!item.attachmentID || (typeof item.source !== 'string' && typeof item.source !== 'number')
+                ? `attachmentID-${item.attachmentID}`
+                : `source-${item.source}|${item.attachmentLink}`,
+        [],
+    );
+
+    /** Calculate items layout information to optimize scrolling performance */
+    const getItemLayout = useCallback(
+        (data: ArrayLike<Attachment> | null | undefined, index: number) => ({
+            length: cellWidth,
+            offset: cellWidth * index,
+            index,
+        }),
+        [cellWidth],
+    );
+
+    const context = useMemo(
+        () => ({
+            pagerItems: [{source, index: 0, isActive: true}],
+            activePage: 0,
+            pagerRef,
+            isPagerScrolling,
+            isScrollEnabled,
+            onTap: handleTap,
+            onScaleChanged: handleScaleChange,
+            onSwipeDown: onClose,
+            onAttachmentError,
+        }),
+        [onAttachmentError, source, isPagerScrolling, isScrollEnabled, handleTap, handleScaleChange, onClose],
+    );
+
+    /** Defines how a single attachment should be rendered */
+    const renderItem = useCallback(
+        ({item}: ListRenderItemInfo<Attachment>) => (
+            <View style={[styles.h100, {width: cellWidth}]}>
+                <CarouselItem
+                    item={item}
+                    isFocused={activeAttachmentID === (item.attachmentID ?? item.source)}
+                    onPress={canUseTouchScreen ? handleTap : undefined}
+                    isModalHovered={shouldShowArrows}
+                    reportID={report?.reportID}
+                />
+            </View>
+        ),
+        [activeAttachmentID, canUseTouchScreen, cellWidth, handleTap, report?.reportID, shouldShowArrows, styles.h100],
+    );
+    /** Pan gesture handing swiping through attachments on touch screen devices */
+    const pan = useMemo(
+        () =>
+            Gesture.Pan()
+                .enabled(canUseTouchScreen)
+                .onUpdate(({translationX}) => {
+                    if (!isScrollEnabled.get()) {
+                        return;
+                    }
+
+                    if (translationX !== 0) {
+                        isPagerScrolling.set(true);
+                    }
+
+                    scrollTo(scrollRef, page * cellWidth - translationX, 0, false);
+                })
+                .onEnd(({translationX, velocityX}) => {
+                    if (!isScrollEnabled.get()) {
+                        return;
+                    }
+
+                    let newIndex;
+                    if (velocityX > MIN_FLING_VELOCITY) {
+                        // User flung to the right
+                        newIndex = Math.max(0, page - 1);
+                    } else if (velocityX < -MIN_FLING_VELOCITY) {
+                        // User flung to the left
+                        newIndex = Math.min(attachments.length - 1, page + 1);
+                    } else {
+                        // snap scroll position to the nearest cell (making sure it's within the bounds of the list)
+                        const delta = Math.round(-translationX / cellWidth);
+                        newIndex = Math.min(attachments.length - 1, Math.max(0, page + delta));
+                    }
+
+                    isPagerScrolling.set(false);
+                    scrollTo(scrollRef, newIndex * cellWidth, 0, true);
+                })
+                // eslint-disable-next-line react-compiler/react-compiler
+                .withRef(pagerRef as RefObject<GestureType | undefined>),
+        [attachments.length, canUseTouchScreen, cellWidth, page, isScrollEnabled, scrollRef, isPagerScrolling],
+    );
+
+    // Scroll position is affected when window width is resized, so we readjust it on width changes
+    useEffect(() => {
+        if (attachments.length === 0 || scrollRef.current == null) {
+            return;
+        }
+
+        scrollRef.current.scrollToIndex({index: page, animated: false});
+        // The hook is not supposed to run on page change, so we keep the page out of the dependencies
+        // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
+    }, [cellWidth]);
+
+    return (
+        <View
+            style={[styles.flex1, styles.attachmentCarouselContainer]}
+            onMouseEnter={() => !canUseTouchScreen && setShouldShowArrows(true)}
+            onMouseLeave={() => !canUseTouchScreen && setShouldShowArrows(false)}
+        >
+            {page === -1 ? (
+                <BlockingView
+                    icon={Illustrations.ToddBehindCloud}
+                    iconWidth={variables.modalTopIconWidth}
+                    iconHeight={variables.modalTopIconHeight}
+                    title={translate('notFound.notHere')}
+                />
+            ) : (
+                <>
+                    <CarouselButtons
+                        page={page}
+                        attachments={attachments}
+                        shouldShowArrows={shouldShowArrows}
+                        onBack={() => cycleThroughAttachments(-1)}
+                        onForward={() => cycleThroughAttachments(1)}
+                        autoHideArrow={autoHideArrows}
+                        cancelAutoHideArrow={cancelAutoHideArrow}
+                    />
+                    <AttachmentCarouselPagerContext.Provider value={context}>
+                        <DeviceAwareGestureDetector
+                            canUseTouchScreen={canUseTouchScreen}
+                            gesture={pan}
+                        >
+                            <Animated.FlatList
+                                keyboardShouldPersistTaps="handled"
+                                horizontal
+                                showsHorizontalScrollIndicator={false}
+                                // scrolling is controlled by the pan gesture
+                                scrollEnabled={false}
+                                ref={scrollRef}
+                                initialScrollIndex={page}
+                                initialNumToRender={3}
+                                windowSize={5}
+                                maxToRenderPerBatch={CONST.MAX_TO_RENDER_PER_BATCH.CAROUSEL}
+                                data={attachments}
+                                renderItem={renderItem}
+                                getItemLayout={getItemLayout}
+                                keyExtractor={extractItemKey}
+                                viewabilityConfig={viewabilityConfig}
+                                onViewableItemsChanged={updatePage}
+                            />
+                        </DeviceAwareGestureDetector>
+                    </AttachmentCarouselPagerContext.Provider>
+                    <CarouselActions onCycleThroughAttachments={cycleThroughAttachments} />
+                </>
+            )}
+        </View>
+    );
+}
+
+AttachmentCarouselView.displayName = 'AttachmentCarouselView';
+
+export default AttachmentCarouselView;
diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/types.ts b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/types.ts
new file mode 100644
index 000000000000..ed7f98a1a858
--- /dev/null
+++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/types.ts
@@ -0,0 +1,33 @@
+import type {Attachment, AttachmentSource} from '@components/Attachments/types';
+import type {Report} from '@src/types/onyx';
+
+type AttachmentCarouselViewProps = {
+    /** Where the arrows should be visible */
+    shouldShowArrows: boolean;
+    /** The current page index */
+    page: number;
+    /** The attachments from the carousel */
+    attachments: Attachment[];
+    /** The id of the current active attachment */
+    attachmentID?: string;
+    /** Source is used to determine the starting index in the array of attachments */
+    source: AttachmentSource;
+    /** Callback for auto hiding carousel button arrows */
+    autoHideArrows: () => void;
+    /** Sets the visibility of the arrows. */
+    setShouldShowArrows: (show?: React.SetStateAction<boolean>) => void;
+    /** Callback for cancelling auto hiding of carousel button arrows */
+    cancelAutoHideArrow: () => void;
+    /** A callback that is called when swipe-down-to-close gesture happens */
+    onClose?: () => void;
+    /** Sets current page */
+    setPage: (page: number) => void;
+    /** The report currently being looked at */
+    report?: Report;
+    /** Callback for attachment errors */
+    onAttachmentError?: (source: AttachmentSource, state?: boolean) => void;
+    /** Callback to update the parent modal's state with a source and name from the attachments array */
+    onNavigate?: (attachment: Attachment) => void;
+};
+
+export default AttachmentCarouselViewProps;
diff --git a/src/components/Attachments/AttachmentCarousel/index.native.tsx b/src/components/Attachments/AttachmentCarousel/index.native.tsx
deleted file mode 100644
index a625b4215d52..000000000000
--- a/src/components/Attachments/AttachmentCarousel/index.native.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import React, {useCallback, useEffect, useRef, useState} from 'react';
-import {Keyboard, View} from 'react-native';
-import {useOnyx} from 'react-native-onyx';
-import type {Attachment, AttachmentSource} from '@components/Attachments/types';
-import BlockingView from '@components/BlockingViews/BlockingView';
-import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
-import * as Illustrations from '@components/Icon/Illustrations';
-import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
-import Navigation from '@libs/Navigation/Navigation';
-import variables from '@styles/variables';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import CarouselButtons from './CarouselButtons';
-import extractAttachments from './extractAttachments';
-import type {AttachmentCarouselPagerHandle} from './Pager';
-import AttachmentCarouselPager from './Pager';
-import type {AttachmentCarouselProps} from './types';
-import useCarouselArrows from './useCarouselArrows';
-
-function AttachmentCarousel({report, source, attachmentID, onNavigate, setDownloadButtonVisibility, onClose, type, accountID, onAttachmentError}: AttachmentCarouselProps) {
-    const styles = useThemeStyles();
-    const {translate} = useLocalize();
-    const pagerRef = useRef<AttachmentCarouselPagerHandle>(null);
-    const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, {canEvict: false, canBeMissing: true});
-    const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {canEvict: false, canBeMissing: true});
-    const [page, setPage] = useState<number>();
-    const [attachments, setAttachments] = useState<Attachment[]>([]);
-    const {shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows} = useCarouselArrows();
-    const [activeAttachmentID, setActiveAttachmentID] = useState<AttachmentSource>(attachmentID ?? source);
-    const compareImage = useCallback((attachment: Attachment) => (attachmentID ? attachment.attachmentID === attachmentID : attachment.source === source), [attachmentID, source]);
-
-    useEffect(() => {
-        const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined;
-        let newAttachments: Attachment[] = [];
-        if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) {
-            newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID, report});
-        } else if (type === CONST.ATTACHMENT_TYPE.ONBOARDING) {
-            newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.ONBOARDING, {parentReportAction, reportActions: reportActions ?? undefined, report});
-        } else {
-            newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions, report});
-        }
-
-        let newIndex = newAttachments.findIndex(compareImage);
-        const index = attachments.findIndex(compareImage);
-
-        // If newAttachments includes an attachment with the same index, update newIndex to that index.
-        // Previously, uploading an attachment offline would dismiss the modal when the image was previewed and the connection was restored.
-        // Now, instead of dismissing the modal, we replace it with the new attachment that has the same index.
-        if (newIndex === -1 && index !== -1 && newAttachments.at(index)) {
-            newIndex = index;
-        }
-
-        // If no matching attachment with the same index, dismiss the modal
-        if (newIndex === -1 && index !== -1 && attachments.at(index)) {
-            Navigation.dismissModal();
-        } else {
-            setPage(newIndex);
-            setAttachments(newAttachments);
-
-            // Update the download button visibility in the parent modal
-            if (setDownloadButtonVisibility) {
-                setDownloadButtonVisibility(newIndex !== -1);
-            }
-
-            const attachment = newAttachments.at(newIndex);
-            // Update the parent modal's state with the source and name from the mapped attachments
-            if (newIndex !== -1 && attachment !== undefined && onNavigate) {
-                onNavigate(attachment);
-            }
-        }
-        // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
-    }, [reportActions, compareImage, report]);
-
-    /** Updates the page state when the user navigates between attachments */
-    const updatePage = useCallback(
-        (newPageIndex: number) => {
-            Keyboard.dismiss();
-            setShouldShowArrows(true);
-
-            const item = attachments.at(newPageIndex);
-
-            setPage(newPageIndex);
-            if (newPageIndex >= 0 && item) {
-                setActiveAttachmentID(item.attachmentID ?? item.source);
-                if (onNavigate) {
-                    onNavigate(item);
-                }
-                onNavigate?.(item);
-            }
-        },
-        [setShouldShowArrows, attachments, onNavigate],
-    );
-
-    /**
-     * Increments or decrements the index to get another selected item
-     * @param {Number} deltaSlide
-     */
-    const cycleThroughAttachments = useCallback(
-        (deltaSlide: number) => {
-            if (page === undefined) {
-                return;
-            }
-            const nextPageIndex = page + deltaSlide;
-            updatePage(nextPageIndex);
-            pagerRef.current?.setPage(nextPageIndex);
-
-            autoHideArrows();
-        },
-        [autoHideArrows, page, updatePage],
-    );
-
-    const containerStyles = [styles.flex1, styles.attachmentCarouselContainer];
-
-    if (page == null) {
-        return (
-            <View style={containerStyles}>
-                <FullScreenLoadingIndicator />
-            </View>
-        );
-    }
-
-    return (
-        <View style={containerStyles}>
-            {page === -1 ? (
-                <BlockingView
-                    icon={Illustrations.ToddBehindCloud}
-                    iconWidth={variables.modalTopIconWidth}
-                    iconHeight={variables.modalTopIconHeight}
-                    title={translate('notFound.notHere')}
-                />
-            ) : (
-                <>
-                    <CarouselButtons
-                        shouldShowArrows={shouldShowArrows}
-                        page={page}
-                        attachments={attachments}
-                        onBack={() => cycleThroughAttachments(-1)}
-                        onForward={() => cycleThroughAttachments(1)}
-                        autoHideArrow={autoHideArrows}
-                        cancelAutoHideArrow={cancelAutoHideArrows}
-                    />
-
-                    <AttachmentCarouselPager
-                        items={attachments}
-                        initialPage={page}
-                        onAttachmentError={onAttachmentError}
-                        activeAttachmentID={activeAttachmentID}
-                        setShouldShowArrows={setShouldShowArrows}
-                        onPageSelected={({nativeEvent: {position: newPage}}) => updatePage(newPage)}
-                        onClose={onClose}
-                        ref={pagerRef}
-                        reportID={report.reportID}
-                    />
-                </>
-            )}
-        </View>
-    );
-}
-
-AttachmentCarousel.displayName = 'AttachmentCarousel';
-
-export default AttachmentCarousel;
diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx
index b3d81544a1bd..6e93b9dbe065 100644
--- a/src/components/Attachments/AttachmentCarousel/index.tsx
+++ b/src/components/Attachments/AttachmentCarousel/index.tsx
@@ -1,80 +1,28 @@
 import {deepEqual} from 'fast-equals';
-import type {MutableRefObject} from 'react';
-import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import type {ListRenderItemInfo} from 'react-native';
-import {Keyboard, PixelRatio, View} from 'react-native';
-import type {ComposedGesture, GestureType} from 'react-native-gesture-handler';
-import {Gesture, GestureDetector} from 'react-native-gesture-handler';
-import Animated, {scrollTo, useAnimatedRef, useSharedValue} from 'react-native-reanimated';
-import type {Attachment, AttachmentSource} from '@components/Attachments/types';
-import BlockingView from '@components/BlockingViews/BlockingView';
-import {ToddBehindCloud} from '@components/Icon/Illustrations';
-import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext';
-import useLocalize from '@hooks/useLocalize';
+import React, {useCallback, useEffect, useState} from 'react';
+import {View} from 'react-native';
+import type {Attachment} from '@components/Attachments/types';
+import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
 import useOnyx from '@hooks/useOnyx';
-import useResponsiveLayout from '@hooks/useResponsiveLayout';
-import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
-import useWindowDimensions from '@hooks/useWindowDimensions';
 import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities';
 import Navigation from '@libs/Navigation/Navigation';
-import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import CarouselActions from './CarouselActions';
-import CarouselButtons from './CarouselButtons';
-import CarouselItem from './CarouselItem';
+import AttachmentCarouselView from './AttachmentCarouselView';
 import extractAttachments from './extractAttachments';
-import AttachmentCarouselPagerContext from './Pager/AttachmentCarouselPagerContext';
-import type {AttachmentCarouselProps, UpdatePageProps} from './types';
+import type {AttachmentCarouselProps} from './types';
 import useCarouselArrows from './useCarouselArrows';
-import useCarouselContextEvents from './useCarouselContextEvents';
-
-const viewabilityConfig = {
-    // To facilitate paging through the attachments, we want to consider an item "viewable" when it is
-    // more than 95% visible. When that happens we update the page index in the state.
-    itemVisiblePercentThreshold: 95,
-};
-
-const MIN_FLING_VELOCITY = 500;
-
-type DeviceAwareGestureDetectorProps = {
-    canUseTouchScreen: boolean;
-    gesture: ComposedGesture | GestureType;
-    children: React.ReactNode;
-};
-
-function DeviceAwareGestureDetector({canUseTouchScreen, gesture, children}: DeviceAwareGestureDetectorProps) {
-    // Don't render GestureDetector on non-touchable devices to prevent unexpected pointer event capture.
-    // This issue is left out on touchable devices since finger touch works fine.
-    // See: https://github.com/Expensify/App/issues/51246
-    return canUseTouchScreen ? <GestureDetector gesture={gesture}>{children}</GestureDetector> : children;
-}
 
 function AttachmentCarousel({report, attachmentID, source, onNavigate, setDownloadButtonVisibility, type, accountID, onClose, attachmentLink, onAttachmentError}: AttachmentCarouselProps) {
-    const theme = useTheme();
-    const {translate} = useLocalize();
-    const {windowWidth} = useWindowDimensions();
-    const {shouldUseNarrowLayout} = useResponsiveLayout();
-    const styles = useThemeStyles();
-    const {isFullScreenRef} = useFullScreenContext();
-    const scrollRef = useAnimatedRef<Animated.FlatList<ListRenderItemInfo<Attachment>>>();
-    const isPagerScrolling = useSharedValue(false);
-    const pagerRef = useRef<GestureType>(null);
     const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, {canEvict: false, canBeMissing: true});
     const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {canEvict: false, canBeMissing: true});
     const canUseTouchScreen = canUseTouchScreenUtil();
+    const styles = useThemeStyles();
 
-    const modalStyles = styles.centeredModalStyles(shouldUseNarrowLayout, true);
-    const cellWidth = useMemo(
-        () => PixelRatio.roundToNearestPixel(windowWidth - (modalStyles.marginHorizontal + modalStyles.borderWidth) * 2),
-        [modalStyles.borderWidth, modalStyles.marginHorizontal, windowWidth],
-    );
-    const [page, setPage] = useState(0);
+    const [page, setPage] = useState<number>();
     const [attachments, setAttachments] = useState<Attachment[]>([]);
-    const [activeAttachmentID, setActiveAttachmentID] = useState<AttachmentSource | null>(attachmentID ?? source);
     const {shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows} = useCarouselArrows();
-    const {handleTap, handleScaleChange, isScrollEnabled} = useCarouselContextEvents(setShouldShowArrows);
 
     useEffect(() => {
         if (!canUseTouchScreen) {
@@ -138,211 +86,29 @@ function AttachmentCarousel({report, attachmentID, source, onNavigate, setDownlo
         }
     }, [reportActions, parentReportActions, compareImage, attachments, setDownloadButtonVisibility, onNavigate, accountID, type, report]);
 
-    // Scroll position is affected when window width is resized, so we readjust it on width changes
-    useEffect(() => {
-        if (attachments.length === 0 || scrollRef.current == null) {
-            return;
-        }
-
-        scrollRef.current.scrollToIndex({index: page, animated: false});
-        // The hook is not supposed to run on page change, so we keep the page out of the dependencies
-        // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
-    }, [cellWidth]);
-
-    /** Updates the page state when the user navigates between attachments */
-    const updatePage = useCallback(
-        ({viewableItems}: UpdatePageProps) => {
-            if (isFullScreenRef.current) {
-                return;
-            }
-
-            Keyboard.dismiss();
-
-            // Since we can have only one item in view at a time, we can use the first item in the array
-            // to get the index of the current page
-            const entry = viewableItems.at(0);
-            if (!entry) {
-                setActiveAttachmentID(null);
-                return;
-            }
-
-            const item = entry.item as Attachment;
-            if (entry.index !== null) {
-                setPage(entry.index);
-                setActiveAttachmentID(item.attachmentID ?? item.source);
-            }
-
-            if (onNavigate) {
-                onNavigate(item);
-            }
-        },
-        [isFullScreenRef, onNavigate],
-    );
-
-    /** Increments or decrements the index to get another selected item */
-    const cycleThroughAttachments = useCallback(
-        (deltaSlide: number) => {
-            if (isFullScreenRef.current) {
-                return;
-            }
-
-            const nextIndex = page + deltaSlide;
-            const nextItem = attachments.at(nextIndex);
-
-            if (!nextItem || nextIndex < 0 || !scrollRef.current) {
-                return;
-            }
-
-            scrollRef.current.scrollToIndex({index: nextIndex, animated: canUseTouchScreen});
-        },
-        [attachments, canUseTouchScreen, isFullScreenRef, page, scrollRef],
-    );
-
-    const extractItemKey = useCallback(
-        (item: Attachment) =>
-            !!item.attachmentID || (typeof item.source !== 'string' && typeof item.source !== 'number')
-                ? `attachmentID-${item.attachmentID}`
-                : `source-${item.source}|${item.attachmentLink}`,
-        [],
-    );
-
-    /** Calculate items layout information to optimize scrolling performance */
-    const getItemLayout = useCallback(
-        (data: ArrayLike<Attachment> | null | undefined, index: number) => ({
-            length: cellWidth,
-            offset: cellWidth * index,
-            index,
-        }),
-        [cellWidth],
-    );
-
-    const context = useMemo(
-        () => ({
-            pagerItems: [{source, index: 0, isActive: true}],
-            activePage: 0,
-            pagerRef,
-            isPagerScrolling,
-            isScrollEnabled,
-            onTap: handleTap,
-            onScaleChanged: handleScaleChange,
-            onSwipeDown: onClose,
-            onAttachmentError,
-        }),
-        [onAttachmentError, source, isPagerScrolling, isScrollEnabled, handleTap, handleScaleChange, onClose],
-    );
-
-    /** Defines how a single attachment should be rendered */
-    const renderItem = useCallback(
-        ({item}: ListRenderItemInfo<Attachment>) => (
-            <View style={[styles.h100, {width: cellWidth}]}>
-                <CarouselItem
-                    item={item}
-                    isFocused={activeAttachmentID === (item.attachmentID ?? item.source)}
-                    onPress={canUseTouchScreen ? handleTap : undefined}
-                    isModalHovered={shouldShowArrows}
-                    reportID={report.reportID}
-                />
+    if (page == null) {
+        return (
+            <View style={[styles.flex1, styles.attachmentCarouselContainer]}>
+                <FullScreenLoadingIndicator />
             </View>
-        ),
-        [activeAttachmentID, canUseTouchScreen, cellWidth, handleTap, report.reportID, shouldShowArrows, styles.h100],
-    );
-    /** Pan gesture handing swiping through attachments on touch screen devices */
-    const pan = useMemo(
-        () =>
-            Gesture.Pan()
-                .enabled(canUseTouchScreen)
-                .onUpdate(({translationX}) => {
-                    if (!isScrollEnabled.get()) {
-                        return;
-                    }
-
-                    if (translationX !== 0) {
-                        isPagerScrolling.set(true);
-                    }
-
-                    scrollTo(scrollRef, page * cellWidth - translationX, 0, false);
-                })
-                .onEnd(({translationX, velocityX}) => {
-                    if (!isScrollEnabled.get()) {
-                        return;
-                    }
-
-                    let newIndex;
-                    if (velocityX > MIN_FLING_VELOCITY) {
-                        // User flung to the right
-                        newIndex = Math.max(0, page - 1);
-                    } else if (velocityX < -MIN_FLING_VELOCITY) {
-                        // User flung to the left
-                        newIndex = Math.min(attachments.length - 1, page + 1);
-                    } else {
-                        // snap scroll position to the nearest cell (making sure it's within the bounds of the list)
-                        const delta = Math.round(-translationX / cellWidth);
-                        newIndex = Math.min(attachments.length - 1, Math.max(0, page + delta));
-                    }
-
-                    isPagerScrolling.set(false);
-                    scrollTo(scrollRef, newIndex * cellWidth, 0, true);
-                })
-                // eslint-disable-next-line react-compiler/react-compiler
-                .withRef(pagerRef as MutableRefObject<GestureType | undefined>),
-        [attachments.length, canUseTouchScreen, cellWidth, page, isScrollEnabled, scrollRef, isPagerScrolling],
-    );
+        );
+    }
 
     return (
-        <View
-            style={[styles.flex1, styles.attachmentCarouselContainer]}
-            onMouseEnter={() => !canUseTouchScreen && setShouldShowArrows(true)}
-            onMouseLeave={() => !canUseTouchScreen && setShouldShowArrows(false)}
-        >
-            {page === -1 ? (
-                <BlockingView
-                    icon={ToddBehindCloud}
-                    iconColor={theme.offline}
-                    iconWidth={variables.modalTopIconWidth}
-                    iconHeight={variables.modalTopIconHeight}
-                    title={translate('notFound.notHere')}
-                />
-            ) : (
-                <>
-                    <CarouselButtons
-                        shouldShowArrows={shouldShowArrows}
-                        page={page}
-                        attachments={attachments}
-                        onBack={() => cycleThroughAttachments(-1)}
-                        onForward={() => cycleThroughAttachments(1)}
-                        autoHideArrow={autoHideArrows}
-                        cancelAutoHideArrow={cancelAutoHideArrows}
-                    />
-                    <AttachmentCarouselPagerContext.Provider value={context}>
-                        <DeviceAwareGestureDetector
-                            canUseTouchScreen={canUseTouchScreen}
-                            gesture={pan}
-                        >
-                            <Animated.FlatList
-                                keyboardShouldPersistTaps="handled"
-                                horizontal
-                                showsHorizontalScrollIndicator={false}
-                                // scrolling is controlled by the pan gesture
-                                scrollEnabled={false}
-                                ref={scrollRef}
-                                initialScrollIndex={page}
-                                initialNumToRender={3}
-                                windowSize={5}
-                                maxToRenderPerBatch={CONST.MAX_TO_RENDER_PER_BATCH.CAROUSEL}
-                                data={attachments}
-                                renderItem={renderItem}
-                                getItemLayout={getItemLayout}
-                                keyExtractor={extractItemKey}
-                                viewabilityConfig={viewabilityConfig}
-                                onViewableItemsChanged={updatePage}
-                            />
-                        </DeviceAwareGestureDetector>
-                    </AttachmentCarouselPagerContext.Provider>
-
-                    <CarouselActions onCycleThroughAttachments={cycleThroughAttachments} />
-                </>
-            )}
-        </View>
+        <AttachmentCarouselView
+            page={page}
+            setPage={setPage}
+            attachments={attachments}
+            shouldShowArrows={shouldShowArrows}
+            autoHideArrows={autoHideArrows}
+            cancelAutoHideArrow={cancelAutoHideArrows}
+            setShouldShowArrows={setShouldShowArrows}
+            onClose={onClose}
+            onAttachmentError={onAttachmentError}
+            report={report}
+            attachmentID={attachmentID}
+            source={source}
+        />
     );
 }
 
diff --git a/src/components/Attachments/AttachmentCarousel/types.ts b/src/components/Attachments/AttachmentCarousel/types.ts
index 337c4d2391e8..5487c5d880df 100644
--- a/src/components/Attachments/AttachmentCarousel/types.ts
+++ b/src/components/Attachments/AttachmentCarousel/types.ts
@@ -12,6 +12,9 @@ type AttachmentCarouselProps = {
     /** Source is used to determine the starting index in the array of attachments */
     source: AttachmentSource;
 
+    /** The report currently being looked at */
+    report: Report;
+
     /** The id of the current active attachment */
     attachmentID?: string;
 
@@ -21,9 +24,6 @@ type AttachmentCarouselProps = {
     /** Function to change the download button Visibility */
     setDownloadButtonVisibility?: (isButtonVisible: boolean) => void;
 
-    /** The report currently being looked at */
-    report: Report;
-
     /** The type of the attachment */
     type?: ValueOf<typeof CONST.ATTACHMENT_TYPE>;
 
diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx
index a047ea411f5f..d5506d84c6bf 100644
--- a/src/components/Attachments/AttachmentView/index.tsx
+++ b/src/components/Attachments/AttachmentView/index.tsx
@@ -89,6 +89,7 @@ type AttachmentViewProps = Attachment & {
 
 function checkIsFileImage(source: string | number | ImageURISource | ImageURISource[], fileName: string | undefined) {
     const isSourceImage = typeof source === 'number' || (typeof source === 'string' && Str.isImage(source));
+
     const isFileNameImage = fileName && Str.isImage(fileName);
 
     return isSourceImage || isFileNameImage;
@@ -249,9 +250,14 @@ function AttachmentView({
     // For this check we use both source and file.name since temporary file source is a blob
     // both PDFs and images will appear as images when pasted into the text field.
     // We also check for numeric source since this is how static images (used for preview) are represented in RN.
+
+    // isLocalSource checks if the source is blob as that's the type of the temp image coming from mobile web
     const isFileImage = checkIsFileImage(source, file?.name);
+    const isLocalSourceImage = typeof source === 'string' && source.startsWith('blob:');
+
+    const isImage = isFileImage ?? isLocalSourceImage;
 
-    if (isFileImage) {
+    if (isImage) {
         if (imageError && (typeof fallbackSource === 'number' || typeof fallbackSource === 'function')) {
             return (
                 <View style={[styles.flexColumn, styles.alignItemsCenter, styles.justifyContentCenter]}>
@@ -298,7 +304,7 @@ function AttachmentView({
                         file={file}
                         isAuthTokenRequired={isAuthTokenRequired}
                         loadComplete={loadComplete}
-                        isImage={isFileImage}
+                        isImage={isImage}
                         onPress={onPress}
                         onError={() => {
                             if (isOffline) {
diff --git a/src/components/Attachments/types.ts b/src/components/Attachments/types.ts
index 617b9e80c3af..0f6c14cb3d2e 100644
--- a/src/components/Attachments/types.ts
+++ b/src/components/Attachments/types.ts
@@ -4,6 +4,9 @@ import type IconAsset from '@src/types/utils/IconAsset';
 type AttachmentSource = string | IconAsset | number;
 
 type Attachment = {
+    /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */
+    source: AttachmentSource;
+
     /** Report action ID of the attachment */
     reportActionID?: string;
 
@@ -13,9 +16,6 @@ type Attachment = {
     /** Whether source url requires authentication */
     isAuthTokenRequired?: boolean;
 
-    /** URL to full-sized attachment, SVG function, or numeric static image on native platforms */
-    source: AttachmentSource;
-
     /** URL to preview-sized attachment that is also used for the thumbnail */
     previewSource?: AttachmentSource;
 
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 8c50df7665ca..ed2f102c3055 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -124,6 +124,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator<MoneyRequestNa
     [SCREENS.MONEY_REQUEST.STEP_DESTINATION_EDIT]: () => require<ReactComponentModule>('../../../../pages/iou/request/step/IOURequestStepDestination').default,
     [SCREENS.MONEY_REQUEST.STEP_TIME_EDIT]: () => require<ReactComponentModule>('../../../../pages/iou/request/step/IOURequestStepTime').default,
     [SCREENS.MONEY_REQUEST.STEP_SUBRATE_EDIT]: () => require<ReactComponentModule>('../../../../pages/iou/request/step/IOURequestStepSubrate').default,
+    [SCREENS.MONEY_REQUEST.RECEIPT_VIEW_MODAL]: () => require<ReactComponentModule>('../../../../pages/iou/request/step/IOURequestStepScan/ReceiptViewModal').default,
     [SCREENS.MONEY_REQUEST.SPLIT_EXPENSE]: () => require<ReactComponentModule>('../../../../pages/iou/SplitExpensePage').default,
     [SCREENS.MONEY_REQUEST.SPLIT_EXPENSE_EDIT]: () => require<ReactComponentModule>('../../../../pages/iou/SplitExpenseEditPage').default,
 });
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index d24f0215ccda..9c5cbc519edf 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -1360,6 +1360,7 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
                         [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: ROUTES.MONEY_REQUEST_STEP_MERCHANT.route,
                         [SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.route,
                         [SCREENS.MONEY_REQUEST.STEP_SCAN]: ROUTES.MONEY_REQUEST_STEP_SCAN.route,
+                        [SCREENS.MONEY_REQUEST.RECEIPT_VIEW_MODAL]: ROUTES.MONEY_REQUEST_RECEIPT_VIEW_MODAL.route,
                         [SCREENS.MONEY_REQUEST.STEP_TAG]: ROUTES.MONEY_REQUEST_STEP_TAG.route,
                         [SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: ROUTES.MONEY_REQUEST_STEP_WAYPOINT.route,
                         [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.route,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index f2df578e8815..b5de5bf78131 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -1420,6 +1420,10 @@ type MoneyRequestNavigatorParamList = {
         backTo: Routes;
         backToReport?: string;
     };
+    [SCREENS.MONEY_REQUEST.RECEIPT_VIEW_MODAL]: {
+        transactionID: string;
+        backTo: Routes;
+    };
     [SCREENS.MONEY_REQUEST.STEP_CURRENCY]: {
         action: IOUAction;
         iouType: IOUType;
diff --git a/src/pages/iou/request/step/IOURequestStepScan/ReceiptPreviews/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/ReceiptPreviews/index.tsx
index 4bf746ffd6dd..97cf087bcf9c 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/ReceiptPreviews/index.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/ReceiptPreviews/index.tsx
@@ -13,9 +13,11 @@ import usePrevious from '@hooks/usePrevious';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
+import Navigation from '@libs/Navigation/Navigation';
 import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
 import type {Receipt} from '@src/types/onyx/Transaction';
 import SubmitButtonShadow from './SubmitButtonShadow';
 
@@ -34,6 +36,7 @@ function ReceiptPreviews({submit, isMultiScanEnabled}: ReceiptPreviewsProps) {
     const theme = useTheme();
     const {translate} = useLocalize();
     const {windowWidth} = useWindowDimensions();
+    const backTo = Navigation.getActiveRoute();
     const isPreviewsVisible = useSharedValue(false);
     const previewsHeight = styles.receiptPlaceholder.height + styles.pv2.paddingVertical * 2;
     const previewItemWidth = styles.receiptPlaceholder.width + styles.receiptPlaceholder.marginRight;
@@ -89,8 +92,7 @@ function ReceiptPreviews({submit, isMultiScanEnabled}: ReceiptPreviewsProps) {
                 accessible
                 accessibilityLabel={translate('common.receipt')}
                 accessibilityRole={CONST.ROLE.BUTTON}
-                // TODO: open ReceiptViewModal when implemented https://github.com/Expensify/App/issues/61182
-                onPress={() => {}}
+                onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_RECEIPT_VIEW_MODAL.getRoute(item.transactionID, backTo))}
             >
                 <Image
                     source={{uri: item.source}}
diff --git a/src/pages/iou/request/step/IOURequestStepScan/ReceiptViewModal/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/ReceiptViewModal/index.tsx
new file mode 100644
index 000000000000..37c6b276864d
--- /dev/null
+++ b/src/pages/iou/request/step/IOURequestStepScan/ReceiptViewModal/index.tsx
@@ -0,0 +1,139 @@
+import React, {useCallback, useEffect, useState} from 'react';
+import {InteractionManager} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import AttachmentCarouselView from '@components/Attachments/AttachmentCarousel/AttachmentCarouselView';
+import useCarouselArrows from '@components/Attachments/AttachmentCarousel/useCarouselArrows';
+import useAttachmentErrors from '@components/Attachments/AttachmentView/useAttachmentErrors';
+import Button from '@components/Button';
+import ConfirmModal from '@components/ConfirmModal';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Expensicons from '@components/Icon/Expensicons';
+import Modal from '@components/Modal';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types';
+import {removeTransactionReceipt} from '@userActions/TransactionEdit';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import type {Receipt} from '@src/types/onyx/Transaction';
+
+type ReceiptWithTransactionIDAndSource = Receipt & ReceiptFile;
+
+type ReceiptViewModalProps = {
+    route: {
+        params: {
+            transactionID: string;
+            backTo: Route;
+        };
+    };
+};
+
+function ReceiptViewModal({route}: ReceiptViewModalProps) {
+    const {translate} = useLocalize();
+    const {setAttachmentError, clearAttachmentErrors} = useAttachmentErrors();
+    const {shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows} = useCarouselArrows();
+    const styles = useThemeStyles();
+
+    const [currentReceipt, setCurrentReceipt] = useState<ReceiptWithTransactionIDAndSource | null>();
+    const [page, setPage] = useState<number>(-1);
+    const [isDeleteReceiptConfirmModalVisible, setIsDeleteReceiptConfirmModalVisible] = useState(false);
+
+    const [receipts = []] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {
+        selector: (items) =>
+            Object.values(items ?? {})
+                .map((transaction) => (transaction?.receipt ? {...transaction?.receipt, transactionID: transaction.transactionID} : undefined))
+                .filter((receipt): receipt is ReceiptWithTransactionIDAndSource => !!receipt),
+        canBeMissing: true,
+    });
+
+    useEffect(() => {
+        if (!receipts || receipts.length === 0) {
+            return;
+        }
+
+        const activeReceipt = receipts.find((receipt) => receipt.transactionID === route?.params?.transactionID);
+        const activeReceiptIndex = receipts.findIndex((receipt) => receipt.transactionID === activeReceipt?.transactionID);
+
+        setCurrentReceipt(activeReceipt);
+        setPage(activeReceiptIndex);
+    }, [receipts, route?.params?.transactionID]);
+
+    const handleDeleteReceipt = useCallback(() => {
+        if (!currentReceipt) {
+            return;
+        }
+
+        InteractionManager.runAfterInteractions(() => {
+            removeTransactionReceipt(currentReceipt.transactionID);
+        });
+
+        Navigation.goBack();
+    }, [currentReceipt]);
+
+    const handleCloseConfirmModal = () => {
+        setIsDeleteReceiptConfirmModalVisible(false);
+    };
+
+    const deleteReceipt = useCallback(() => {
+        handleCloseConfirmModal();
+        handleDeleteReceipt();
+    }, [handleDeleteReceipt]);
+
+    const handleGoBack = useCallback(() => {
+        Navigation.goBack(route.params.backTo);
+    }, [route.params.backTo]);
+
+    return (
+        <Modal
+            type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
+            isVisible
+            onClose={handleGoBack}
+            onModalHide={clearAttachmentErrors}
+        >
+            <HeaderWithBackButton
+                title={translate('common.receipt')}
+                shouldDisplayHelpButton={false}
+                onBackButtonPress={handleGoBack}
+                onCloseButtonPress={handleCloseConfirmModal}
+            >
+                <Button
+                    shouldShowRightIcon
+                    iconRight={Expensicons.Trashcan}
+                    onPress={() => setIsDeleteReceiptConfirmModalVisible(true)}
+                    innerStyles={styles.bgTransparent}
+                    large
+                />
+            </HeaderWithBackButton>
+            <AttachmentCarouselView
+                attachments={receipts}
+                source={currentReceipt?.source ?? ''}
+                page={page}
+                setPage={setPage}
+                attachmentID={currentReceipt?.transactionID}
+                onClose={handleGoBack}
+                autoHideArrows={autoHideArrows}
+                cancelAutoHideArrow={cancelAutoHideArrows}
+                setShouldShowArrows={setShouldShowArrows}
+                onAttachmentError={setAttachmentError}
+                shouldShowArrows={shouldShowArrows}
+            />
+            <ConfirmModal
+                title={translate('receipt.deleteReceipt')}
+                isVisible={isDeleteReceiptConfirmModalVisible}
+                onConfirm={deleteReceipt}
+                onCancel={handleCloseConfirmModal}
+                prompt={translate('receipt.deleteConfirmation')}
+                confirmText={translate('common.delete')}
+                cancelText={translate('common.cancel')}
+                onBackdropPress={handleCloseConfirmModal}
+                danger
+            />
+        </Modal>
+    );
+}
+
+ReceiptViewModal.displayName = 'ReceiptViewModal';
+
+export default ReceiptViewModal;
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
index 680a47db80df..883f9d710420 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx
@@ -29,7 +29,6 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
 import Text from '@components/Text';
 import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
 import useLocalize from '@hooks/useLocalize';
-import usePermissions from '@hooks/usePermissions';
 import usePolicy from '@hooks/usePolicy';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
@@ -103,8 +102,9 @@ function IOURequestStepScan({
     const hasFlash = !!device?.hasFlash;
     const camera = useRef<Camera>(null);
     const [flash, setFlash] = useState(false);
-    const {isBetaEnabled} = usePermissions();
-    const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport;
+    // TODO: use correct canUseMultiScan value when all multi-scan functionality is implemented
+    // const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport;
+    const canUseMultiScan = true;
     const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false);
     const [receiptFiles, setReceiptFiles] = useState<ReceiptFile[]>([]);
     const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`, {canBeMissing: true});
diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
index a6a7a8b6f1c6..fdb73e49aa91 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx
+++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx
@@ -133,7 +133,9 @@ function IOURequestStepScan({
     const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true});
     const [isLoadingReceipt, setIsLoadingReceipt] = useState(false);
     const isEditing = action === CONST.IOU.ACTION.EDIT;
-    const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport;
+    // TODO: use correct canUseMultiScan value when all multi-scan functionality is implemented
+    // const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport;
+    const canUseMultiScan = true;
 
     const [optimisticTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {
         selector: (items) => Object.values(items ?? {}),
diff --git a/src/pages/iou/request/step/IOURequestStepScan/types.ts b/src/pages/iou/request/step/IOURequestStepScan/types.ts
index 83176d8e05ac..6ad8d973b87e 100644
--- a/src/pages/iou/request/step/IOURequestStepScan/types.ts
+++ b/src/pages/iou/request/step/IOURequestStepScan/types.ts
@@ -4,6 +4,7 @@ import type {WithWritableReportOrNotFoundProps} from '@pages/iou/request/step/wi
 import type {FileObject} from '@pages/media/AttachmentModalScreen/types';
 import type SCREENS from '@src/SCREENS';
 import type * as OnyxTypes from '@src/types/onyx';
+import type {ReceiptSource} from '@src/types/onyx/Transaction';
 
 type IOURequestStepScanProps = WithCurrentUserPersonalDetailsProps &
     WithWritableReportOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.STEP_SCAN | typeof SCREENS.MONEY_REQUEST.CREATE> & {
@@ -24,7 +25,7 @@ type IOURequestStepScanProps = WithCurrentUserPersonalDetailsProps &
     };
 
 type ReceiptFile = {
-    source: string;
+    source: ReceiptSource;
     file?: FileObject;
     transactionID: string;
 };
