OpenAIExpoMay 26, 2026, 1:15 PM

Expo UI is now stable: SwiftUI and Jetpack Compose from a single import

A condensed section focused on the key takeaways first.

Original Post

Quick Digest

Summary

A condensed section focused on the key takeaways first.

openaienmodel: gpt-5-mini-2025-08-07

Expo UI is now stable: SwiftUI and Jetpack Compose from a single import

Key Points

  • Single import exposes SwiftUI and Compose
  • Drop-in native replacements for community packages
  • Stable SDK 56 with extensibility and Material 3

Summary

Expo UI reached stable in SDK 56. A single JavaScript import (@expo/ui) now exposes real SwiftUI-backed primitives on iOS and Jetpack Compose-backed primitives on Android via a universal layer — no JS re-implementation between your React code and native primitives. Expo UI is included in the default create-expo-app template and bundled in Expo Go, so you can try it immediately.

Key Points

  • One import for native primitives: import { Host, Row, Column, Switch, Slider, Text } from '@expo/ui' — the universal layer maps to SwiftUI on iOS and Compose on Android. Platform-specific packages remain available under @expo/ui/swift-ui and @expo/ui/jetpack-compose for specialized APIs.
  • Stable native APIs: SDK 56 finalizes renames and audits for SwiftUI and Compose parity; prop/modifier names align with platform docs. The API is mostly stable going forward.
  • Drop-in replacements: Native near-API-compatible replacements for several common community packages ship under @expo/ui/community, reducing extra native deps. Typical migration is a one-line import swap.
  • New capabilities: extensibility to author custom SwiftUI/Compose views and modifiers, useMaterialColors for Material 3 Dynamic Colors, Material Symbols integration, and useNativeState for UI-thread driven controls.
  • Notes and caveats: Web implementations are experimental and may lag native quality. The universal layer is a first iteration — report which components work or need platform-specific fallbacks.

Getting started

  • Create a new project: npx create-expo-app@latest --template default@sdk-56 (Expo UI included).
  • Try swapping imports for community packages to @expo/ui/community/... for fewer native deps.
  • Test on devices (iOS/Android) and open issues or PRs if you hit gaps.

Practical advice

  • Use Expo UI for OS-native-feeling screens (settings, pickers, sheets). Mix with React Native Views for custom designs, Skia for custom drawing, and web/DOM components where appropriate.
  • Keep platform-specific imports available for advanced platform-only APIs.

Stable means the foundation is firm — proceed to migrate incrementally and file feedback as you try components.

Full Translation

Translations

A translation section that keeps the flow of the original article.

openaijamodel: gpt-5-mini-2025-08-07

Expo UI が安定版に:1つの import で SwiftUI と Jetpack Compose を利用可能に

Expo UI が安定版になりました — 本番利用に対応

Expo UI が安定(stable)になり、本番環境で使えるようになりました。単一の JavaScript import で、SwiftUI / Jetpack Compose を裏で使うネイティブプリミティブビューが無料で手に入ります。また、既存のコミュニティパッケージに対するドロップイン置換ビューも含まれており、package.json からその依存を削除できます。Expo UI のユニバーサルコンポーネントは Android、iOS、Web 全てで動作します。

SDK 56 から、@expo/ui を使うと iOS では SwiftUI、Android では Jetpack Compose を利用できます。本物のネイティブプリミティブであり、JavaScript で再実装されたものではありません。Expo UI は create-expo-app のデフォルトテンプレートに組み込まれ、Expo Go にバンドルされているため、プロジェクトを作成または開くとすぐに試せます。

このマイルストーンは3つの SDK サイクルにわたる反復の成果です:

  • SDK 53 — 最初のプロトタイプ。SwiftUI と Jetpack Compose のいくつかのリーフコンポーネント。
  • SDK 54 — Host、SwiftUI modifier、および FormList のようなコンテナビュー。hot-chocolate のショーケースでこれだけで完全なアプリを作れることが示されました。
  • SDK 55 — Jetpack Compose のサポートはベータ化、SwiftUI 側は監査され、各コンポーネント、prop、modifier 名が Apple のドキュメントと 1 対 1 で対応するようになりました。
  • SDK 56 — Jetpack Compose 側も同様の監査を受け、両者の上にユニバーサルレイヤーが置かれ、長く使われてきたコミュニティパッケージのうちいくつかにネイティブのドロップイン置換が提供されました。

以下で各要素が実際にどう提供されるかを説明します。

1 つの import で全プラットフォーム:ユニバーサルコンポーネント

SDK 56 で最も目立つ新機能はユニバーサルレイヤーです。@expo/ui/swift-ui@expo/ui/jetpack-compose から個別にインポートする代わりに、@expo/ui からインポートするだけで、プラットフォームに応じた実装が内部で選ばれます:

import { Host, FieldGroup, Row, Switch, Slider, Text, Spacer } from '@expo/ui';

内部では各ユニバーサルコンポーネントはラッパーに過ぎません。iOS では SwiftUI バージョンをレンダリングし、Android では Compose バージョンをレンダリングします。間に JS の再実装はなく、どちらの場合も実際のプラットフォームコンポーネントを使用しています。

レイアウトプリミティブ、テキスト、入力、コントロール、シートなどが対象です:HostRowColumnScrollView など。詳細はユニバーサルコンポーネントのドキュメントを参照してください。

いくつかの命名上の選択に注意してください:SwitchToggle ではない)、Column / RowHStack / VStack ではない)といった具合に、ユニバーサルレイヤーは React Native 開発者が日常的に使う名前に寄せています。SwiftUI 風の名前が欲しい場合は、@expo/ui/swift-ui の下にそのまま残っています。

ユニバーサルコンポーネントのショーケース:設定画面

FieldGroup は SwiftUI の Form に相当するユニバーサルコンポーネントで、セクション付きのグループ化リスト、セクションタイトルやフッター、プラットフォーム適切なスタイリングを iOS / Android の両方で提供します。以下のスニペットには複合コンポーネントが含まれています:FieldGroup.Section(セクション)、FieldGroup.SectionFooter(下に表示される灰色の説明テキスト)など。

import { useState } from 'react';
import { Button, FieldGroup, Host, Row, Slider, Spacer, Switch, Text } from '@expo/ui';

export default function SettingsScreen() {
  const [notifications, setNotifications] = useState(true);
  const [sounds, setSounds] = useState(false);
  const [brightness, setBrightness] = useState(0.6);

  return (
    <Host style={{ flex: 1 }}>
      <FieldGroup>
        <FieldGroup.Section title="Notifications">
          <LabeledRow label="Push notifications">
            <Switch value={notifications} onValueChange={setNotifications} />
          </LabeledRow>
          <LabeledRow label="Sounds">
            <Switch value={sounds} onValueChange={setSounds} />
          </LabeledRow>
          <FieldGroup.SectionFooter>
            <Text textStyle={{ fontSize: 13, color: '#6c6c70' }}>
              Notification previews can expose sensitive content on the lock screen.
            </Text>
          </FieldGroup.SectionFooter>
        </FieldGroup.Section>

        <FieldGroup.Section title="Display">
          <LabeledRow label="Brightness">
            <Slider value={brightness} onValueChange={setBrightness} />
          </LabeledRow>
        </FieldGroup.Section>

        <FieldGroup.Section>
          <Row alignment="center" style={{ padding: 12 }}>
            <Spacer flexible />
            <Button variant="outlined" onPress={() => alert('Signed out')} label="Sign out" />
            <Spacer flexible />
          </Row>
        </FieldGroup.Section>
      </FieldGroup>
    </Host>
  );
}

function LabeledRow({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <Row alignment="center" spacing={16}>
      <Text>{label}</Text>
      <Spacer flexible />
      {children}
    </Row>
  );
}

同じコードで両プラットフォームに対応します。iOS では inset-grouped セクションをもつ SwiftUI の Form とシステムの Switch / Slider を、Android では Material 3 のグループ化リストを得ます。.ios.tsx / .android.tsx の分割は不要です。

@expo/ui/swift-ui@expo/ui/jetpack-compose の各プラットフォーム専用パッケージは、SwiftUI の glassEffect() や Compose の DockedSearchBar のような個別 API が必要な画面のために引き続き利用可能です。同じ Host 内で混在させることもできます。

ユニバーサルレイヤーに関する早期の注意点

  • これは最初のイテレーションです。いくつかのユニバーサルコンポーネントは三つのプラットフォームすべてでうまく動作しますが、SwiftUI、Compose、Web が同一のプリミティブを提供しない場合もあり、その差分により期待通りにならないコンポーネントもあります。今サイクルで、どのユニバーサルコンポーネントが有用か、どれが不十分か、欠けているのは何か、そしてどこで直接 @expo/ui/swift-ui@expo/ui/jetpack-compose を使うことになったかを教えてください。
  • Web は実験的です。ユニバーサルコンポーネントは Web 実装を持ちますが、ネイティブと同じ品質水準にはまだ達していません。今後の SDK でフィードバックを元に改善していく予定です。

安定したネイティブ API と新機能

監査が完了したことで、Apple ドキュメントや Google ドキュメント、その他のサンプルにある SwiftUI / Compose のコードは、ほぼ検索と置換だけで Expo UI 上で動くようになっています。コンポーネント名、prop 名、modifier 名は同じです。

具体的には:SwiftUI の ToggleForm@expo/ui/swift-ui 内で名前がそのまま残り、Compose の LazyColumn@expo/ui/jetpack-compose 内で名前が変わりません。prop と modifier 名も同じルールに従います。SDK 56 には Compose 監査に基づく最終的なリネームが含まれており、このリリース以降 API は概ね安定しています。

この監査と並行して、いくつかの新機能が今サイクルで導入されました:

  • 拡張性(Extensibility)。SwiftUI と Jetpack Compose には何百ものビューと modifier が存在し、そのすべてを @expo/ui でラップするのは不可能です。そこで独自の SwiftUI / Jetpack Compose ビューや modifier を作成できるようにし、Expo UI がレイアウト、prop、イベントの配線を処理します。Learn more for SwiftUI and Jetpack Compose.
  • Material 3 Dynamic Colors。useMaterialColors でシステムテーマに従う Material 3 Dynamic Colors を取得できます。
  • Material Symbols カタログ。Icon コンポーネントは @expo/material-symbols と組み合わせて Material Symbols の全カタログにアクセス可能です。
  • Worklet 駆動のネイティブ状態。SwiftUI と Jetpack Compose 向けの useNativeState により、ちらつきのない UI スレッド駆動のコントロールを作れます。これについては追って専用の投稿を行います。

コミュニティパッケージのドロップイン置換

多くの React Native アプリはピッカーやスライダーといったプラットフォームプリミティブのためにコミュニティ製パッケージのスタックを使いがちです。それぞれがネイティブ依存であり、別のパッケージ追加、別のリリーススケジュール、SDK アップグレード時の壊れる要因になり得ます。SDK 56 では @expo/ui/community の下で7つのネイティブ置換が提供されます。

各置換は置き換えるパッケージとほぼ API 互換です。多くのマイグレーションはインポートの一行置換で済みますが、Expo UI が UIKit / Android Views ではなく SwiftUI / Jetpack Compose をバックエンドにしているため、一部の prop は差異があります:

// Before
import DateTimePicker from '@react-native-community/datetimepicker';

// After
import DateTimePicker from '@expo/ui/community/datetime-picker';

詳細はドロップイン置換のドキュメントを参照してください。

効果としては、ネイティブ依存が減り、アップグレードの一貫したストーリーが得られ、同じ監査済みの基盤の上で動くようになります。

アプリの目的に応じて適切なツールを選ぶ

react-dom では divspan を書くように、@expo/ui では ColumnRow を書きます。つまり、Expo UI は UI コンポーネントライブラリやデザインシステムではなく、iOS と Android が既に提供しているプリミティブを React コンポーネントとして公開するものです。

しかし Expo UI が唯一の選択肢というわけではありません。Expo フレームワークの利点のひとつは、単一アプリ内でツールキットを混在できることです:

  • カスタムデザイン(ブランドページや独自のデザインシステムなど)には React Native の View / Text を使ってください。これはスタイリングに依存しないプリミティブで、CSS スタイル風の prop、flexbox レイアウト、NativeWind 等のスタイリングライブラリが利用できます。
  • OS に馴染むネイティブ感(設定、モーダル、ピッカー、シートなど)を期待する UI には Expo UI を使ってください。Host 内ではレイアウトは SwiftUI / Compose のプリミティブ(HStack/VStack、Row/Column)であり、Yoga の flexbox ではありません。
  • 自前で描画する部分(チャート、カスタムシェーダー、独自アニメーション)には react-native-skia を使います。
  • 3D や GPU 計算が必要な場合は react-native-webgpu と TypeGPU を使います。
  • Web エコシステム側に既に答えがある画面(例:shadcn)では、Web 用の DOM コンポーネントを WebView で実行するか、Web 上ではそのまま DOM を使う方法があります。

重要なのは、これらのオプションはコンポーネントレベルで合成できることです。React Native、Expo UI、Skia、WebGPU / TypeGPU、DOM コンポーネントを同じ画面・同じビュー階層で混ぜて使い、ユニバーサルかつ深くネイティブな体験を構築できます。

ショーケースアプリの更新

hot-chocolate を SDK 56 に更新しました。元々は SwiftUI 専用だったアプリがユニバーサルコンポーネントを持ち、Android と Web でも動作するようになりました。

Apple TV と Android TV

Expo UI は Apple TV と Android TV の両方でも動作するようになりました。多くは Douglas Lowder の貢献によるものです。ほとんどのコンポーネントと API はサポートされていますが、TV ではそもそも使えないネイティブの SwiftUI / Compose API も一部あります。ExpoUITV アプリはサポートされているほぼすべての API を表示し、TV とモバイルの両方で動作します。

コミュニティへの感謝

Expo UI が安定版になるまでにはコミュニティの存在が欠かせませんでした。SDK 56 サイクルは特にコミュニティ主導でした。みなさんは issue を上げ、API 監査を手伝い、コンポーネントを追加し、我々がまだ見ていなかったクラッシュの修正を提供してくれました。

特別な感謝(原文のまま):

2hwayoung, AKSHAY JADHAV, Axel, Benjamin Komen, Beto, Christian Wooldridge, Dennis Morello, Dylan, Eliot Gevers, fedeciancaglini, Gregory Moskaliuk, Hugo Extrat, hypnokermit, Ian Berry, Isaiah Hamilton, JeroenG, Joss Mackison, K.Dileepa Thushan Peiris, Kfir Fitousi, kimchi-developer, Leonardo E. Dominguez, Liès, Loic CHOLLIER, Louis, lucabc2000, Petr Chalupa, Pflaumenbaum, Ray, Sam Shubham, Shubh Porwal, starsky-nev, Suvesh Moza, Terijaki, ThiMal, Yousof Abouhalawa, Zhovtonizhko Dmitriy

テスト、issue 提出、オフィスアワー参加、または Discord で作業中に一緒に過ごしてくれたすべての人に感謝します。あなたたちがこのリリースを PR と同じくらい形作りました。

どこから始めるか

  • npx create-expo-app@latest --template default@sdk-56 — Expo UI はデフォルトのテンプレートに入っているので、HostSwitchPicker がすぐにインポート可能です。
  • 既存アプリが前述のドロップイン対象パッケージを使っている場合は、まずインポートを 1 箇所だけ差し替えて試してみてください。
  • ショーケースアプリ(hot-chocolate と ExpoUITV)を参照してください。
  • 問題を報告したり PR を送ったり、Discord で話しかけてください。

「安定(stable)」は完成を意味しません — 次の構築ラウンドのために基盤が十分に固まったことを意味します。Expo UI の安定化へようこそ。さあ、ネイティブな何かを作りましょう。