React Native

Flexbox і layout у React Native

Колонка за замовчуванням, осі, align/justify, flex і gap, safe area, клавіатура — і п’ять типових екранів для мобільної верстки

Flexbox і layout у React Native

Навіщо ця стаття

У попередній статті з’явилися «цеглини» UI: View, Text, Image, Pressable, ScrollView, safe area. Якщо просто скласти їх підряд, екран швидко виглядає «як стопка блоків»: заголовок прилип до краю, кнопка кудись зникла під клавіатурою, картки різної висоти «танцюють», а між елементами — або нуль, або випадкові margin.

Layout (розкладка) — це правила, за якими контейнер розміщує вкладені елементи (children) у просторі: колонка чи рядок, хто розтягується, хто лишається фіксованим, куди йде «вільне місце», що робить екран, коли з’являється клавіатура.

У React Native майже весь layout будується на Flexbox — тій самій ідеї, що й у CSS Flexbox у браузері, але з кількома важливими відмінностями. Найчастіша помилка після вебу: чекати, що View поводиться як div з display: block і «рядком за замовчуванням».

Після статті:

  1. Зрозуміло, що таке main axis і cross axis і чому за замовчуванням вкладені елементи йдуть колонкою.
  2. Ви впевнено користуєтесь justifyContent, alignItems, gap, flex, flexWrap.
  3. Знаєте п’ять типових патернів екрана: header/body/footer, split, grid, sticky CTA, empty state.
  4. Розумієте, навіщо KeyboardAvoidingView і як узгодити його з safe area.
  5. У Nomad — чіткіша структура стрічки поїздок (шапка / список / закріплена кнопка).
Головна думка. У React Native кожен View — уже flex-контейнер. Напрямок за замовчуванням — колонка (flexDirection: 'column'), а не рядок, як часто звикли з web-flex. Спочатку оберіть вісь (flexDirection), потім — як розподілити місце вздовж неї (justifyContent) і як вирівняти вкладені елементи по cross axis (alignItems).
Живі прев’ю. Як і в статті про базові компоненти, тут багато ::react-native-preview (react-native-web у рамці телефону). Імпорти @/…, expo-* і Router у прев’ю не працюють — демо самодостатні. Клавіатура й системні вирізи в браузері відчуваються слабше, ніж на реальному пристрої: фінальну перевірку KAV і safe area робіть у Expo Go / симуляторі.

Місток з web CSS

У браузері (CSS)У React NativeКоментар
display: flex треба вмикатиView уже flexНемає display: block / inline у тому ж сенсі
За замовчуванням flex-direction: rowЗа замовчуванням columnНайважливіша різниця
justify-contentjustifyContentcamelCase у style-об’єкті
align-itemsalignItemsТе саме
gapgap (сучасний RN)Зручніше за «margin на кожному вкладеному елементі»
CSS Gridнемає в core RNСітки збирають flex + wrap або бібліотеками
position: absoluteєДля оверлеїв і sticky-імітацій; основа все одно flex
vh / складні одиниціобмеженоЧастіше %, числа в dp, flex: 1
Web-звичка. Написати flexDirection: 'row' «бо так у всіх туторіалах CSS» і дивуватися, чому «все стало в один рядок і роз’їхалось». У мобільному UI колонка екрана — норма: заголовок зверху, контент нижче, кнопка внизу. row — для внутрішніх рядків (іконка + текст, два стовпці, чіпи).

Дві осі: main і cross

Уявіть шухляду, у яку ви складаєте книги.

  • Main axis (головна вісь) — напрямок, у якому елементи йдуть один за одним (у колонці — зверху вниз, у рядку — зліва направо).
  • Cross axisперпендикулярна до main: якщо main — зверху вниз, cross — ліворуч/праворуч; якщо main — зліва направо, cross — зверху вниз.

Властивість flexDirection обирає main axis:

ЗначенняMain axisВкладені елементи…
column (за замовчуванням)зверху внизодин під одним
column-reverseзнизу вгорузворотний порядок
rowзліва направов один ряд
row-reverseсправа наліворяд у зворотному порядку
justifyContent
уздовж main axis
Розподіляє вільне місце між вкладеними елементами (і навколо них) вздовж main axis.
flex-start — зібрати на початку; center — по центру; flex-end — в кінці; space-between / space-around / space-evenly — рівномірно розподілити проміжки (з різною логікою).
alignItems
уздовж cross axis
Вирівнює вкладені елементи вздовж cross axis (перпендикулярно до main). За замовчуванням у RN часто stretch: елемент розтягується на всю «товщину» cross axis (у колонці — на всю ширину контейнера), якщо в нього немає фіксованого розміру в цьому напрямку.
alignSelf
на одному елементі
Те саме, що alignItems, але лише для одного елемента. Зручно: контейнер stretch, а один бейдж — alignSelf: 'flex-start', щоб не розтягувався на всю ширину.
gap
number
Однаковий проміжок між вкладеними елементами (не зовні контейнера). Замінює «у кожної картки marginBottom, у останньої — нуль».
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff
skinparam defaultFontName Helvetica

rectangle "flexDirection: column (за замовчуванням)" as C {
  rectangle "Main axis ↓" as M1
  rectangle "A" as A1
  rectangle "B" as B1
  rectangle "C" as C1
  M1 -[hidden]-> A1
  A1 --> B1
  B1 --> C1
}

rectangle "flexDirection: row" as R {
  rectangle "Main axis →" as M2
  rectangle "A" as A2
  rectangle "B" as B2
  rectangle "C" as C2
  M2 -[hidden]-> A2
  A2 --> B2
  B2 --> C2
}

@enduml

Живе демо: column vs row

TSXFlexDirectionDemo.tsx
iPhone
9:41

Loading…

react-native-web · not a real device


justifyContent — місце вздовж головної осі

Коли контейнер вищий (у колонці) або ширший (у рядку), ніж сума вкладених елементів, з’являється вільний простір. justifyContent каже, куди його подіти.

ЗначенняЩо бачить користувач (колонка)
flex-startУсе зверху, низ порожній
flex-endУсе знизу, верх порожній
centerБлок по центру вертикалі
space-betweenПерший біля старту, останній біля кінця, рівні дірки між
space-aroundНапівпроміжки на краях, повні між елементами
space-evenlyОднакові відстані скрізь, включно з краями
Коли що.
  • Екран «по центру» (логін, splash-контент) → justifyContent: 'center'.
  • Шапка зверху + кнопка внизу без «роз’їзду» середини → часто не space-between на весь екран, а схема header / flex:1 body / footer (нижче).
  • Чіпи в рядку з рівними дірками → space-between або краще gap + flex-start.

Playground: justifyContent

TSXJustifyPlayground.tsx
iPhone
9:41

Loading…

react-native-web · not a real device


alignItems — вирівнювання по cross axis

У колонці cross axis — горизонталь: alignItems керує «ліво / центр / право / розтягнути на всю ширину».
У рядку cross axis — вертикаль: «верх / середина / низ / розтягнути по висоті».

ЗначенняТиповий ефект у колонці
stretch (часте default)Діти без власної ширини займають усю ширину
flex-startПритиснути вліво (у LTR)
centerПо центру горизонталі
flex-endВправо
baselineЗа базовими лініями тексту (рідше на старті)
Чому кнопка «на всю ширину». Якщо батько — колонка з alignItems: 'stretch' (або default stretch), а в кнопки немаєalignSelf: 'flex-start' і немає фіксованої width, вона розтягнеться. Це не баг — так працює stretch. Для «кнопки за вмістом» поставте alignSelf: 'flex-start' або оберніть у рядок.

Playground: alignItems + row

TSXAlignPlayground.tsx
Android
9:41

Loading…

react-native-web · not a real device


flex: 1 і «хто з’їдає вільне місце»

Число flex на вкладеному елементі каже: «я хочу частку вільного місця батька вздовж головної осі».

  • flex: 1 — візьми рівну частку з іншими flex: 1.
  • flex: 2 поруч із flex: 1 — удвічі більшу частку.
  • Без flex (і без фіксованої висоти/ширини по осі) елемент зазвичай займає лише стільки, скільки потрібно вмісту (плюс padding/border).

Найкорисніший патерн екрана:

View (flex: 1)                 ← екран на всю висоту
  Header (без flex)            ← висота за вмістом
  Body   (flex: 1)             ← «решта» висоти
  Footer (без flex)            ← висота за вмістом

Саме так кнопка «липне» до низу без абсолютного позиціонування: body забирає все, що лишилось між шапкою й футером.

flex: 1 на корені екрана. Корінь (Screen / SafeAreaView / зовнішній View) майже завжди має flex: 1, інакше контейнер «стискається» до вмісту й justifyContent: 'center' центрує в крихітній смужці, а не на весь телефон.

Демо: flex-частки

TSXFlexGrowDemo.tsx
iPhone
9:41

Loading…

react-native-web · not a real device

Grow, shrink, basis — три важелі замість «магічного» flex: 1

У CSS і в React Native запис flex: 1 — це скорочення. Під капотом layout-рушій дивиться на три окремі властивості. Коли рядок «ламається», кнопка з’їдає текст, або блок не хоче рости — корисно крутити їх окремо, а не лише flex: 1.

Порядок думки (main axis).
  1. flexBasis — з якого розміру стартуємо (бажана ширина в row, висота в column).
  2. Якщо після цього залишилось місце в контейнері → ділимо його за flexGrow.
  3. Якщо місця не вистачає → стискаємо за flexShrink.
flexBasis
number | 'auto'
Стартовий розмір уздовж головної осі до grow/shrink.
  • Число (наприклад 100) — «хочу близько 100 dp».
  • 'auto' — орієнтуйся на вміст / width / height.
    Це не жорсткий width: далі grow може розтягнути, shrink — стиснути.
flexGrow
number (зазвичай 0 або 1+)
Наскільки елемент росте, коли в контейнері є вільне місце.
  • 0 — не рости (лишайся біля basis).
  • 1 — бери частку вільного місця.
  • Два сусіди з flexGrow: 1 і 2 ділять «залишок» як 1 : 2.
    Саме grow «ховається» всередині звичного flex: 1.
flexShrink
number (зазвичай 0 або 1)
Наскільки елемент стискається, коли basis-и не влазять у контейнер.
  • 1 (типово для тексту в рядку) — можна стиснути.
  • 0 — «не чіпай мій розмір» (іконка, бейдж, кнопка фіксованої ширини).
    Без shrink довгий заголовок у row часто виштовхує правий хвіст за край екрана.
Зв’язок із flex: 1. У більшості випадків flex: 1 означає приблизно: «basis гнучкий / мінімальний, рости (grow), умій стискатися (shrink)». Для навчання нижче крутимо три поля окремо — так видно, який важіль що робить.

1. flexBasis — стартовий розмір

Обидва блоки в одному рядку. У кожного свій basis (синій 80, зелений 160). flexGrow: 0, тож вони не розтягуються на всю ширину — видно «бажану» ширину.

TSXFlexBasisDemo.tsx
iPhone
9:41

Loading…

react-native-web · not a real device

2. flexGrow — хто забирає вільне місце

Ті самі basis (80 і 160), але тепер у зеленого flexGrow: 1, у синього 0. Вільне місце справа «віддається» зеленому.

TSXFlexGrowOnlyDemo.tsx
Android
9:41

Loading…

react-native-web · not a real device

3. flexShrink — хто поступається, коли тісно

Спеціально ставимо великі basis (сума більша за ширину рядка). Червоний з flexShrink: 0 майже не здає ширину; синій з flexShrink: 1 стискається.

TSXFlexShrinkDemo.tsx
iPhone
9:41

Loading…

react-native-web · not a real device

4. Playground: крутимо grow / shrink / basis разом

Нижче три блоки в одному рядку. Чіпами змінюйте властивості середнього (зеленого) — одразу видно, як змінюється розкладка. Краї (синій / червоний) фіксовані: basis 56, grow 0, shrink 0 — як іконки-«якорі».

TSXFlexGSBPlayground.tsx
iPhone
9:41

Loading…

react-native-web · not a real device

5. Життєвий рядок: іконка · текст · ціна

Те саме трьома важелями без «демо-кольорів» — типовий list item.

Зонаbasis / widthgrowshrinkНавіщо
Іконка злівафіксована 4400не стискати, не розтягувати
Текстauto / м’який11забрати середину, обрізатись при нестачі
Ціна справаза вмістом00завжди читабельна
TSXFlexGSBRealRow.tsx
Android
9:41

Loading…

react-native-web · not a real device

flexBasis: 0 + flexGrow: 1 на тексті. Частий прийом у рядках: «не орієнтуйся на довжину рядка як на basis, а одразу діли вільне місце і стискайся». Разом із numberOfLines={1} і (за потреби) minWidth: 0 довгі назви коректно отримують , а ціна не з’їжджає.
Плутанина width vs flexBasis.
  • width: 100 — жорсткіша геометрія в багатьох випадках.
  • flexBasis: 100участь у flex-грі: далі grow/shrink можуть змінити підсумковий розмір.
    Для «іконка завжди 44» достатньо width/height. Для «хочу ~120, але можу віддати місце» — basis + shrink.

gap, flexWrap і «сітка» без CSS Grid

gap

gap: 12 на контейнері ставить 12 між сусідами. Не треба:

// крихкіше
item: { marginBottom: 12 }
// і окремо «останній без margin»

Працює і для row, і для column, і для wrap.

flexWrap: 'wrap'

Коли вкладених елементів більше, ніж вміщується в один ряд (при flexDirection: 'row'), вони переносяться на наступний. Так збирають:

  • сітку іконок 2×N або 3×N;
  • «хмару» чіпів фільтрів;
  • плитки категорій.

Ширину плитки часто задають як відсоток від батька, з урахуванням gap (наприклад, дві колонки ≈ '48%' + gap, або точніший розрахунок).

Демо: wrap-сітка

TSXWrapGridDemo.tsx
iPhone
9:41

Loading…

react-native-web · not a real device

Відсотки.width: '48%' — відсоток від ширини батька. Дві клітинки + gap візуально дають дві колонки. На дуже вузьких екранах інколи переходять на одну колонку (width: '100%') через перевірку ширини (useWindowDimensions) — це вже адаптив; базова ідея wrap та сама.

Це скелет більшості мобільних екранів: фіксована шапка, прокручувана середина, стабільний низ (кнопка «Зберегти», «Далі», «Нова поїздка»).

Корінь flex: 1

Екран займає всю доступну висоту (під safe area — окремо).

Header без flex

Висота за текстом і відступами. Не розтягується.

Body з flex: 1

Забирає решту. Усередині часто ScrollView з style={{ flex: 1 }} і contentContainerStyle для padding.

Кнопка завжди видима внизу (поки не відкрита клавіатура — про неї нижче).

TSXHeaderBodyFooter.tsx
iPhone
9:41

Loading…

react-native-web · not a real device

Поширена помилка. Покласти кнопку всерединуScrollView в кінці списку — і на довгому списку користувач не бачить CTA, доки не долистає вниз. Для «завжди на виду» CTA виносять у footer поза скролом (sticky bottom). Для «природно в кінці форми» — лишають у скролі. Обидва варіанти валідні; питання продукту, не «єдиної істини».

Патерн: split (два регіони)

Split — екран, поділений на дві зони: наприклад, карта зверху + список знизу, або ілюстрація + форма. Реалізація: колонка, два вкладені блоки з flex: 1 (порівну) або flex: 2 / flex: 1 (2/3 і 1/3).

TSXSplitLayout.tsx
Android
9:41

Loading…

react-native-web · not a real device


Патерн: sticky CTA

Sticky CTA (call to action, що «липне») — кнопка дії завжди в зоні великого пальця. Технічно це той самий footer з попереднього патерну; акцент на продуктовій ролі: «оплатити», «зберегти чернетку», «додати в кошик».

Іноді CTA малюють через position: 'absolute', bottom: 0, left: 0, right: 0. Це працює, але:

  • треба падінг знизу в ScrollView, інакше останні рядки сховаються під кнопкою;
  • safe area знизу (home indicator) треба додати вручну (useSafeAreaInsets().bottom).

Схема header/body/footer без absolute зазвичай простіша для новачка.


Патерн: empty state

Коли списку немає (немає поїздок, порожній пошук), показують порожній стан: ілюстрація або іконка, короткий текст, кнопка «що робити далі». Layout — колонка по центру (flex: 1, justifyContent: 'center', alignItems: 'center').

TSXEmptyState.tsx
iPhone
9:41

Loading…

react-native-web · not a real device


Safe area глибше

У статті про компоненти вже був safe area: зона екрана, яку не перекривають виріз камери (notch / Dynamic Island), статус-бар, home indicator.

Два API

ПідхідЗвідкиКоли
SafeAreaView з react-nativecoreШвидкий прототип; на Android поведінка історично обмеженіша
react-native-safe-area-contextпакет (у Expo Router уже є)Рекомендовано для продукту: SafeAreaProvider, SafeAreaView, useSafeAreaInsets()

У Nomad примітив Screen уже на SafeAreaView з safe-area-context.

useSafeAreaInsets()

Хук повертає { top, bottom, left, right } у пікселях. Корисно, коли:

  • sticky-кнопка з position: 'absolute' — додаєте paddingBottom: insets.bottom + 16;
  • кастомний хедер у модалці;
  • екран на весь зріст під статус-бар (картинка edge-to-edge), а текст — уже з paddingTop: insets.top.
Padding у SafeAreaView. Офіційний SafeAreaView з core RN реалізує відступи через padding; власний padding на тому ж компоненті може ігноруватись або поводитись дивно. Тому padding контенту часто ставлять на внутрішнійView / ScrollView, а не «все в один SafeAreaView-стиль».

Краї та горизонталь

На iPhone з «вухами» інколи важливі left / right (landscape). Для більшості портретних екранів курсу достатньо top + bottom.


Клавіатура: KeyboardAvoidingView

На телефоні екранна клавіатура наїжджає знизу й може закрити поле вводу або кнопку «Надіслати». Користувач не бачить, що друкує — і це виглядає як зламаний застосунок.

KeyboardAvoidingView — контейнер, який зсуває або стискає свій вміст, коли клавіатура відкрита.

behavior
'height' | 'position' | 'padding'
Як реагувати. На iOS часто 'padding', на Android — 'height' (або інший варіант після перевірки на пристрої). Документація радить завжди задавати behavior явно — платформи інтерпретують його по-різному.
keyboardVerticalOffset
number
Додатковий зсув, якщо над KAV є хедер навігації, статус-бар тощо. Якщо «не дотягує» або «перелітає» — крутять цей offset.
enabled
boolean
Можна вимкнути на екранах без інпутів.

Типовий каркас:

import {
  KeyboardAvoidingView,
  Platform,
  TextInput,
  View,
  StyleSheet,
} from 'react-native';

export function LoginForm() {
  return (
    <KeyboardAvoidingView
      style={styles.root}
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 0}
    >
      <View style={styles.inner}>
        <TextInput placeholder="Email" style={styles.input} />
        {/* кнопка залишається видимою над клавіатурою */}
      </View>
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  root: { flex: 1 },
  inner: { flex: 1, justifyContent: 'flex-end', padding: 16, gap: 12 },
  input: {
    borderWidth: 1,
    borderColor: '#E2E8F0',
    borderRadius: 12,
    padding: 12,
    fontSize: 16,
  },
});
Часто behavior="padding": контейнер додає padding знизу ≈ висоті клавіатури. Перевіряйте на симуляторі з різними розмірами телефонів. Якщо є кастомний header — підставте keyboardVerticalOffset.
Dismiss. Щоб сховати клавіатуру тапом поза полем: Keyboard.dismiss() (наприклад, з Pressable на фон або ScrollView + keyboardShouldPersistTaps). Повний розбір форм — у статті про TextInput і валідацію.
KAV + ScrollView + SafeArea. Три шари легко «повоювати» за висоту. Практичне правило для старту:
SafeAreaView/ScreenKeyboardAvoidingView (flex:1)ScrollView (flex:1) → форма.
Якщо щось стрибає — спрощуйте дерево, перевіряйте flex: 1 на кожному рівні, підкручуйте offset. Бібліотеки на кшталт react-native-keyboard-controller — пізніше, коли базовий KAV не вистачає.

Демо форми з KAV (у web клавіатура умовна)

TSXKavForm.tsx
iPhone
9:41

Loading…

react-native-web · not a real device


Рядок «іконка + текст + хвіст» (дуже частий атом)

Майже кожен список у мобільному UI — рядок:

[аватар/іконка]  Заголовок ..............  [шеврон / дата]
                 Підзаголовок

Збирається так:

  1. Зовнішній View / Pressable з flexDirection: 'row', alignItems: 'center', gap.
  2. Фіксований квадрат зліва.
  3. Середина з flex: 1 (забирає ширину, текст може обрізатись numberOfLines).
  4. Правий блок без flex: 1 (ширина за вмістом).
TSXRowAtom.tsx
iPhone
9:41

Loading…

react-native-web · not a real device


Антипатерни layout


Міні-проєкт: «5 layout-екранів»

Окремий маленький застосунок лише на layout (не Nomad). Мета — перемикати п’ять екранів-патернів і очима побачити різницю.

Постановка

Зібрати Expo blank TypeScript-проєкт, у якому:

  1. Shell — header/body/footer зі списком.
  2. Split — дві flex-зони.
  3. Grid — wrap-сітка плиток.
  4. Sticky CTA — довгий скрол + кнопка внизу поза скролом.
  5. Empty — порожній стан по центру.

Перемикач екранів — рядок чіпів зверху (flex row + wrap).

Крок 1. Створити проєкт

cd /path/to  # поруч: git clone https://github.com/arakviel/nomad.git
npx create-expo-app@latest layout-lab -t blank-typescript@sdk-54
cd layout-lab
npx expo start

Відкрити в Expo Go (SDK як у курсі).

Крок 2. Замінити App.tsx

Нижче — один файл з п’ятьма екранами. У blank-шаблоні точка входу вже підключає App. Повний лістинг згорнуто — розгорніть, коли будете копіювати в проєкт або звірятись.

Крок 3. Анатомія

SafeAreaView + tabs

Чіпи — row + wrap + gap. Активний таб підсвічується окремим стилем.

stage з flex: 1

Уся решта висоти під табами — сцена для поточного патерну.

Shell / Sticky

Однакова ідея: колонка flex:1, середина ScrollView з flex:1, низ — footer без flex.

Split

Дві зони з flex: 2 і flex: 1 без скролу — чисто про частки висоти.

Grid

flexDirection: 'row', flexWrap: 'wrap', width: '48%'.

Empty

flex: 1 + justifyContent: 'center' + alignItems: 'center'.

Критерій «готово»

  • Усі 5 табів щось показують без вильотів layout.
  • У Shell і Sticky кнопка видима без долистування вниз.
  • Grid має щонайменше 2 колонки на телефоні.
  • Empty виглядає відцентрованим, а не прибитим до верху.

Живе прев’ю (після кроків)

TSXLayoutLab.tsx
iPhone
9:41

Loading…

react-native-web · not a real device

Міні-проєкт навмисно без tokens і @/ — фокус на flex. У Nomad нижче ті самі ідеї з design tokens і feature-структурою.

Nomad: shell стрічки (header / list / sticky CTA)

Навіщо користувачу

На домашньому екрані важливо: зрозуміти «де я», бачити поїздки й завжди мати під рукою «Нова поїздка». Це класичний mobile shell, не «все в одному ScrollView».

Нитка проєкту

Уже є (з попередніх статей — не викидаємо):

  • Mock-картки TripCard + mockTrips (стаття 06).
  • Стрічка через map (поки без віртуалізації).

Додаємо в цій статті:

  • Layout shell: sticky header + прокручуваний список (flex: 1) + sticky footer з «Нова поїздка».
  • Мета «N поїздок» і empty state, якщо масив порожній.
  • Кнопка CTA не ховається в кінці довгого скролу.

Повний знімок проєкту після цієї статті

Готовий код 1:1 з Nomad. Найшвидший шлях: git pull у клоні репо. Нижче — весь проєкт на момент цієї статті (усі файли з кодом), не фрагмент «лише нові папки». Клік по файлу в дереві → повний вміст для копіпасту.
cd /path/to/nomad
git pull
npm install
npx expo start

Перевірка

  1. Шапка й кнопка «Нова поїздка» лишаються на місці під час скролу списку.
  2. Список займає середню зону (flex: 1).
  3. Якщо тимчасово mockTrips = [] — видно empty state (потім поверніть дані).

Коміт

Якщо збирали вручну (не через git pull на вже запушений репо):

cd /path/to/nomad
git add -A
git commit -m "$(cat <<'EOF'
feat: trips list shell layout with sticky CTA

Material: content/15.react-native/07.flexbox-and-layout.md
EOF
)"
git push

У публічному репо цей коміт уже є — після git pull повторно комітити не потрібно, якщо ви не змінювали код локально.

Результат (прев’ю)

TSXNomadShell.tsx
iPhone
9:41

Loading…

react-native-web · not a real device


Підсумок

Далі — StyleSheet і теми: світла/темна, семантичні токени, як не розмазати кольори по всьому проєкту.


Практичні завдання

Базовий рівень

  1. Пояснити своїми словами, чим justifyContent відрізняється від alignItems у колонці.
  2. Зібрати рядок «аватар + ім’я + шеврон» на flexDirection: 'row'.
  3. На екрані з flex: 1 відцентрувати один Text по вертикалі й горизонталі.

Середній рівень

  1. У layout-lab додати шостий таб «Form» з TextInput + KeyboardAvoidingView.
  2. Зробити сітку 3 колонки (width ≈ 30% + gap) для 9 плиток.
  3. У Nomad тимчасово підставити const trips: Trip[] = [] і перевірити empty state + sticky CTA.

Професійний рівень

  1. Sticky footer з useSafeAreaInsets().bottom (або padding у Screen edges) — щоб кнопка не лізла на home indicator, якщо вимкнути bottom safe area на корені.
  2. Картка поїздки в компактному режимі: cover зліва фіксованої ширини, текст справа (row).
  3. Порівняти (абзац у нотатках) absolute CTA vs footer-flex: плюси/мінуси для accessibility і клавіатури.

Часті запитання

Copyright © 2026