Integration
Consume Canvas, a universal React Native UI kit, on native and on the web.
Installation
Canvas is published as @nannier/canvas and lists react, react-native, and react-native-svg as peer dependencies.
tsx
Optional peers
These back specific features. Skip any you don't use; Canvas still works and degrades gracefully.
react-native-qrcode-svg: renders the QRCode component; without it, QRCode shows an empty labeled frame.
expo-video: plays clips in the Video component (a native module: rebuild the app after installing it). Without it, Video keeps its size and poster in a labeled frame.
expo-glass-effect: native Liquid Glass for eligible functional surfaces on supported iOS 26+. Without it, shared rendering uses available frost or the complete opaque solid skin.
expo-blur: static frost on iOS, including content below iOS 26 Liquid Glass, and supported legacy Android frost. The browser frost is built in and does not need this peer.
@nannier/canvas-blur: optional Android 12+ capture and frost for Expo SDK 57 native builds, sampling a safe live backdrop: the page behind an overlay or a native Modal (see Overlay hosting). Install it in the native app and rebuild so Expo autolinks the module. Capture runs only while needed. Missing or unsafe material restores the complete opaque solid skin.
react-native-safe-area-context: safe-area insets for the overlays that use them (Drawer, sheets); without it, they fall back to fixed insets.
expo-clipboard: backs the CodeBlock copy chip on native; the web covers copy with navigator.clipboard either way.
tsx
Quick start
Wrap the app once in ThemeProvider and use the components. This is the whole usage API, and it is the same code on iOS, Android, and the web, the component props never change per platform. On native (Expo or React Native CLI) it is all you need beyond your normal RN bundler; the web adds two setup steps, the react-native-web alias and canvas.css, shown below.
tsx
Overlay hosting
Mount one OverlayProvider inside ThemeProvider at the app's root, outside any scroller. It fills the window by default, and menus, selects, popovers and other floating content render above the whole page in its outlet, placed against the window. Without one, they render inline where they are declared. On Android with @nannier/canvas-blur, the root provider's page content is also the capture plane that glass overlays and native modals sample for their frost; a provider that does not fill its window has none, and glass there keeps its solid skin.
tsx
Native modals
A React Native Modal is a native window of its own on Android, outside the root provider's page. GlassModalBlurTarget is the bridge that hands that page to the Modal, so the glass inside it frosts the page behind the Modal. Canvas Drawer and ActionSheet use it already. An app hosting its own Modal wraps the Modal's content in it; it renders no view of its own.
tsx
- Put it directly inside the Modal's content, outside the Modal's own OverlayProvider separateWindow, which gives the menus and popovers inside the Modal a host in its window
- It frosts on Android 12 or newer with @nannier/canvas-blur in an Expo SDK 57 native build, under an app-root OverlayProvider that fills the window. iOS (Liquid Glass and its frost) and the web (a backdrop filter) need no plane, so there it provides nothing, as it does on Android with no root provider: the Modal's surfaces render as they would without it
- It belongs only in a Modal. Placed in the main window, around page content or in a react-native-screens or expo-router modal screen (which shares the main window on Android), it hands its surfaces the plane they sit inside, which can never be sampled: @nannier/canvas-blur 0.2 or newer reports that once the frost has mounted, and every surface under the bridge then takes its complete solid skin (until the report, and for good with an older module, they show a tint with no blur)
- On Android a Modal's frost samples the outermost OverlayProvider's page content only. It leaves out what is stacked above the page: the root outlet's toasts and pinned cards, any Modal beneath (a drawer opened from a drawer frosts the page, not the first drawer) and the Modal's own scrim and surfaces. iOS and the web blur the whole stack behind the Modal
Blur planes
A glass surface in the page has no plane of its own on Android: the window's plane contains it, and a frost may not sample a plane that holds it, so it keeps its solid skin. A page whose backdrop sits behind its glass (a gradient behind a frosted panel) can make that backdrop the plane. useGlassBlurPlane makes one, GlassBlurPlane wraps the backdrop it captures, and GlassBlurScope offers it to the surfaces inside it.GlassBlurPlaneHandle is the plane's type, and GlassBlurPlaneProps and GlassBlurScopeProps type the two components. The Canvas docs frame their pages this way.
tsx
- The plane fills its parent, as a backdrop does, and takes no touches; keep the backdrop inside it and the surfaces that frost it outside it
- Scope only the surfaces that sit directly over the backdrop: every glass surface inside the scope frosts the plane, including those nested in a scoped card, so content with glass of its own belongs outside it
- It frosts on Android 12 or newer with @nannier/canvas-blur in an Expo SDK 57 native build. Without it, and on iOS and the web (whose materials need no plane), both render their children only, and the surfaces render as they would without them
- A surface placed inside its own plane can never sample it: @nannier/canvas-blur refuses that plane and the surface keeps its solid skin
Accessibility
The bridge adds nothing to the accessibility tree: the Modal's content reaches VoiceOver, TalkBack and the keyboard exactly as without it. The Modal's own semantics stay yours to give: its onRequestClose for Android's Back and the Escape key, and accessibilityViewIsModal so VoiceOver ignores the page behind it, as Drawer and ActionSheet set them. Reduce Transparency and Increase Contrast give every surface inside the Modal its opaque skin, as they do in the page.
Web setup: react-native-web
The component code above does not change on the web; this is build configuration only. In the browser Canvas runs through react-native-web, so alias react-native to react-native-web in your bundler. Here is a Vite config (Metro and webpack take the same alias). canvas.css needs no plugin: it is plain CSS that any bundler resolves (next section).
tsx
Theming on the web
Components theme themselves through ThemeProvider (they read the active scheme's tokens with useTheme). On the web you also import the CSS token layer so the DOM theme helpers and any custom CSS you write stay in sync. The helpers toggle attributes on <html>; a small hook mirrors the .dark class back into the provider so the React Native components follow the page.
tsx
The scheme-sync hook
tsx
Theme, surface, density helpers
setTheme and toggleTheme toggle the .dark class to update the CSS color tokens. setSurface andsetDensity persist preferences and set the CSS handoff attributes for material mode, page backdrop, and supported card/table spacing. Read the choices back (getSurface, getDensity) and sync them into your ThemeProvider and component props, the way the hook above syncs the scheme. All four are web-only (they touch document and localStorage).
tsx
Reading tokens (web)
token() reads the live computed value of a CSS custom property off document.documentElement, so it reflects the current theme. It needs canvas.css loaded and is web-only; on native, read theme values from useTheme() instead.
tsx
Building on Canvas
Compose existing Canvas components and use their semantic boolean props. Use Row, Column, Container, and Grid for arrangement. If a component lacks a real functional capability, extend it in the kit so every consumer gets the same behavior, accessibility, and platform skin.
tsx
Package exports
@nannier/canvas
Components, the style foundation (ThemeProvider, useTheme, useResponsive, shadow, alpha, and the View / Text / Pressable / TextInput / ScrollView primitives), and the token / theme utilities. Ships as compiled JavaScript with TypeScript declarations.
@nannier/canvas/styles/canvas.css
The CSS token layer: plain custom properties, plus the .dark scheme hook and the [data-platform] skin hook. Web only, no build step.