11 人が閲覧(直近 30 日) モバイル(iOS・Android)

Expo UI(@expo/ui)とは?SwiftUI・Jetpack Composeを描く使い方とSDK 58の変更点

Expo UI(@expo/ui)とは?SwiftUI・Jetpack Composeを描く使い方とSDK 58の変更点

Expo UI(パッケージ名 @expo/ui)は、React NativeのJSXからiOSではSwiftUI、AndroidではJetpack Composeの部品をそのまま描くExpo公式のコンポーネント群です。ボタンやスイッチをJavaScriptで描き直すUIライブラリではなく、OSが持つ部品をReact側から呼び出す橋渡しの層にあたります。SDK 56で安定版になり、2026年10月時点の最新はSDK 58対応の58.0.25です。この記事では、インストールからimport先の選び分け、よく使う部品のコード、既存パッケージの置き換え、SDK 58で変わった挙動までを、Expo公式のドキュメントと変更履歴をもとに整理します。

まとめ

  • @expo/uiは「UIキット」ではなく、SwiftUIとJetpack Composeの部品を1対1でReact Nativeへ持ち込む層。部品は基本的に Host で囲んで使い(BottomSheetとcommunity版は内部でHostを用意する)、Host の中ではflexbox(Yoga)が効かない。
  • SDK 58の既定テンプレートには最初から入っている。後から足すなら npx expo install @expo/ui。App Store版Expo Goも58.0.5(2026年10月22日)で、開発ビルドなしで試せる。
  • import先は3つ。iOS・Android・Webで同じコードを使うなら @expo/ui(universal・18項目)、OS固有の部品や修飾が要るなら @expo/ui/swift-ui(47項目)と @expo/ui/jetpack-compose(51項目)。
  • @gorhom/bottom-sheetや@react-native-picker/pickerなど8つのコミュニティパッケージは、import先を @expo/ui/community/… に変えるだけで置き換えられる。ただし一部のpropsは非対応。
  • SDK 58ではiOSの Host が上寄せになり、HStack・VStack のspacing既定が変わり、Androidの RNHostView から style が消えた。パッチ版でも既定値が動くので、spacing を明示し、リリースビルドで確かめる。
  • Webが主戦場のアプリや、全OSで同じ独自デザインに揃えたいアプリには向かない。Webの実装はネイティブではなくreact-domとreact-native-webによるJavaScript実装になる。

Expo UI(@expo/ui)の正体:UIキットではなくネイティブ部品の橋渡し

Expo UIの公式リファレンスは冒頭で「Expo UI is not another UI library」と書き、SwiftUIとJetpack Composeの基本部品をReact Nativeへ持ち込むものだと説明しています。部品はネイティブ側と1対1で対応し、SwiftUIの Toggle や Form、Composeの LazyColumn は同じ名前のまま使えます。AppleやGoogleのサンプルコードを読めば、Expo UIでどう書くかの見当がつく設計です。

使い方の基本は、Expo UIの部品を Host の内側に置くことです。Host はReact NativeとSwiftUI・Composeの境目にあたり、iOSでは UIHostingController でSwiftUIのビューをUIKitの中に表示します。公式FAQによると、flexboxのstyleが効くのは Host 自身だけで、内側の配置はAndroidなら Row・Column、iOSなら HStack・VStack で組みます。例外はuniversalの BottomSheet とcommunity版の互換部品で、Hostの外に置かれると内部でHostを用意するため、後述のコードのようにHostの外に書けます。

npmでの公開は2025年1月21日に始まり、SDK 53の試作、SDK 54での Host とSwiftUI修飾子の追加、SDK 55でのJetpack Compose対応のベータ提供を経て、SDK 56で安定版になりました。安定版の告知(2026年5月26日)は、SDK 56で名前の変更は最後で、以降のAPIは「mostly stable」としています。ライセンスはMITで、Expoの他のパッケージと同じく無料で使えます。

react-native-paperなどのUIライブラリとの違い

react-native-paperやreact-native-elementsは、Material DesignなどのデザインをJavaScriptとReact Nativeの部品で再現し、全OSで同じ見た目に揃えます。Expo UIは逆で、iOSではSwiftUIの部品、AndroidではMaterial 3のCompose部品が出るため、OSごとに見た目が違います。公式FAQも、Expo UIは「opinionated design kit」ではなく基本部品のライブラリだと書いています。テーマ付きの部品一式(いわゆるUIキット)を探しているなら、目的が違うと考えてください。React NativeでTailwind CSSの書き方を使いたい場合は、NativeWindの導入手順の方が近い選択肢です。

expo-system-uiとは別のパッケージ

名前が似ている expo-system-ui は、Reactの描画範囲の外にあるUI、つまりルートビューの背景色と、Androidでのライト・ダークの固定を扱うパッケージです(公式リファレンス)。ボタンやリストのような部品は持っていません。SDK 58の既定テンプレートには @expo/ui(~58.0.25)と expo-system-ui(~58.0.7)の両方が入っているため、package.jsonで並んでいても役割は別物です。

Expo UIを動かす:インストールとExpo Goでの確認

SDK 58の既定テンプレート(expo-template-default 58.0.30)は、依存関係に @expo/ui を最初から含んでいます。新しく作るプロジェクトでは追加の作業は要りません。既存のプロジェクトに足す場合は、SDKに合う版を選ばせるため npm install ではなく npx expo install を使います。

npx create-expo-app@latest my-app
cd my-app
# 既存プロジェクトに足す場合
npx expo install @expo/ui

Expo を入れていない素のReact Nativeアプリでは、先に expo パッケージの導入が要ります。useNativeState をworkletでUIスレッドから同期更新する使い方には react-native-worklets も必要です(SDK 57でreact-native-reanimatedへの依存は外れました)。動作条件はSDKの対応表のとおりです。

項目 SDK 58時点の値
@expo/ui の最新版 58.0.25(2026年10月28日)
React Native / React 0.88 / 19.3.0
iOS / Xcode 16.4以上 / 26.4以上
Android 7以上(compileSdk 37)
Node.js 22系は22.13以上・24系は24.3以上・26以降(23と25は非対応)
対応プラットフォーム Android・iOS・tvOS・Web(universalのみ)
App Store版 Expo Go 58.0.5(2026年10月22日)

Expo UIはExpo Goに同梱されているため、開発ビルドを作らなくても実機で確認できます(自作のSwiftUI・Composeビューを組み込む場合は開発ビルドが要ります)。Expo Go・開発ビルド・EASの役割分担はExpoの仕組みと採用判断で詳しく扱っています。

3つのimport先の選び分け(universal・swift-ui・jetpack-compose)

@expo/uiのimport先は3つあり、どれを使うかで書ける部品と名前が変わります。universalのリファレンスによると、universalの部品はAndroidではjetpack-compose、iOSではswift-uiへ処理を回し、Webではreact-domかreact-native-webで作ったJavaScript実装を使います。

import先 対応OS 公式一覧の項目数 名前の例
@expo/ui(universal) Android・iOS・Web 18 Switch、Row、Column
@expo/ui/swift-ui iOS・tvOS 47 Toggle、HStack、VStack
@expo/ui/jetpack-compose Android 51 Switch、Row、LazyColumn
@expo/ui/community/… Android・iOS・Web 8 既存パッケージ互換

項目数は公式一覧の行数で、Host・修飾子・useNativeState なども1行に数えています。SDK 56の一覧ではswift-uiが43、jetpack-composeが48だったので、2版で7項目増えました。

判断は単純で、まずuniversalで書き、universalに無い部品や修飾子が要る画面だけswift-ui・jetpack-composeを直接使います。同じ Host の中で混ぜても構いません。注意したいのは名前の違いです。universalはReact Nativeに慣れた人向けに Switch・Row・Column を使い、swift-uiは Toggle・HStack・VStack とSwiftUIの名前のままです。SDK 58のテンプレートでTypeScriptの型検査(tsc 6.0.3)にかけると、universalから Toggle を、jetpack-composeから HStack をimportした行はどちらもTS2305(存在しないexport)で止まります。SwiftUIやComposeのサンプルを書き写すときは、import先とセットで名前を確認してください。

Expo UIの主要コンポーネントの実装例

ここに載せる5本のコードは、SDK 58の既定テンプレートに@expo/ui 58.0.25を入れ、tsc –noEmitで型エラーが出ないことを確かめたものです。確かめたのは型の整合までで、端末上の表示や操作は別に確認が要ります。見た目は端末のOSによって変わります。

ボタン・スイッチ・スライダーによる設定カードの実装

universalの Button は filled・outlined・text の3種類の見た目を variant で選びます。Host に matchContents を付けると、Hostが中身の大きさに縮みます。

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

export default function SettingsCard() {
  const [notify, setNotify] = useState(true);
  const [volume, setVolume] = useState(0.5);

  return (
    <Host matchContents>
      <Column spacing={12} style={{ padding: 16 }}>
        <Row alignment="center">
          <Text>通知を受け取る</Text>
          <Spacer flexible />
          <Switch value={notify} onValueChange={setNotify} />
        </Row>
        <Slider value={volume} onValueChange={setVolume} />
        <Button variant="outlined" label="保存" onPress={() => console.log(notify, volume)} />
      </Column>
    </Host>
  );
}

公式サンプルの多くは、useColorScheme でダークモード時の文字色を自分で textStyle に渡しています。背景を独自に塗る画面では、文字色も明示しておくと読みにくくなる事故を防げます。

リストとボトムシート(モーダル)の連携

universalの List は、AndroidではComposeの LazyColumn、iOSではSwiftUIの List で描かれます。BottomSheet は表示状態を isPresented で持ち、下へのスワイプや背景のタップで onDismiss が呼ばれます。

import { useState } from 'react';
import { Host, List, ListItem, BottomSheet, Column, Text, Button } from '@expo/ui';

const ITEMS = [
  { id: 'wifi', name: 'Wi-Fi' },
  { id: 'bt', name: 'Bluetooth' },
];

export default function ListWithSheet() {
  const [selected, setSelected] = useState<string | null>(null);

  return (
    <>
      <Host style={{ flex: 1 }}>
        <List>
          {ITEMS.map(item => (
            <ListItem key={item.id} onPress={() => setSelected(item.name)}>
              {item.name}
            </ListItem>
          ))}
        </List>
      </Host>
      <BottomSheet isPresented={selected !== null} onDismiss={() => setSelected(null)}>
        <Column spacing={12}>
          <Text>{`${selected ?? ''} の設定`}</Text>
          <Button label="閉じる" onPress={() => setSelected(null)} />
        </Column>
      </BottomSheet>
    </>
  );
}

行数が多いリストには List.ForEach を使います。data と keyExtractor を渡すと、見えている付近の行だけを描画して使い回す方式で、swift-uiの List では58.0.0、universalの List では58.0.23から使えます。SDK 58の変更履歴では、List.ForEach に要素を子として並べる書き方は非推奨になりました。iOSでシートの上に別のシートを重ねるときは、2枚目を1枚目の中身の中に入れ子にします。SwiftUIの sheet 修飾子の制約によるものです。

iOS専用のメニューとLiquid Glass

メニューやガラス表現のようにOS固有の部品・修飾子は、swift-uiから直接importします。修飾子は @expo/ui/swift-ui/modifiers から取り出し、modifiers に配列で渡します。

import { Host, Menu, Button } from '@expo/ui/swift-ui';
import { glassEffect, padding } from '@expo/ui/swift-ui/modifiers';

export function IosMenu() {
  return (
    <Host matchContents>
      <Menu label="並べ替え" systemImage="arrow.up.arrow.down">
        <Button label="新しい順" onPress={() => {}} />
        <Button label="古い順" onPress={() => {}} />
      </Menu>
    </Host>
  );
}

export function GlassButton() {
  return (
    <Host matchContents>
      <Button label="保存" onPress={() => {}} modifiers={[padding({ all: 12 }), glassEffect()]} />
    </Host>
  );
}

このファイルはiOS専用なので、Androidでは同じ役割の画面を @expo/ui/jetpack-compose の DropdownMenu で別に書くか、両OSで動くcommunity版の Menu を使います。glassEffect はSwiftUIの glassEffect(_:in:) を呼ぶ修飾子で、Apple側の提供はiOS 26以降です。SwiftUI側での使い方はLiquid GlassのSwiftUI実装、systemImage に渡す記号名はSF Symbolsの探し方で確認できます。

RNHostViewによるReact Native部品の埋め込み

Expo UIのレイアウトの中にReact Nativeの View を置くときは RNHostView で囲みます。測るのは最初の子1つだけなので、複数の部品は1つの View にまとめてから渡します。

import { Host, Column, RNHostView, Text } from '@expo/ui';
import { View, Text as RNText } from 'react-native';

export default function Bridge() {
  return (
    <Host matchContents>
      <Column spacing={12} style={{ padding: 16 }}>
        <Text>ネイティブ側のラベル</Text>
        <RNHostView matchContents>
          <View style={{ padding: 12, backgroundColor: '#9B59B6', borderRadius: 8 }}>
            <RNText style={{ color: '#fff' }}>React Nativeの View</RNText>
          </View>
        </RNHostView>
      </Column>
    </Host>
  );
}

SDK 58でAndroidの RNHostView から style propが削除されました。大きさや位置は modifiers で指定します。境目を何度もまたぐと、レイアウトがSwiftUI・Composeとyogaのどちらに従うか追いにくくなります。公式FAQも境目を明確に保つよう勧めています。

既存パッケージから@expo/ui/communityへの移行

よく使われる8つのコミュニティパッケージには、APIを合わせた置き換え版があります(Drop-in replacements)。中身はAndroidではJetpack Compose、iOSではSwiftUIです。

置き換え元 置き換え先のimport
@gorhom/bottom-sheet @expo/ui/community/bottom-sheet
@react-native-community/datetimepicker @expo/ui/community/datetime-picker
@react-native-masked-view/masked-view @expo/ui/community/masked-view
@react-native-menu/menu @expo/ui/community/menu
react-native-pager-view @expo/ui/community/pager-view
@react-native-picker/picker @expo/ui/community/picker
@react-native-segmented-control/segmented-control @expo/ui/community/segmented-control
@react-native-community/slider @expo/ui/community/slider

多くはimport行を書き換えるだけで動きます。セグメントコントロールと日付ピッカーの例です。

import { useState } from 'react';
import SegmentedControl from '@expo/ui/community/segmented-control';
import DateTimePicker from '@expo/ui/community/datetime-picker';

export default function DropIn() {
  const [index, setIndex] = useState(0);
  const [date, setDate] = useState(new Date());
  return (
    <>
      <SegmentedControl
        values={['日', '週', '月']}
        selectedIndex={index}
        onChange={event => setIndex(event.nativeEvent.selectedSegmentIndex)}
      />
      <DateTimePicker value={date} mode="date" onChange={(_e, d) => d && setDate(d)} />
    </>
  );
}

「APIが同じ」は全propsが同じという意味ではありません。たとえばSegmentedControlは、values に画像を渡せず文字列だけで、momentary・backgroundColor・fontStyle・activeFontStyle は非対応、tintColor は当初Androidだけの対応で、58.0.22でiOSにも効くようになりました(移行ガイドはAndroidのみの記述のままです)。置き換える前に、各ページの「Migrating from」節で自分が使っているpropsが残っているかを確認してください。新しく書くコードなら、community版のPickerよりuniversalの Picker を使うよう公式は勧めています。

SDK 57・58での変更点とアップグレード時の確認箇所

安定版になった後も、@expo/uiは更新が速いパッケージです。SDK 58向けだけで58.0.0(2026年9月10日)から58.0.25(10月28日)まで26版が出ています。SDK 56の内容で止まっている解説を読んで書いたコードは、次の点で挙動が変わります。

版 変更 対応
57.0.0 workletがreact-native-workletsを直接使う reanimated経由の前提を外す
57.0.0 universal・swift-uiのHostに seedColor を追加 1色からテーマを生成できる
58.0.0 iOSのHostが中央寄せから上寄せへ flex: 1 のHostの配置を見直す
58.0.0 HStack・VStackのspacing未指定がSwiftUI既定に 旧配置なら spacing={0}
58.0.0 AndroidのRNHostViewからstyleを削除 modifiersへ移す
58.0.23 universalのRow・ColumnのiOS既定spacingが0に spacingを明示する
58.0.24 Androidのリリースビルドでスライダー操作時のクラッシュを修正 58.0.24以上へ上げる

58.0.23の変更は、Androidとweb(既定0)にiOSを揃えたものです。swift-uiの HStack・VStack は未指定ならSwiftUIの既定間隔、universalの Row・Column は未指定なら0と、同じSDK 58の中でも既定が違います。パッチ版の更新で余白が変わらないよう、spacing は常に数値で書いておくのが確実です。

58.0.24のクラッシュはリリースビルドでだけ起きるものでした。Expo Goや開発ビルドで動いたことは、リリースビルドで動くことの保証になりません。ストア提出前に一度はリリースビルドで触っておく手順を残してください。変更内容は@expo/uiのCHANGELOGに版ごとに載っています。SDK全体のアップグレード手順はExpo SDK 58・57・56の変更点にまとめています。

Expo UIを採用する条件・避ける条件

採用してよいのは、設定画面・フォーム・ピッカー・シート・メニューのように、利用者が「OS標準の見た目」を期待する画面です。上の表の8パッケージのどれかを使っている既存アプリなら、置き換えで依存パッケージを減らせます。SDK 58で新しく作るアプリは、テンプレートに入っているので試すための手間もかかりません。

次の条件に当てはまるなら、Expo UIを主軸にしない方が無難です。

  • Webが主な配信先:Webはネイティブ部品ではなくJavaScript実装で、たとえば List はスクロールする View になる。SDK 56の告知も「Web is experimental」としていた。Web向けの描画はReact Native Webの仕組みも合わせて確認したい。
  • 全OSでピクセル単位まで同じ独自デザインにしたい:iOSとAndroidで見た目が違うことがExpo UIの前提になる。
  • flexboxで組んだ既存の画面をそのまま移したい:Host の中ではyogaが効かず、Row・Column(HStack・VStack)で組み直しになる。
  • macOS版も同じSDKで出したい:SDK 58自体がmacOSに未対応で、react-native-macosがReact Native 0.86以上に追いつくまで待つ必要がある。

jetpack-composeの部品選びでは、Jetpack Composeの仕組みを知っていると迷いが減ります。Expo UIを使ったiOS・Androidアプリの設計や開発を外部に任せたい場合は、スマホアプリ開発の支援内容をご覧ください。

よくある質問

@expo/uiは無料で、本番アプリに使えますか?

MITライセンスで無料です。SDK 56で安定版として本番利用に耐えると告知されましたが、上の変更点の表のとおりパッチ版でも挙動が動くため、リリースビルドでの確認を挟んでから出してください。

Expo Goだけで試せますか?

試せます。Expo Goに同梱されており、SDK 58対応のApp Store版Expo Go 58.0.5も2026年10月22日に出ています。

Webでも同じコンポーネントが使えますか?

universal(@expo/ui)とcommunity版はWebでも動きますが、中身はreact-domやreact-native-webで作ったJavaScript実装です。@expo/ui/swift-ui と @expo/ui/jetpack-compose はそれぞれiOS・tvOSとAndroidでしか動きません。

SDK 55以前のプロジェクトでも使えますか?

SDK 54以前の@expo/uiは0.x系のcanary・beta版として配られ、版番号がSDKと揃ったのはSDK 55向けの55.0.0(2026年2月25日)からです。そのSDK 55でもJetpack Compose側はベータで、Compose側の名前の変更はSDK 56まで続きました。SDK 55以前で書いたコードはimport名やprops名の書き換えが要る前提で、本格的に使うならSDK 56以降に上げてからにする方が手戻りが少なくなります。

「Expo UI kit」のようなテーマ付きの部品一式はありますか?

@expo/uiはテーマ付きのデザインキットではありません。公式FAQは「not an opinionated design kit」と明記しています。色をまとめて変えたい場合は、universalやswift-ui・jetpack-composeの Host に seedColor を渡すと、AndroidではMaterial 3の配色、iOSでは操作部品の色合いが1色から作られます。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.29 テックブログ ソニー生命の不正アクセス:38.5万件の対象確認と窓口、停止中の手続き
  2. 2026.10.31 テックブログ 無印良品の情報漏洩:MUJIアプリ最大2,726万件の対象確認となりすましメール対策
  3. 2026.01.30 コラム 税務調査は個人と法人で何が違う?担当部署・件数・調べられる点と追徴税額を比較【令和6事務年度】
  4. 2026.10.31 テックブログ 松屋の不正アクセス:237万件の対象確認と窓口、漏れたパスワードの守り方
  5. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処

RELATED POSTS 関連記事

目次