スタイル
RN のスタイリングは Web の CSS とよく似ているが別物と思った方が安全。
ファイル分離はせず、コンポーネントの中に StyleSheet.create で書く流派が標準。
Tailwind 風に書きたいなら NativeWind がある。本ページは RN スタイル全部入り。
StyleSheet の基本
import { View, Text, StyleSheet } from "react-native"
export default function Card() {
return (
<View style={s.card}>
<Text style={s.title}>タイトル</Text>
<Text style={s.body}>本文</Text>
</View>
)
}
const s = StyleSheet.create({
card: {
backgroundColor: "#fff",
padding: 16,
borderRadius: 12,
shadowColor: "#000",
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 2, // Android 用
},
title: { fontSize: 18, fontWeight: "600", marginBottom: 4 },
body: { fontSize: 14, color: "#666" },
})
- キーは camelCase:
background-colorではなくbackgroundColor - 値は数値(DP)または文字列:
padding: 16/color: "#fff" - セミコロン不要。JS のオブジェクトなのでカンマ区切り
StyleSheet.create vs 直書きオブジェクト
どちらでも動くが、慣習として StyleSheet.create を使う。理由:
- 型推論が効く(TypeScript で値の型を厳密にチェック)
- 不正なプロパティ名で警告が出る(
colourなど) - id 化されてブリッジ越しの転送が軽くなる(古い RN では効果大、新アーキテクチャでは差は小)
<View style={{ padding: 16, backgroundColor: "#fff" }} />
コンポーネントの中で毎回 {...} を書くと、レンダーごとに新しいオブジェクトが作られる。
ホットパスで子の memo を破壊する原因になることがある。
パフォーマンスが気になる箇所は StyleSheet.create または useMemo で固定する。
スタイルの合成
style プロパティには配列を渡せる。後ろが優先(CSS と同じ)。
false や null、undefined は無視される。
<View style={[s.card, isActive && s.cardActive, { marginTop: 8 }]} />
多重配列も OK(再帰的にフラット化される):
<View style={[s.base, [s.row, s.padded], extra]} />
StyleSheet.flatten / StyleSheet.compose
StyleSheet.flatten([a, b])— 配列を1つのオブジェクトにマージ。中身を読みたい時StyleSheet.compose(a, b)— 2つを合成(配列を返す)
const merged = StyleSheet.flatten([s.a, s.b])
console.log(merged.color) // 最終的な色を取りたい時
動的スタイル
値が状態で変わるなら、関数や配列を組み合わせる:
// 配列で重ねる
<View style={[s.box, { transform: [{ scale: zoom }] }]} />
// 関数で組み立てる
const dynamicStyle = (color: string) => ({ backgroundColor: color, padding: 16 })
<View style={dynamicStyle(theme.bg)} />
// 多くの場合は useMemo で固定
const cardStyle = useMemo(
() => [s.card, isActive && s.active, { borderColor: tint }],
[isActive, tint],
)
値の単位
- すべてDP(密度非依存ピクセル)。
14は 14 DP - % も使える:
width: "50%" "auto":marginLeft: "auto"等で右寄せが作れる(一部プロパティのみ)- 負の値:
marginTop: -10も可能(重ね合わせの定石) - ヘアライン:
StyleSheet.hairlineWidthでデバイスの細い線
// 1px の細い線(高 DPI でも 1 物理ピクセル)
<View style={{ height: StyleSheet.hairlineWidth, backgroundColor: "#888" }} />
Flexbox(最重要)
RN は Flexbox がレイアウトの主軸。Grid はビルトインでは無い(サードパーティはある)。
Web との違い
| Web | RN | |
|---|---|---|
flexDirection 既定 | row | column |
display | 明示が必要 | すべて flex |
flex: 1 | flex-grow: 1 | 残り全部埋める(flex: 1; flex-shrink: 1; flex-basis: 0% 相当) |
alignContent | flex-wrap 必要 | 同様(複数行時のみ意味) |
gap | ○ | ○(最近の RN) |
Flexbox プロパティ完全表
| プロパティ | 値 | 意味 |
|---|---|---|
flex | 数値 | 残り空間の取り分(1 で全部、2 なら相対比) |
flexGrow | 数値 | 余白の伸び方 |
flexShrink | 数値 | 狭くなった時の縮み方 |
flexBasis | 数値 / "auto" / "%" | 初期サイズ |
flexDirection | "column"(既定)/"row"/"column-reverse"/"row-reverse" | 主軸 |
flexWrap | "nowrap"(既定)/"wrap"/"wrap-reverse" | 折り返し |
justifyContent | "flex-start"(既定)/"flex-end"/"center"/"space-between"/"space-around"/"space-evenly" | 主軸方向の配置 |
alignItems | "stretch"(既定)/"flex-start"/"flex-end"/"center"/"baseline" | 交差軸方向の配置 |
alignSelf | 同上 + "auto" | 個別アイテムの交差軸配置 |
alignContent | justifyContent と同じ + "stretch" | 複数行の配置(wrap 時のみ) |
gap | 数値 | 子要素間の隙間 |
rowGap / columnGap | 数値 | 方向別の隙間 |
典型レイアウト
<View style={{ flex: 1 }}> ... </View>
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>中央</Text>
</View>
<View style={{ flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
<Text>戻る</Text>
<Text>タイトル</Text>
<Text>保存</Text>
</View>
<View style={{ flexDirection: "row" }}>
<View style={{ flex: 2, backgroundColor: "#7dd3fc" }} />
<View style={{ flex: 1, backgroundColor: "#fbbf24" }} />
</View>
<View style={{ flexDirection: "row" }}>
<Text>左</Text>
<Text style={{ marginLeft: "auto" }}>右</Text>
</View>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 12 }}>
{items.map((item) => (
<View key={item.id} style={{ width: "30%", aspectRatio: 1, backgroundColor: "#7dd3fc" }} />
))}
</View>
サイズ
サイズ系のプロパティは Web と同じ。aspectRatio が便利。
| プロパティ | 意味 |
|---|---|
width / height | 幅・高さ |
minWidth / maxWidth | 最小・最大幅 |
minHeight / maxHeight | 最小・最大高さ |
aspectRatio | 幅:高さ。1=正方形、16/9=動画 |
{/* 幅100%・高さは幅から自動計算(16:9 の動画サムネ) */}
<View style={{ width: "100%", aspectRatio: 16/9, backgroundColor: "#000" }} />
margin / padding
| プロパティ | 意味 |
|---|---|
margin / padding | 4辺すべて |
marginVertical / paddingVertical | 上下のみ |
marginHorizontal / paddingHorizontal | 左右のみ |
marginTop/Right/Bottom/Left | 個別 |
marginStart / marginEnd | RTL(右→左言語)対応の論理プロパティ |
<View style={{
marginVertical: 8, // 上下 8
marginHorizontal: 16, // 左右 16
paddingTop: 12,
}} />
marginStart / marginEnd はアラビア語等の RTL 言語で左右が自動で反転する。多言語対応するなら使う。
position
Web と同じく親が relative 相当(RN では position: "relative" が既定)の中で absolute 配置。
fixed は無い。
<View style={{ position: "absolute", top: 16, right: 16 }}> ... </View>
{/* 全画面を覆う(よく使う) */}
<View style={StyleSheet.absoluteFillObject} />
{/* または */}
<View style={{ position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }} />
StyleSheet.absoluteFillObject は定数オブジェクト。StyleSheet.absoluteFill は同じものを ID 化したスタイル。どちらでもよい。
z-index と Android の elevation
重なり順は zIndex。ただしAndroid では elevation も影響する。影と重ね順が一緒に決まるクセ。
{/* iOS:zIndex で重なる */}
<View style={{ zIndex: 10 }} />
{/* Android:影と重なり両方を出すなら elevation */}
<View style={{ elevation: 8 }} />
border
| プロパティ | 意味 |
|---|---|
borderWidth | 4辺の太さ |
borderTop/Right/Bottom/LeftWidth | 個別 |
borderColor | 色 |
borderTop/.../LeftColor | 個別の色 |
borderStyle | "solid"(既定)/"dotted"/"dashed" |
borderRadius | 4 角の角丸 |
borderTopLeftRadius 等 | 角別の角丸 |
{/* よくある角丸カード */}
<View style={{
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 12,
padding: 16,
}} />
{/* 上の角だけ丸める(モーダル下から出す等) */}
<View style={{
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
}} />
{/* 完全な丸(円) */}
<View style={{ width: 60, height: 60, borderRadius: 30 }} />
{/* または */}
<View style={{ width: 60, height: 60, borderRadius: 999 }} />
borderRadius + 子の overflow
Android で角丸の親に画像等を入れると、画像が角丸を無視してはみ出ることがある。
親に overflow: "hidden" を付ける必要がある(iOS は普通に切れる)。
影
プラットフォーム別に書く必要があるのが厄介。
iOS の影
{
shadowColor: "#000",
shadowOpacity: 0.15,
shadowRadius: 8,
shadowOffset: { width: 0, height: 4 },
}
Android の影 = elevation
Android では影の細部は OS が管理。elevation 数値で「浮き具合」だけを指定。
{
elevation: 4,
shadowColor: "#000", // Android でも色だけは効く(API 28+)
}
両方を1つに
const cardShadow = {
// iOS
shadowColor: "#000",
shadowOpacity: 0.1,
shadowRadius: 8,
shadowOffset: { width: 0, height: 4 },
// Android
elevation: 4,
}
boxShadow(新しめ)
最近の RN(0.76+)と Expo SDK では CSS の boxShadow 文字列がそのまま使える。
プラットフォーム差を吸収してくれる:
{
boxShadow: "0 4px 8px rgba(0,0,0,0.1)",
}
まだ過渡期なので、互換重視のチームは旧来の書き方が無難。非対応バージョンでは無視される。
transform
Web と同じくたいてい使える。配列で渡す(CSS の連結とは違う)。
style={{
transform: [
{ translateX: 10 },
{ translateY: -5 },
{ rotate: "45deg" },
{ scale: 1.2 },
{ scaleX: 1.2 },
{ scaleY: 0.8 },
{ skewX: "10deg" },
{ skewY: "5deg" },
{ rotateX: "30deg" },
{ rotateY: "60deg" },
{ rotateZ: "90deg" },
],
}}
配列の順番が重要: 後ろのものが先に適用される(行列の合成順序)。
例えば [{ translateX: 50 }, { rotate: "45deg" }] は先に回転、その後に移動。
transformOrigin
最近の RN では transformOrigin: "center" や "top left" で原点指定可能。
古い RN は常に中心が原点。
opacity
子要素も含めて全体に適用される。CSS と同じ。
<View style={{ opacity: 0.5 }} />
overflow
"visible"(既定) — 子がはみ出して見える"hidden"— 切る"scroll"(あまり使わない) — ScrollView を使うべき
overflow: visible が効かないケース
Android では親の領域外に描いた子(影、はみ出る画像)が切られることがある。
特に borderRadius + 画像、絶対配置の子要素ではトラブルが起きやすい。
workaround: 親にも明示的に overflow: "visible"、もしくは構造を見直して領域内に収める。
pointerEvents
タッチ判定の制御。
"auto"(既定) — 通常"none"— 自身も子も無視"box-none"— 自身は無視、子だけ反応"box-only"— 自身だけ反応、子は無視
オーバーレイ(透明な層を重ねる)で下のタッチを通したい時に "none" / "box-none" を使う。
backfaceVisibility
3D 回転で裏面を表示するか。"hidden" でカードめくりの定石。
テキスト固有のスタイル
<Text> にだけ適用できるプロパティ:
| プロパティ | 意味 |
|---|---|
fontSize | サイズ |
fontWeight | "100"..."900" / "normal" / "bold" |
fontStyle | "normal" / "italic" |
fontFamily | フォント名 |
color | テキスト色 |
letterSpacing | 文字間 |
lineHeight | 行間(数値、単位なし) |
textAlign | "auto"(既定) / "left" / "right" / "center" / "justify" |
textAlignVertical (Android) | 縦方向の揃え |
textDecorationLine | "underline" / "line-through" |
textDecorationColor | 下線等の色 |
textShadow* | テキスト影 |
writingDirection | RTL |
includeFontPadding (Android) | Android のフォント上下余白を消す(よく false に) |
<Text style={{
fontSize: 16,
lineHeight: 24,
letterSpacing: 0.2,
color: "#111",
fontWeight: "600",
includeFontPadding: false, // Android で見栄え統一
textAlign: "center",
}}>
Hello
</Text>
テキストの継承は <Text> の中の子 <Text> でだけ働く(RN で唯一の例外):
<Text style={{ color: "white", fontSize: 16 }}>
通常テキスト
<Text style={{ fontWeight: "bold" }}>太字部分</Text>
</Text>
画像のスタイル
<Image> 固有:
| プロパティ | 意味 |
|---|---|
resizeMode | "cover"/"contain"/"stretch"/"center"/"repeat" |
tintColor | 単色化(アイコンの色変更に便利) |
expo-image は contentFit(resizeMode 相当)等の現代的 API。
サイズ・寸法を取得
useWindowDimensions
回転やスプリットビューに自動追従。これを使う。
import { useWindowDimensions } from "react-native"
function Foo() {
const { width, height, fontScale, scale } = useWindowDimensions()
return <View style={{ width: width * 0.8 }} />
}
width/height— DP のサイズscale— DPR(物理ピクセル比)fontScale— ユーザーが設定した文字スケール(1.5x など)
Dimensions.get(古い)
1度しか取らないので回転に追従しない。使わない方が無難。
onLayout でビューサイズを取る
<View
onLayout={(e) => {
const { x, y, width, height } = e.nativeEvent.layout
console.log(width, height)
}}
/>
PixelRatio
DPR を使った1物理ピクセル計算等で:
import { PixelRatio } from "react-native"
const onePixel = 1 / PixelRatio.get() // = StyleSheet.hairlineWidth
const fontPx = PixelRatio.getPixelSizeForLayoutSize(16) // 物理ピクセル換算
SafeArea とインセット
ノッチ・Dynamic Island・ホームインジケータ・ステータスバー等のシステム UI を避ける。
react-native-safe-area-context を使う(標準の SafeAreaView は機能制限あり)。
npx expo install react-native-safe-area-context
ルートで Provider
import { SafeAreaProvider } from "react-native-safe-area-context"
<SafeAreaProvider>
<App />
</SafeAreaProvider>
使い方
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context"
// パターン A: SafeAreaView でくるむ
<SafeAreaView style={{ flex: 1 }} edges={["top", "left", "right"]}>
...
</SafeAreaView>
// パターン B: 自分で padding を計算
function Header() {
const insets = useSafeAreaInsets()
return (
<View style={{
paddingTop: insets.top + 8,
paddingHorizontal: 16,
paddingBottom: 12,
}}> ... </View>
)
}
プラットフォーム別スタイル
import { Platform, StyleSheet } from "react-native"
const padding = Platform.OS === "ios" ? 20 : 16
const s = StyleSheet.create({
text: {
fontSize: 16,
...Platform.select({
ios: { fontFamily: "Helvetica" },
android: { fontFamily: "sans-serif" },
default: { fontFamily: "System" },
}),
},
})
バージョン別
if (Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 17) {
// iOS 17+
}
ファイル分離
Foo.ios.tsx / Foo.android.tsx / Foo.tsx を作ると、import 時に拡張子なしで自動選択される。UI が大きく違う場合に有効。
ダークモード
useColorScheme で OS 設定を取得。"light" / "dark" / null(不明)。
import { useColorScheme } from "react-native"
function Card() {
const scheme = useColorScheme()
const dark = scheme === "dark"
return (
<View style={{ backgroundColor: dark ? "#0b0d12" : "#fff" }}>
<Text style={{ color: dark ? "#e7ebf3" : "#111" }}>こんにちは</Text>
</View>
)
}
テーマプロバイダパターン
実用ではテーマオブジェクトを Context や Zustand で共有:
const lightTheme = { bg: "#fff", text: "#111", muted: "#666" }
const darkTheme = { bg: "#0b0d12", text: "#e7ebf3", muted: "#98a2b3" }
const ThemeContext = createContext(lightTheme)
function ThemeProvider({ children }: any) {
const scheme = useColorScheme()
const theme = scheme === "dark" ? darkTheme : lightTheme
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
}
export const useTheme = () => useContext(ThemeContext)
押下・hover・focus 状態
Pressable は関数として style を渡せる:
<Pressable
style={({ pressed, hovered, focused }) => [
s.btn,
pressed && { opacity: 0.7 },
hovered && { backgroundColor: "#0070cc" }, // Web 出力時のみ
focused && { borderColor: "#3b82f6" },
]}
>
<Text>ボタン</Text>
</Pressable>
グラデーション
標準では出来ない。expo-linear-gradient を使う。
npx expo install expo-linear-gradient
import { LinearGradient } from "expo-linear-gradient"
<LinearGradient
colors={["#7dd3fc", "#9333ea"]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={{ width: 200, height: 100, borderRadius: 12 }}
/>
放射グラデーションは react-native-svg + RadialGradient、または react-native-linear-gradient 系の別ライブラリ。
ぼかし(Blur)
標準では出来ない。expo-blur を使う:
npx expo install expo-blur
import { BlurView } from "expo-blur"
<BlurView intensity={50} tint="light" style={{ ...StyleSheet.absoluteFillObject }} />
tint は "light" / "dark" / "default"。
Android では一部端末で品質が劣る場合がある。
カスタムフォント
Expo なら expo-font + useFonts フックで読み込み。
ロード完了するまでスプラッシュ表示で待つのが定番。
npx expo install expo-font
import { useFonts } from "expo-font"
import { SplashScreen } from "expo-router"
SplashScreen.preventAutoHideAsync()
export default function Layout() {
const [loaded] = useFonts({
"Inter-Regular": require("../assets/fonts/Inter-Regular.ttf"),
"Inter-Bold": require("../assets/fonts/Inter-Bold.ttf"),
})
useEffect(() => {
if (loaded) SplashScreen.hideAsync()
}, [loaded])
if (!loaded) return null
return <Slot />
}
<Text style={{ fontFamily: "Inter-Bold" }}>太字</Text>
fontWeight はカスタムフォントに効かない
fontWeight: "700" はシステムフォント前提。カスタムフォントは別ファイルとして読み込んで fontFamily を切り替えるのが正解。
ウェイトごとに Inter-Regular / Inter-Medium / Inter-Bold 等を読み込む。
フォントスケーリング
OS の「テキストサイズ」設定でユーザーが文字を大きくできる。 既定で対応するが、レイアウトが壊れる場合は制限するのも手:
{/* 個別 */}
<Text allowFontScaling={false}>固定サイズ</Text>
<Text maxFontSizeMultiplier={1.5}>最大 1.5 倍まで</Text>
{/* グローバルに */}
import { Text } from "react-native"
Text.defaultProps = Text.defaultProps || {}
Text.defaultProps.allowFontScaling = true
Text.defaultProps.maxFontSizeMultiplier = 1.5
ただしアクセシビリティを下げるのでむやみに切らない。レイアウト側で柔軟に対応するのが本来。
NativeWind(Tailwind ライク)
Tailwind の className 文化を RN に持ち込む人気ライブラリ。className を書くだけでスタイルが当たる。
セットアップ
npm install nativewind tailwindcss
tailwind.config.js:
module.exports = {
content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
presets: [require("nativewind/preset")],
theme: { extend: {} },
}
babel.config.js に NativeWind プラグイン、metro.config.js でグローバル CSS を読み込ませる。
詳しくは 公式ドキュメント。
使い方
import { View, Text } from "react-native"
<View className="flex-1 items-center justify-center bg-slate-900 px-4">
<Text className="text-white text-2xl font-semibold mb-4">Hello</Text>
<Text className="text-slate-400">サブテキスト</Text>
</View>
ダークモード
<View className="bg-white dark:bg-slate-900">
<Text className="text-black dark:text-white">切替</Text>
</View>
ブレークポイント・状態
md:/lg:— 幅ブレークポイントactive:— 押下中focus:— フォーカス中disabled:— 無効ios:/android:/web:— プラットフォーム別
<Pressable className="bg-blue-500 active:bg-blue-700 ios:px-4 android:px-3 px-2">
<Text className="text-white">タップ</Text>
</Pressable>
NativeWind の長所・短所
- ○ Web の Tailwind と記法統一
- ○
StyleSheet.createオブジェクトを書かなくていい - ○ ダークモード・状態が簡潔に書ける
- ○ react-native-web との相性が良い
- × セットアップが少し複雑(Babel/Metro 設定)
- × カスタムテーマや
theme.colorsの管理に慣れが要る - × Tamagui ほどの最適化はない
Tamagui
Web/Native 統合のデザインシステム + コンパイラ。 ビルド時にスタイルを最適化(インライン化・条件分岐の静的解決)してくれる。
import { Stack, Text, Button } from "tamagui"
<Stack flex={1} ai="center" jc="center" bg="$background">
<Text color="$color" fontSize="$8" fontWeight="600">Hello</Text>
<Button theme="active" size="$5">Tap</Button>
</Stack>
$background のようにテーマトークンを $ プリフィックスで参照する。
セットアップが大がかりだが、本格プロダクトでの効果は大きい。
その他の UI ライブラリ
| ライブラリ | 特徴 |
|---|---|
| react-native-paper | Material Design。完成された見た目 |
| gluestack-ui | shadcn/ui 風のヘッドレスコンポーネント |
| react-native-elements | 軽量な汎用 UI |
| NativeBase | 古めの定番(NativeWind/gluestack に移行傾向) |
| UI Kitten | Eva Design System ベース |
レスポンシブ・タブレット対応
useWindowDimensionsで width を見て分岐- iOS なら
app.jsonのios.supportsTablet: trueを有効化 flex/%/aspectRatioを活用- 2 ペイン UI(左にリスト、右に詳細)はタブレットで価値が高い
function Layout() {
const { width } = useWindowDimensions()
const isTablet = width >= 768
return (
<View style={{ flex: 1, flexDirection: isTablet ? "row" : "column" }}>
<View style={{ flex: 1 }}><List /></View>
{isTablet ? <View style={{ flex: 2 }}><Detail /></View> : null}
</View>
)
}
StyleSheet と CSS の差分まとめ
| CSS | RN StyleSheet | |
|---|---|---|
| キー命名 | kebab-case | camelCase |
| 単位 | px / em / rem / % など | 数値(DP)か "%" |
| display 既定 | block | flex |
| flexDirection 既定 | row | column |
| position fixed | あり | なし |
| continuous styles の継承 | あり | テキストのみ |
| :hover / :active / :focus | セレクタ | Pressable の関数 / 状態 props |
| transition | CSS | Animated / Reanimated 必須 |
| animation | @keyframes | 同上 |
| media query | あり | useWindowDimensions で自前 |
| :dir(rtl) / etc | あり | I18nManager + start/end プロパティ |
| z-index | 整数 | 整数 + Android では elevation 影響 |
| 盲点 | — | overflow の Android 挙動、影の差 |
よく使うパターン集
水平セパレータ
<View style={{ height: StyleSheet.hairlineWidth, backgroundColor: "#ccc" }} />
角丸の中で画像をきれいに切る
<View style={{ borderRadius: 12, overflow: "hidden" }}>
<Image source={...} style={{ width: 200, height: 120 }} />
</View>
カードの上に重ねるバッジ
<View style={{ position: "relative" }}>
<Card />
<View style={{
position: "absolute",
top: -8,
right: -8,
backgroundColor: "red",
borderRadius: 999,
paddingHorizontal: 8,
paddingVertical: 2,
}}>
<Text style={{ color: "white", fontSize: 12 }}>NEW</Text>
</View>
</View>
幅100%のボタン
<Pressable style={{ alignSelf: "stretch", paddingVertical: 12, alignItems: "center" }}> ... </Pressable>
フッター固定(footer を画面下に)
<View style={{ flex: 1 }}>
<ScrollView style={{ flex: 1 }}> ... </ScrollView>
<View style={{ height: 56 }}> ... フッタ ... </View>
</View>
キーボードに合わせて持ち上がる入力欄
import { KeyboardAvoidingView, Platform } from "react-native"
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : undefined}
style={{ flex: 1 }}
> ... </KeyboardAvoidingView>
3 行で省略
<Text numberOfLines={3} ellipsizeMode="tail">
長い長い長いテキスト...
</Text>
ローディング中のスケルトン
<View style={{
width: "100%",
height: 80,
backgroundColor: "#eee",
borderRadius: 8,
opacity: 0.6,
}} />
デバッグ: スタイルのトラブルシュート
領域を可視化
「あれ、要素どこにあるんだ?」と思ったら背景色を当てる:
<View style={{ backgroundColor: "rgba(255,0,0,0.2)" }}> ... </View>
Element Inspector
開発メニューから "Toggle Inspector"。タップで要素を選んで Box モデルが見える。
Layout 完了タイミング
<View onLayout={(e) => console.log("layout:", e.nativeEvent.layout)} />
よくある原因
- 「flex: 1 が効かない」 — 親に高さがない(
flex: 1は親のコンテキストに依存) - 「overflow: hidden が効かない」 — Android のクセ。親階層を見直す
- 「影が出ない」 — iOS は
shadow*、Android はelevation。両方書く - 「角丸が画像に効かない」 — 親に
overflow: "hidden" - 「テキストが切れる」 — 親の高さ・
numberOfLines・flexShrinkを確認 - 「ボタンが反応する範囲が小さい」 —
hitSlopを付ける - 「z-index が効かない」 — 同じ親の下である必要・Android では
elevationも
✓ カラートークンを最初に決める(colors.ts 等)
✓ 余白も4/8/12/16/24/32 のような ステップに揃える
✓ テキストサイズも 12/14/16/18/20/24 のように決める
✓ 影・角丸はトークン化(shadows.card 等)
✓ ダーク/ライト両方で当たり前にコントラストが満たせるパレットにする(a11y / 色・コントラスト)