スタイル

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" },
})

StyleSheet.create vs 直書きオブジェクト

どちらでも動くが、慣習として StyleSheet.create を使う。理由:

直書き(OK だが型・警告が弱い)
<View style={{ padding: 16, backgroundColor: "#fff" }} />
直書きは毎レンダーで新オブジェクト

コンポーネントの中で毎回 {...} を書くと、レンダーごとに新しいオブジェクトが作られる。 ホットパスで子の memo を破壊する原因になることがある。 パフォーマンスが気になる箇所は StyleSheet.create または useMemo で固定する。

スタイルの合成

style プロパティには配列を渡せる。後ろが優先(CSS と同じ)。 falsenullundefined は無視される。

<View style={[s.card, isActive && s.cardActive, { marginTop: 8 }]} />

多重配列も OK(再帰的にフラット化される):

<View style={[s.base, [s.row, s.padded], extra]} />

StyleSheet.flatten / StyleSheet.compose

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],
)

値の単位

// 1px の細い線(高 DPI でも 1 物理ピクセル)
<View style={{ height: StyleSheet.hairlineWidth, backgroundColor: "#888" }} />

Flexbox(最重要)

RN は Flexbox がレイアウトの主軸。Grid はビルトインでは無い(サードパーティはある)。

Web との違い

WebRN
flexDirection 既定rowcolumn
display明示が必要すべて flex
flex: 1flex-grow: 1残り全部埋めるflex: 1; flex-shrink: 1; flex-basis: 0% 相当)
alignContentflex-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"個別アイテムの交差軸配置
alignContentjustifyContent と同じ + "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>
2:1 のカラム分割
<View style={{ flexDirection: "row" }}>
  <View style={{ flex: 2, backgroundColor: "#7dd3fc" }} />
  <View style={{ flex: 1, backgroundColor: "#fbbf24" }} />
</View>
右寄せ(margin: auto)
<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 / padding4辺すべて
marginVertical / paddingVertical上下のみ
marginHorizontal / paddingHorizontal左右のみ
marginTop/Right/Bottom/Left個別
marginStart / marginEndRTL(右→左言語)対応の論理プロパティ
<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

プロパティ意味
borderWidth4辺の太さ
borderTop/Right/Bottom/LeftWidth個別
borderColor
borderTop/.../LeftColor個別の色
borderStyle"solid"(既定)/"dotted"/"dashed"
borderRadius4 角の角丸
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 }} />
Android の 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

Android で overflow: visible が効かないケース

Android では親の領域外に描いた子(影、はみ出る画像)が切られることがある。 特に borderRadius + 画像、絶対配置の子要素ではトラブルが起きやすい。
workaround: 親にも明示的に overflow: "visible"、もしくは構造を見直して領域内に収める。

pointerEvents

タッチ判定の制御。

オーバーレイ(透明な層を重ねる)で下のタッチを通したい時に "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*テキスト影
writingDirectionRTL
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-imagecontentFitresizeMode 相当)等の現代的 API。

サイズ・寸法を取得

useWindowDimensions

回転やスプリットビューに自動追従。これを使う。

import { useWindowDimensions } from "react-native"

function Foo() {
  const { width, height, fontScale, scale } = useWindowDimensions()
  return <View style={{ width: width * 0.8 }} />
}

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>

ブレークポイント・状態

<Pressable className="bg-blue-500 active:bg-blue-700 ios:px-4 android:px-3 px-2">
  <Text className="text-white">タップ</Text>
</Pressable>

NativeWind の長所・短所

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-paperMaterial Design。完成された見た目
gluestack-uishadcn/ui 風のヘッドレスコンポーネント
react-native-elements軽量な汎用 UI
NativeBase古めの定番(NativeWind/gluestack に移行傾向)
UI KittenEva Design System ベース

レスポンシブ・タブレット対応

幅でレイアウト切替
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 の差分まとめ

CSSRN StyleSheet
キー命名kebab-casecamelCase
単位px / em / rem / % など数値(DP)か "%"
display 既定blockflex
flexDirection 既定rowcolumn
position fixedありなし
continuous styles の継承ありテキストのみ
:hover / :active / :focusセレクタPressable の関数 / 状態 props
transitionCSSAnimated / 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)} />

よくある原因

スタイル設計の原則

カラートークンを最初に決めるcolors.ts 等)
余白も4/8/12/16/24/32 のような ステップに揃える
テキストサイズも 12/14/16/18/20/24 のように決める
影・角丸はトークン化shadows.card 等)
ダーク/ライト両方で当たり前にコントラストが満たせるパレットにする(a11y / 色・コントラスト