Skip to content
Expo Development Foundation
Esc
navigateopen⌘Jpreview
On this page

iOS Liquid Glass

Native glass via expo-glass-effect with capability gates and solid fallbacks.

Native iOS 26 Liquid Glass via expo-glass-effect — FABs, pills, cards, toolbars, popovers. Never rebuilt with blur + opacity. Expo iOS only: Android and web render the solid fallback, which is the same shape by construction.

When to use

  • Floating controls, pills, cards, toolbars, and popovers on iOS where the system glass material is available.
  • Anchors for native dropdown menus (MenuView) on device builds.

When not to use

  • Android or web surfaces — they get the fallback, so design the fallback first and treat glass as enhancement.
  • Every View — glass is for floating/elevated controls, not backgrounds.
  • SwiftUI (liquid-glass-design skill) or @expo/ui menus (expo-ui skill) — different owners.

File map

src/theme/glass.ts            # NATIVE_GLASS / NATIVE_MENU capability flags
src/components/glass-*.tsx    # glass control + same-shape solid fallback
npx expo install expo-glass-effect expo-blur expo-symbols
# only when using native menus:
npx expo install @react-native-menu/menu

Full procedure: the expo-ios-glass skill (mount gates, GlassContainer merging, header bars, popovers, motion budgets).

Key excerpts (adapted)

Gate on the capability API — never on isLiquidGlassAvailable(), never ANDed (iOS 26 betas can crash), and never && the transparency promise:

import { Platform, UIManager } from 'react-native';
import Constants from 'expo-constants';
import { isGlassEffectAPIAvailable } from 'expo-glass-effect';

export const NATIVE_GLASS = Platform.OS === 'ios' && isGlassEffectAPIAvailable();

function nativeMenuAvailable(): boolean {
  if (Platform.OS === 'web') return false;
  // Expo Go never ships MenuView — a real device/dev-client binary does.
  if (Constants.appOwnership === 'expo') return false;
  try {
    return UIManager.hasViewManagerConfig('MenuView');
  } catch {
    return false;
  }
}

export const NATIVE_MENU = nativeMenuAvailable();

Glass or same-shape fallback — "regular" for controls, "clear" for popovers only, isInteractive only on tappables, theme-driven colorScheme:

{NATIVE_GLASS ? (
  <GlassView glassEffectStyle="regular" isInteractive={tappable} colorScheme={scheme} style={s.pill}>
    {children}
  </GlassView>
) : (
  <View style={[s.pill, { backgroundColor: colors.card }]}>{children}</View>
)}

Live demo

Open the (patterns)/glass-card route in the showcase/ app: a glass pill and card under the capability gate, the live NATIVE_GLASS readout, and the solid fallback each renders off-platform. Expo Go is enough (note: MenuView is intentionally not demoed — Expo Go has no such native view).

Anti-patterns

  • Fake glass: rgba fill + opacity on a plain View.
  • Mounting on isLiquidGlassAvailable() (alone or ANDed with the capability API) — beta crash risk.
  • tintColor as an rgba “token” — fills are theme solids; tintColor takes the resolved theme color.
  • Ionicons where expo-symbols SymbolView belongs; zeego where MenuView belongs.
  • Unguarded MenuView in Expo Go — red Unimplemented component box. Gate on NATIVE_MENU, with an ActionSheetIOS fallback on iOS Go and an expandable list on Android/web.
  • Opacity or FadeIn animations on GlassView or its parents; motion budget is 180–240ms cubic on non-glass only, honoring Reduce Motion.
  • Hardcoded colorScheme="dark""auto" for system-following, otherwise the app’s in-app toggle value.

Provenance

Application labels are anonymized because the production source checkouts are private. They document observed SDK versions and file shapes, not publicly reproducible sources.

  • External expo-ios-glass skill (full procedure this page condenses).
  • Production Expo 57 app B src/theme/glass.ts: NATIVE_GLASS / NATIVE_MENU gates.
  • Production Expo 57 app C DESIGN.md §3–§5: glass token table, surface hierarchy, and the colorScheme-binding + no-washed-gray invariants.

Was this page helpful?