@facilitronworks/react-native-windows-modal

中文文档
0.1.0-pre.1发布于 2026/07/21MITTypeScriptCommonJS零依赖近 30 天下载 4248

A drop-in replacement for react-native's that actually works on react-native-windows new architecture, where the native WindowsModalHostView renders transparent flex content as an empty, unclosable shell. Delegates to the real on every oth

原始使用文档
文档目录9 节
  1. Why this exists
  2. The fix
  3. Installation
  4. Usage
  5. Supported props, and what is ignored on Windows
  6. Session replay / FullStory
  7. Traps you will hit
  8. Verification status
  9. License

@facilitronworks/react-native-windows-modal

A drop-in replacement for react-native's <Modal> that works on React Native Windows new architecture (Fabric / Composition), where the core <Modal> does not.

JS/TSX only — no native code, no autolinking, no rebuild.

Status: pre-release (0.1.0-pre.1). Extracted from a shipping production Windows app (RNW 0.83.2) where it replaced every broken modal in the app.

What is verified here: see Verification status.

Why this exists

On RNW new-arch, <Modal> is backed by the native WindowsModalHostView (Microsoft.ReactNative/Fabric/Composition/Modal). That component does not render an in-app overlay the way iOS and Android do. It spins up a separate top-level OS window — DesktopPopupSiteBridge + OverlappedPresenter.IsModal(true) — with its own native title bar, border and close (X) button, and sizes that window from the child's layout frame (ContentSizePolicy::None → AdjustWindowSize).

Two consequences break real-world modals:

1. transparent + flex content renders as an empty, unclosable shell. The overwhelmingly common modal pattern is a transparent host with a full-bleed overlay child:

<Modal transparent visible={open} onRequestClose={close}>
  <View style={{ flex: 1 }}>…</View>
</Modal>

Inside a content-sized popup there is no bounded size for flex: 1 to fill, so the frame measures {0, 0}, AdjustWindowSize bails, and you get an empty window. Worse, it is stuck: the in-content close button never mounts because the content never laid out, and the native title-bar X only emits onRequestClose — it does not close the window itself. If your handler is on the content that never rendered, there is no way out of the modal.

2. Non-transparent full-screen content becomes a second desktop window. Complete with title bar and chrome — nothing like the in-app sheet the same code produces on iOS and Android.

The fix

react-native-paper's <Modal> works on RNW precisely because it renders an in-tree <Portal> overlay in the same window/island — no native modal host is involved. This package mirrors that approach behind the standard <Modal> API:

  • On Windows: renders children into an absolutely-positioned, root-level <Portal> overlay. StyleSheet.absoluteFill gives the overlay a definite size, which is exactly what the content-sized native popup could not provide — so flex: 1 children lay out correctly.
  • On every other platform: delegates to the real react-native <Modal>, props spread through unchanged. iOS / Android / web behaviour is byte-for-byte identical.

That means you can swap the import globally and only change Windows behaviour.

Installation

npm install @facilitronworks/react-native-windows-modal
# or: yarn add @facilitronworks/react-native-windows-modal

Peer dependencies: react, react-native, and react-native-paper >= 5 — Paper's Portal is the overlay host, and Paper's useTheme() supplies the background colour for non-transparent modals.

You must have a <PaperProvider> (or a bare <Portal.Host>) mounted at your app root. That is what the Portal renders into. Without it the modal renders nothing on Windows.

// app root
<PaperProvider>
  <App />
</PaperProvider>

Usage

import WindowsModal from '@facilitronworks/react-native-windows-modal';

<WindowsModal transparent visible={open} onRequestClose={close}>
  <View style={{ flex: 1, justifyContent: 'center' }}>
    …
  </View>
</WindowsModal>

The simplest adoption is a project-wide import swap:

- import { Modal } from 'react-native';
+ import WindowsModal as Modal from '@facilitronworks/react-native-windows-modal';

Both a named export (WindowsModal) and a default export are provided.

Supported props, and what is ignored on Windows

Every ModalProps is forwarded verbatim on non-Windows platforms. On Windows, only the props that are meaningful for an in-tree overlay are honoured:

PropWindows behaviour
visibleHonoured — false renders null.
transparentHonoured — when false, the overlay fills with backdropColor ?? theme.colors.background.
backdropColorHonoured (only when not transparent).
onRequestCloseHonoured — also required for the tap-outside-to-close backdrop to be rendered at all.
onShowCalled manually when visible flips true, to preserve the RN contract.
unmaskHonoured — when true, forwards fsClass="fs-unmask" onto the overlay to un-mask it in FullStory session replay. Defaults to false (masked). See Session replay / FullStory.
animationTypeIgnored. No animation — the overlay appears instantly.
onDismiss, statusBarTranslucent, hardwareAccelerated, supportedOrientations, presentationStyle, navigationBarTranslucentIgnored (mobile/native-window concepts).

Session replay / FullStory

FullStory (React Native) masks all content by default and reveals an element only when it carries the fs-unmask marker. On Windows this component renders its overlay with a plain react-native View, so — unlike the app-internal View wrapper it was extracted from, which hardcoded fsClass="fs-unmask" — the modal is masked in session replay.

To un-mask it, pass unmask:

<WindowsModal transparent visible={open} onRequestClose={close} unmask>
  …
</WindowsModal>

This forwards fsClass="fs-unmask" onto the overlay View. It defaults to false (masked) — the safe FullStory default — so opt in only where the modal content is safe to show in replay. Windows-only (the overlay is Windows-only); it is inert on other platforms.

Traps you will hit

  • No <Portal.Host> means no modal. The single most likely failure. If your app root has no PaperProvider, the Windows path silently renders nothing.
  • This is not a real modal — it does not trap focus. It is an overlay in the same window. Keyboard focus and screen-reader traversal can still reach the app tree underneath. Do not rely on it for anything security- or correctness-critical.
  • Escape does not close it. The native title-bar X is gone (that is the point), but nothing replaces it — wire your own key handling if you need it.
  • The backdrop only exists when you pass onRequestClose. Without that prop there is no Pressable scrim, so tapping outside does nothing. This is deliberate — a scrim that cannot close anything is a trap.
  • animationType silently does nothing on Windows. Modals appear and disappear instantly; a design that depends on a slide-in will look abrupt.
  • Sibling z-order. The overlay sets zIndex/elevation 9999, but two of these open simultaneously stack in Portal order, not by any explicit priority.
  • transparent behaves differently than you might read it. It controls whether this component paints a background fill, not whether the OS window is transparent (there is no OS window).

Verification status

Verified, on this exact packaged source:

  • Typechecks clean under TypeScript 5.9 in strict mode, resolved against real react, react-native, react-native-paper and @types/react installations (RN 0.83.2, Paper 5.14.5).
  • npm pack produces a tarball containing the actual TSX source.

NOT verified — do not assume any of these:

  • Runtime behaviour of this package. The logic is production-proven in the app it was extracted from, but the code as packaged here has not been rendered — not on Windows, not on any platform.

  • Two extraction changes are new and unexercised. The original used app-local modules; both were generalised for this package:

    1. the overlay container was an app-internal View wrapper (adding only a FullStory fsClass prop) → now a plain react-native View that re-applies fsClass="fs-unmask" only when the unmask prop is set;
    2. the background colour came from an app-local theme context → now useTheme() from react-native-paper.

    These compile and typecheck; they have not been observed on screen. The Paper useTheme() swap in particular assumes your Paper theme's colors.background is the colour you want behind a non-transparent modal.

  • No example app, no automated tests, no CI.

  • Behaviour with nested modals, and interaction with other Portal users, is untested.

License

MIT

版本与发布记录

@facilitronworks/react-native-windows-modal 共发布 2 个版本,首次发布于 2026/07/18;过去 12 个月发布了 2 个版本。

版本发布日期距今
0.1.0-pre.1latest2026/07/213 个月前
0.1.0-pre.02026/07/183 个月前

关于 @facilitronworks/react-native-windows-modal 的常见问题

@facilitronworks/react-native-windows-modal 是什么?

A drop-in replacement for react-native's that actually works on react-native-windows new architecture, where the native WindowsModalHostView renders transparent flex content as an empty, unclosable shell. Delegates to the real on every oth

如何安装 @facilitronworks/react-native-windows-modal?

在项目目录运行 npm install @facilitronworks/react-native-windows-modal,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18.0.0)、react-native(>=0.76.0)、react-native-paper(>=5.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@facilitronworks/react-native-windows-modal 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@facilitronworks/react-native-windows-modal 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@facilitronworks/react-native-windows-modal 可以商用吗?

@facilitronworks/react-native-windows-modal 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@facilitronworks/react-native-windows-modal 的下载量有多少?

根据 npm 官方下载统计,@facilitronworks/react-native-windows-modal 在 2026/10/14 – 2026/11/12 这 30 天内被下载 4,248 次,日均约 142 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@facilitronworks/react-native-windows-modal 还在维护吗?

最新版本 0.1.0-pre.1 发布于 2026/07/21(3 个月前);至今共发布 2 个版本,其中过去 12 个月发布了 2 个;首次发布于 2026/07/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。