truesheet-usage について
truesheet-usageは、React Native TrueSheet v4を利用する開発者向けの実装ガイドです。統合パターンの選択から、シートのレイアウトや操作、一般的なインタラクションまで扱います。
主な機能
- 01シート制御パターンの選択ガイド
- 02デテントとコンテンツ配置のガイド
- 03スクロール領域・ヘッダー・フッター
- 04背景・キーボード・閉じ方の指針
活用シーン
- 01React NativeアプリへのTrueSheet導入
- 02高さを調節できるコンテンツパネルの実装
- 03ナビゲーションフローでのシート利用
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lodev09/ react-native-true-sheet --skill truesheet-usageインストール後、Claude Code で/truesheet-usageを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced-patterns.mdapi.mdconfiguration.mdtroubleshooting.md詳細
TrueSheet 利用者向けガイド
@lodev09/react-native-true-sheet(v4)を使用するアプリで、正しく慣用的なコードを書くためのスキルです。適切な連携パターンの選択、公開 API の正しい適用、プラットフォーム固有の落とし穴の回避について説明します。
React Native >= 0.82(Expo SDK 55+)と新しいアーキテクチャが必要です。
クイックスタート
最もシンプルなシートです。ref、ボタン、コンテンツを用意します。
import { useRef } from 'react'
import { Button, Text, View } from 'react-native'
import { TrueSheet } from '@lodev09/react-native-true-sheet'
export function App() {
const sheet = useRef<TrueSheet>(null)
return (
<View>
<Button title="Open" onPress={() => sheet.current?.present()} />
<TrueSheet ref={sheet} detents={['auto']} cornerRadius={24} grabber>
<View style={{ padding: 16 }}>
<Text>Hello from the sheet</Text>
<Button title="Close" onPress={() => sheet.current?.dismiss()} />
</View>
</TrueSheet>
</View>
)
}
コンテンツのレイアウト(v4)
シートのコンテンツは自然にレイアウトされます。通常のビューや React Navigation の画面と同様に、子要素の高さに合わせて広がります。デフォルトではシート全体を埋めません。そのため、次の点に注意してください。
- コンテンツが埋められていない場合、
flex: 1の子要素の高さはゼロになります。 - スペーサー、中央揃えのコンテンツ、サイズ制限付きのスクロールビューなど、各デテントでシートの表示領域を埋めるには、シートの
styleprop にflex: 1を渡します。
<TrueSheet style={{ flex: 1 }} detents={[0.5, 1]}>
<View style={{ flex: 1, justifyContent: 'center' }}>
<Text>Centered</Text>
</View>
</TrueSheet>
コンテンツのサイズは各デテントでシートの表示領域に合わせて調整され、ドラッグ中もリアルタイムで追従します。そのため、フレックスレイアウトはシートの端にフレーム単位で追従します。
適切な制御パターンを選ぶ
トリガーがシートに対してどこにあるか、対象とするプラットフォームに応じて選択してください。
| パターン | 用途 | プラットフォーム |
|---|---|---|
| Ref | トリガーとシートが同じコンポーネントにある | すべて |
| 名前付きシート+グローバルメソッド | トリガーがシートから離れた場所にある(別の画面、ツリーの深い位置) | ネイティブのみ |
TrueSheetProvider + useTrueSheet() | Web 対応が必要な場合、またはフックベースの制御を使いたい場合 | すべて(Web では必須) |
createTrueSheetNavigator() / Expo Router Sheet | シートがナビゲーションフローの一部である | すべて |
ReanimatedTrueSheet | シートの位置に同期したアニメーション値が必要な場合 | すべて |
Ref ベース
クイックスタートで紹介した方法です。ref で present()、dismiss()、resize(index) を使用します。
グローバルメソッドを使った名前付きシート(ネイティブのみ)
トリガーがシートのレンダリング位置から離れている場合に使います。
// Somewhere in the tree
<TrueSheet name="profile" detents={['auto', 1]}>
<ProfileContent />
</TrueSheet>
// Anywhere else (native only)
await TrueSheet.present('profile')
await TrueSheet.dismiss('profile')
await TrueSheet.resize('profile', 1)
await TrueSheet.dismissAll()
すべての name は一意である必要があります。Web では静的メソッドを使用できません。代わりにプロバイダーパターンを使用してください。
プロバイダーを使った Web 向けの制御
アプリを TrueSheetProvider でラップします(ネイティブではオーバーヘッドなしでそのまま通過します)。Web では @radix-ui/react-dialog と @radix-ui/react-presence のインストールも必要です(オプションのピア依存関係)。
import { TrueSheet, TrueSheetProvider, useTrueSheet } from '@lodev09/react-native-true-sheet'
function Toolbar() {
const { present, dismiss } = useTrueSheet()
return <Button title="Open" onPress={() => present('settings')} />
}
export function App() {
return (
<TrueSheetProvider>
<Toolbar />
<TrueSheet name="settings" detents={[0.5, 1]}>
<SettingsContent />
</TrueSheet>
</TrueSheetProvider>
)
}
ナビゲーション(React Navigation / Expo Router)
createTrueSheetNavigator、静的 API(createTrueSheetScreen)、/navigation/expo-router の Expo Router Sheet レイアウト、setOptions 経由の scrollableRef を含む画面オプション、シート画面をスタックとして使う複数ステップのフロー、useTrueSheetNavigation を使った完全な設定については、高度なパターンのリファレンスを参照してください。
Reanimated
ReanimatedTrueSheet、ReanimatedTrueSheetProvider、およびアニメーション値(animatedPosition、animatedIndex、animatedDetent)については、高度なパターンのリファレンスを参照してください。
デテント
デテントは、シートがスナップする高さを定義します。最大 3 つのデテントを指定でき、小さい順に並びます。デフォルトは [0.5, 1] です。
| 値 | 意味 |
|---|---|
'auto' | コンテンツに合わせたサイズ(iOS 16 以降、Android、Web)。スクロール可能なコンテンツにも対応し、スクロール内容に合わせたサイズになり、内容の増減に応じてサイズが変わります |
'peek' | 計測された header と絶対配置の footer に加え、TrueSheetPeek マーカーまでのコンテンツを基準とした折りたたみ時の高さ(iOS 16 以降、Android、Web)。指定がない場合は 150 にフォールバックします |
0 – 1 | 画面の高さに対する割合 |
// Content-sized sheet
<TrueSheet detents={['auto']} />
// Half and full screen
<TrueSheet detents={[0.5, 1]} />
// Collapsed summary that expands to near-full
<TrueSheet detents={['peek', 0.9]} />
よく使うレシピ
スクロール可能なコンテンツ
scrollableRef を使って、シートをスクロールビューに紐付けます。固定のデテントでは、シートの style に flex: 1 を設定してスクロールビューを制約します。
const scrollableRef = useRef<ScrollView>(null)
<TrueSheet scrollableRef={scrollableRef} style={{ flex: 1 }} detents={[0.5, 1]} grabber>
<ScrollView ref={scrollableRef}>
{items.map(item => <ItemRow key={item.id} item={item} />)}
</ScrollView>
</TrueSheet>
detents={['auto']}の場合、flex: 1は不要です。シートはスクロールコンテンツに合わせてサイズが決まり、ビューポートも自動的に制約されます- 例外:スクロールビューを
flex: 1でラップするリスト(例:FlashList)は、シートにstyle={{ flex: 1 }}が設定されていないと、'auto'で空白になります。シートのサイズはリストに合わせて決まります - スクロールビューは直接の子でなくてもかまいません。ラップする
Viewを使用できます - キーボード処理、ネストされたスクロール(Android)、下部のセーフエリアインセットは自動的に設定されます
- 最上部までスクロールすると次のデテントまで展開されます。
scrollableOptions={{ scrollingExpandsSheet: false }}で無効にできます(YouTube 風のコメント) - 下部のセーフエリアインセット(および絶対配置フッターの高さ)は、コンテンツをスクロールできる状態でネイティブに適用されます。手動でパディングを追加しないでください。または
scrollableOptions={{ contentInsetAdjustment: 'never' }}で無効にできます('safe-area'/'footer'を指定すると、どちらか一方が維持されます)
固定ヘッダーとフッター
<TrueSheet
detents={[0.5, 1]}
scrollableRef={scrollableRef}
style={{ flex: 1 }}
header={
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
</View>
}
footer={<BottomActions />}
>
<ScrollView ref={scrollableRef}>{/* ... */}</ScrollView>
</TrueSheet>
headerとfooterの props を使用します。ネイティブコンテナビュー内にレンダリングされるため、レイアウト計算は自動的に処理されます。絶対配置で代用しないでください。- どちらもデフォルトで相対配置です。ヘッダーはコンテンツの上、フッターは下にスペースを取り、両方とも
'auto'デテントの高さに含まれます。 - フッターは下部のセーフエリアインセットをネイティブに吸収します。二重にならないよう、フッターコンテンツから手動の
paddingBottom: insets.bottomを削除してください。インセットを埋めるには、footerStyleでフッターの背景を設定します。 - 相対配置のフッターはコンテンツの下に配置されます。固定デテントでは、フッターが表示されたままになるよう、コンテンツを
flex: 1(シートのstyle)で制約してください。 - 代わりにコンテンツの上に重ねる場合(
'auto'の高さには含まれません)は、headerOptions={{ position: 'absolute' }}/footerOptions={{ position: 'absolute' }}を使用します。絶対配置のフッターはキーボードの上に移動します(avoidKeyboard: falseで無効化できます)。相対配置のフッターはレイアウトフロー内にとどまり、キーボードの背後に配置されます。 - 絶対配置のフッターは、接続されたスクロール可能なビューの下部を覆います。スクロールコンテンツには、測定されたフッターの高さが自動的にパディングされます(デフォルトの
contentInsetAdjustment: 'automatic')。また、フッターは'auto'に含まれるため、paddingBottom: FOOTER_HEIGHTを固定値で指定しないでください。コンテンツをフッターの下までスクロールさせるには、'safe-area'を使用します。 avoidKeyboard: falseの場合、キーボードより上にあるフッター部分のみがスクロール用パディングに加算され、展開時の'auto'の高さに含まれます。フッターのインセット調整を有効にする必要があります。
のぞき見(地図風の折りたたみシート)
import { TrueSheet, TrueSheetPeek } from '@lodev09/react-native-true-sheet'
<TrueSheet detents={['peek', 0.9]} initialDetentIndex={0} header={<Summary />}>
<TrueSheetPeek>
<QuickActions />
</TrueSheetPeek>
<FullDetails />
</TrueSheet>
折りたたまれた状態では、TrueSheetPeek の下端までのすべてが表示されます。それより下のコンテンツは、展開されるまで非表示のままです。空の <TrueSheetPeek /> を既存のビュー間の折り目マーカーとして使用できます。シートごとに peek コンポーネントは 1 つです。絶対配置のフッターは peek の高さに含まれますが、相対配置のフッターは含まれません(peek 時には画面外に押し出されます)。
シートの上に表示するトーストとダイアログ
import { TrueSheet, TrueSheetOverlay } from '@lodev09/react-native-true-sheet'
<TrueSheetOverlay>
{toast && <Toast message={toast} />}
</TrueSheetOverlay>
子要素を、表示中のすべてのシートより上のネイティブレイヤーにレンダリングします。子要素の条件付きレンダリングで表示/非表示を切り替えます。ウィンドウ全体を埋め、子要素に当たらないタッチは通過します。今後は RN Modal / FullWindowOverlay を使わないでください。react-native-screens のコンテナ(ネイティブスタック、タブ、ドロワー)をこの中に配置しないでください。Android がクラッシュします。
閉じる操作を許可しない確認
<TrueSheet
ref={sheet}
detents={['auto']}
dismissible={false}
draggable={false}
dimmed
grabber={false}
>
<View style={{ padding: 24 }}>
<Text>Are you sure?</Text>
<Button title="Confirm" onPress={handleConfirm} />
<Button title="Cancel" onPress={() => sheet.current?.dismiss()} />
</View>
</TrueSheet>
閉じる前に確認する(未保存の変更)
<TrueSheet
ref={sheet}
dismissible={!isDirty}
onDismissAttempt={() =>
Alert.alert('Discard changes?', undefined, [
{ text: 'Keep editing', style: 'cancel' },
{ text: 'Discard', style: 'destructive', onPress: () => sheet.current?.dismiss() },
])
}
>
{/* form */}
</TrueSheet>
コールバックから閉じる操作をブロックしようとしないでください。ネイティブ側は JS の応答を待てません。状態に応じて dismissible を切り替えます。ブロックされたドラッグと戻る/エスケープ操作では onDismissAttempt が発火します(背景のタップでは発火しません)。その後、プログラムから dismiss() を呼び出します。
背景
<TrueSheet backgroundBlur="system-material" /> {/* iOS-only blur, wins over backgroundColor */}
<TrueSheet backgroundStyle={{ backgroundColor: 'rgba(0, 122, 255, 0.25)' }} /> {/* tint glass/blur */}
<TrueSheet backgroundStyle={{ backgroundColor: '#18202b' }} /> {/* exact opaque color */}
<TrueSheet backgroundColor="transparent" /> {/* clear, no glass on supported iOS 26.1+ */}
<TrueSheet background={<BlurView intensity={40} style={StyleSheet.absoluteFill} />} /> {/* custom node */}
<TrueSheet detentBackgrounds={[null, { blur: 'system-material' }, '#18202b']} /> {/* per detent */}
backgroundStyle.backgroundColor は、ガラス効果やぼかし効果の上に色を塗ります。background はシートのエフェクトの上に任意のノードを描画します。このラッパーはタッチを無視します。
blurOptions は v4 で削除されました。強度をカスタマイズするには、background 内でサードパーティ製のぼかしを使用してください。
レイヤー、デテントごとのトランジション、iOS のフォールバックについては、背景を参照してください。
マウント時に表示
<TrueSheet detents={['auto', 1]} initialDetentIndex={0} initialDetentAnimated>
<WelcomeContent />
</TrueSheet>
'auto' デテント用にコンテンツを事前マウントする
デフォルトでは、最初の present() でコンテンツがマウントされます。コンテンツの準備が非同期に完了する場合(データの取得、画像など)、'auto' の測定が早すぎることがあります。lazy={false} を設定すると、表示せずにコンテンツを事前にマウントできます。その後、準備ができたら表示します(ネイティブのみ)。
<TrueSheet ref={sheet} lazy={false} detents={['auto']}>
<AsyncContent onReady={() => sheet.current?.present()} />
</TrueSheet>
ディミングの制御
// No dimming (allows background interaction)
<TrueSheet dimmed={false} detents={['auto']} />
// Dim only above a certain detent
<TrueSheet detents={['auto', 0.7, 1]} dimmedDetentIndex={1} />
// Custom dim color and opacity
<TrueSheet detents={['auto']} dimColor="#1b0a3c" dimOpacity={0.3} />
// Invisible dim that still blocks touches and dismisses on tap
<TrueSheet detents={['auto']} dimOpacity={0} />
プログラムによるサイズ変更
resize() が受け取るのは値ではなく、デテントのインデックスです。
const sheet = useRef<TrueSheet>(null)
// detents={[0.3, 0.6, 1]}
await sheet.current?.resize(2) // expands to full (index 2)
デバッグ時間を節約するルール
- デテントは最大 3 つで、小さいものから大きいものへ順に並べます。
- コンテンツはデフォルトではシート全体に広がりません — 子要素が
flex: 1を使用する場合は、シートにstyle={{ flex: 1 }}を渡してください。そうしないと、高さが 0 で描画されます。 scrollableRefは v3 のscrollableプロパティの代わりです — ScrollView/FlatList の ref を渡します。固定デテントではシートのstyleにflex: 1を指定してバインドしてください。'auto'では不要ですが、FlashListやflex: 1でラップされたほかのリストは例外です。resize()が受け取るのはインデックスであり、割合ではありません。resize(1)は「2 番目のデテントに移動する」という意味です。- シート名はアプリ全体で一意にする必要があります。
- 静的メソッドはネイティブ専用です — Web では
useTrueSheet()を使用してください。 - フッターや、シートに組み込んだスクロールコンテンツに手動でセーフエリアのパディングを追加しないでください — フッターが下部インセットを吸収し、スクロール可能な領域にはネイティブに適用されます。二重に適用することは、v4 で最もよくあるレイアウトの不具合です。
autoFocusが動作するのはマウント時のみです — デフォルトの遅延マウントシートでは問題ありません(シートと一緒にキーボードが表示されます)が、lazy={false}の場合は最初の表示時にしかフォーカスしません。毎回表示時にフォーカスするには、onWillPresent(シートと一緒にキーボードが表示されます)またはonDidPresent(シートが表示された後)でフォーカスしてください。<TrueSheet lazy={false} onWillPresent={() => inputRef.current?.focus()}>- Android でシート内に
GestureHandlerRootViewを配置する場合、シート自体にstyle={{ flex: 1 }}を指定していない限り、flex: 1ではなくflexGrow: 1を使用してください。 - iOS では、Modal を閉じる前にシートを閉じてください — シートが表示されている状態で Modal を閉じると、画面が真っ白になる React Native の不具合があります。
- 固定表示要素には
header/footerプロパティを使用してください — 絶対配置を使わないでください。コンテンツに重ねる場合は、headerOptions/footerOptionsのposition: 'absolute'でフロート表示できます。 - 背景エフェクト:対応する iOS 26.1 以降では、ぼかしが色より優先され、それ以外の場合は Liquid Glass がデフォルトになります。透明なエフェクトにするには
backgroundColor="transparent"を使用してください。ガラス効果やぼかしに色味を付けたり、正確な色を塗ったりするにはbackgroundStyle.backgroundColorを使用します。エフェクトのセッターがない場合や、iOS 26 でデザイン互換モードが有効な場合はフォールバックが使われます。iOS 27 以降ではUIDesignRequiresCompatibilityは無視されます。強度をカスタマイズするには、background内でサードパーティ製のぼかしを使用してください。blurOptionsは削除されました。 - Sheet Navigator/Expo Router では、ネイティブスタックはシート画面ではなくベース画面に配置してください。最初の画面は通常のビューで、
createNativeStackNavigatorをホストできます(これがアプリをラップする方法です)。シート画面には配置できません — Android ではScreenContainer is not attached under ReactRootViewによりクラッシュします。複数ステップのフローでは、各ステップをそれぞれ別のシート画面にしてください。
プラットフォームごとの違い
| 機能 | iOS | Android | Web |
|---|---|---|---|
'auto' detent | iOS 16+ | 対応 | 対応 |
'peek' detent / TrueSheetPeek | iOS 16+ | 対応 | 対応 |
TrueSheetOverlay | 対応 | 対応 | 対応 |
detentBackgrounds | 対応する 26.1+ ではネイティブ効果、それ以外では中間点でクロスフェード | 色のクロスフェード | 色のクロスフェード |
backgroundBlur | 対応 | 非対応 | 非対応 |
background / backgroundStyle | 対応 | 対応 | 対応 |
| Liquid Glass | iOS 26+ | 非対応 | 非対応 |
| 静的なグローバルメソッド | 対応 | 対応 | 非対応(プロバイダーを使用) |
scrollableRef | 対応 | 対応 | 対応 |
| スクロールエッジ効果 | iOS 26+ | 非対応 | 非対応 |
grabberOptions | 対応 | 対応 | 非対応 |
dimColor / dimOpacity | カスタムの暗転レイヤーがシステムの暗転を置き換えます。重ねたシートごとに暗転レイヤーが追加されます | 対応 | 対応 |
placement / サイドシート | マージンはシステムが制御します。iOS 27+ では 'automatic' と 'center' の動作が異なります | placementOffset prop | placementOffset prop |
presentation | iOS 17+(iPad) | 該当なし | 横向き/タブレット |
foldOptions | iOS 27.1+ の折りたたみ式デバイス(テーブルトップ) | 非対応 | 非対応 |
detached モード | 非対応 | 非対応 | 対応 |
insetAdjustment | 対応 | 対応 | 非対応 |
dismissThreshold | 非対応(システムが制御) | 対応 | 対応 |
lazy={false}(コンテンツの事前マウント) | 対応 | 対応 | 非対応 |
| エッジ・トゥ・エッジ | 該当なし | 自動検出 | 該当なし |
| キーボード処理 | 組み込み | 組み込み | ソフトキーボードの上に表示 |
イベント
最もよく使用されるイベント:
| イベント | 発生タイミング | ペイロード |
|---|---|---|
onMount | コンテンツがマウントされ、使用可能になったとき | — |
onDidPresent | シートの表示が完了したとき | { index, position, detent } |
onDidDismiss | シートの非表示が完了したとき | — |
onDismissAttempt | dismissible={false} の状態でユーザーが非表示にしようとしたとき | — |
onDetentChange | ユーザーのドラッグまたは resize() によって detent が変化したとき | { index, position, detent } |
onPositionChange | ドラッグ/アニメーション中に位置が継続的に更新されたとき | { index, position, detent, realtime } |
イベントの全一覧(ドラッグイベント、フォーカス/ぼかしイベント、will/did ライフサイクルのペア、onBackPress)については、API リファレンスを参照してください。
メソッド
ref で使用:
present(index?, animated?)— シートを表示しますdismiss(animated?)— シートとそのすべての子シートを非表示にしますdismissStack(animated?)— 上に重なっているシートのみを非表示にしますresize(index)— インデックスで指定した detent にスナップします
グローバル(ネイティブのみ):
TrueSheet.present(name, index?, animated?)TrueSheet.dismiss(name, animated?)TrueSheet.dismissStack(name, animated?)TrueSheet.resize(name, index)TrueSheet.dismissAll(animated?)
Web フック:
const { present, dismiss, dismissStack, resize, dismissAll } = useTrueSheet()
シートの重ね合わせ
別のシートが表示されている状態で新しいシートを表示すると、最初のシートは自動的に隠れます。一番上のシートを非表示にすると、前のシートが再び表示されます。この機能は組み込み済みのため、追加の設定は不要です。
dismiss()は連鎖して動作し、現在のシートとその上に重なっているすべてのシートを非表示にしますdismissStack()は上に重なっているシートのみを非表示にし、現在のシートは表示したままにします- シートが一番上に移動したとき、または一番上から外れたときの処理には、
onDidFocus/onDidBlurを使用します
詳細リファレンス
全体像を確認するには、次のリファレンスファイルを参照してください。
| リファレンス | 内容 |
|---|---|
| 設定 | 各 prop の型、デフォルト値、プラットフォーム対応状況、注記 |
| API | ペイロードの型を含む、イベントとメソッドの完全なリファレンス |
| 高度なパターン | ナビゲーション、Reanimated、Web、サイドシート、背景、Liquid Glass、Jest のモック、v3→v4 の移行 |
| トラブルシューティング | プラットフォーム別の一般的な問題と解決方法 |