Хотите заказать веб-сайт? Связаться с нами

Grid и Flex в Mantine: адаптивная верстка

В прошлом уроке мы собрали каркас приложения с помощью AppShell, Navbar и Header — то есть научились управлять расположением крупных блоков интерфейса. Но что делать, когда нужно аккуратно разложить контент внутри этих блоков: карточки товаров, колонки формы или произвольный набор виджетов на дашборде? Сегодня разберём два главных инструмента раскладки в Mantine — Grid и Flex — и научимся делать их отзывчивыми под любые экраны.

Grid и Flex в Mantine: адаптивная верстка

Grid — сетка на 12 колонок для строгих макетов

Grid — это компонент-обёртка, который имитирует классическую 12-колоночную сетку, знакомую многим по Bootstrap или другим CSS-фреймворкам.

Внутри он построен на flexbox, но избавляет вас от необходимости вручную считать проценты ширины и писать медиа-запросы. Компонент состоит из двух частей: сам Grid задаёт контейнер и общие настройки — например, отступы между колонками через проп gutter, а каждый дочерний элемент оборачивается в Grid.Col, у которого указывается ширина через проп span.

Ширина колонки задаётся числом от 1 до 12 — ровно так же, как в классических сеточных системах. Если сумма значений span в одной строке превышает 12, лишние колонки автоматически переносятся на следующую строку, поэтому не нужно вручную считать переносы. Значение span={12} растягивает колонку на всю ширину контейнера, что удобно для полноширинных блоков вроде баннеров или разделителей.

import { Grid } from '@mantine/core';
 
function ProductLayout() {
  return (
    <Grid gutter="md">
      <Grid.Col span={8}>
        <div>Основной контент</div>
      </Grid.Col>
      <Grid.Col span={4}>
        <div>Боковая панель</div>
      </Grid.Col>
    </Grid>
  );
}

Кроме ширины, у Grid.Col есть проп offset, который сдвигает колонку вправо на указанное количество виртуальных колонок, оставляя пустое пространство слева. Это удобно, когда нужно визуально выделить блок по центру или создать асимметричный макет без лишних пустых div-контейнеров. А проп gutter у родительского Grid принимает значения из шкалы отступов темы — xs, sm, md, lg, xl — так что расстояния между колонками остаются согласованными с остальным интерфейсом.

Адаптивность Grid — брейкпоинты под любые экраны

Самое ценное в Grid — это то, что и span, и offset можно задавать не только числом, но и объектом с ключами-брейкпоинтами: base, xs, sm, md, lg, xl. Значение base применяется по умолчанию для самых узких экранов, а остальные ключи переопределяют его начиная с соответствующей ширины окна. Брейкпоинты берутся из темы приложения, поэтому если вы настраивали их в createTheme в одном из прошлых уроков, Grid автоматически будет их учитывать.

import { Grid } from '@mantine/core';
 
function CardsRow() {
  return (
    <Grid>
      <Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
        <div>Карточка 1</div>
      </Grid.Col>
      <Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
        <div>Карточка 2</div>
      </Grid.Col>
      <Grid.Col span={{ base: 12, md: 4 }}>
        <div>Карточка 3</div>
      </Grid.Col>
    </Grid>
  );
}

В примере выше на мобильных экранах каждая карточка занимает всю ширину строки (base: 12), на планшетах — половину (sm: 6), а на больших мониторах — треть (md: 4), и в итоге в одну строку помещается три карточки. Такой подход избавляет от написания собственных медиа-запросов и держит логику адаптивности прямо в разметке компонента, что значительно упрощает чтение кода — не нужно прыгать между JSX и CSS-файлом, чтобы понять, как элемент будет вести себя на разных экранах.

Лайфхак: если один и тот же адаптивный объект вроде { base: 12, sm: 6, md: 4 } повторяется в нескольких местах разметки, вынесите его в отдельную константу за пределы компонента. React не будет пересоздавать этот объект при каждом рендере, а если колонка обёрнута в memo, стабильная ссылка на проп убережёт её от лишних перерисовок.

Flex — гибкая раскладка без сетки

Если Grid хорош для строгих, предсказуемых макетов, то Flex — это лёгкая обёртка над обычным display: flex, которая нужна, когда сетка из 12 колонок избыточна. Компонент принимает практически все стандартные flex-свойства в виде пропсов: direction, wrap, align, justify и gap. Разница с обычным CSS в том, что значения вроде gap можно указывать токенами шкалы отступов темы (xs, sm, md) вместо магических чисел в пикселях, а сам компонент по умолчанию рендерится как div, так что не тянет за собой лишней разметки.

Вот несколько типичных ситуаций, где Flex удобнее сетки:

  1. Панель инструментов с логотипом слева и кнопками действий справа
  2. Строка с тегами или бейджами произвольной длины, которая переносится на новую строку
  3. Группа кнопок формы, выровненная по правому краю
  4. Список аватаров пользователей с равными отступами между ними
import { Flex } from '@mantine/core';
 
function Toolbar() {
  return (
    <Flex justify="space-between" align="center" gap="sm" wrap="wrap">
      <div>Логотип</div>
      <div>Поиск</div>
      <div>Профиль</div>
    </Flex>
  );
}

Обратите внимание на проп wrap="wrap" — без него содержимое Flex по умолчанию не переносится на новую строку и может вылезти за пределы контейнера на узких экранах. А значение justify="space-between" равномерно распределяет пространство между дочерними элементами, что часто нужно именно для панелей инструментов, как в примере выше.

Так же, как и у Grid, большинство пропсов Flex поддерживают адаптивные объекты с брейкпоинтами. Это особенно полезно для direction: например, на мобильном экране элементы можно расположить в колонку, а на десктопе — выстроить в строку, поменяв всего одно значение.

import { Flex, Button } from '@mantine/core';
 
function ActionsBar() {
  return (
    <Flex
      direction={{ base: 'column', sm: 'row' }}
      gap={{ base: 'xs', sm: 'md' }}
      justify="flex-end"
    >
      <Button variant="default">Отмена</Button>
      <Button>Сохранить</Button>
    </Flex>
  );
}

Grid vs Flex — как выбрать инструмент

На первый взгляд кажется, что Flex и Grid взаимозаменяемы, ведь оба построены поверх flexbox и оба умеют быть отзывчивыми. Но у них разное назначение, и выбор правильного инструмента экономит время на будущих правках макета.

Задача Что использовать
Строгая сетка колонок с фиксированными пропорциями Grid
Произвольное число элементов без деления на 12 колонок Flex
Форма с несколькими полями в ряд на десктопе и в столбик на мобильном Grid
Панель инструментов, теги, группа кнопок Flex
Одинаковые по важности карточки без сложной пропорции ширины SimpleGrid

Проще говоря, если вы заранее знаете, сколько колонок должно быть в строке и как они соотносятся между собой по ширине — берите Grid. Если же вам нужно просто аккуратно расположить произвольное количество элементов друг за другом с одинаковыми отступами — Flex справится с меньшим количеством кода.

Робот собирает из блоков сайт

Собери свой код. Запусти сайт!

От наброска на салфетке до первого работающего лендинга. Наш онлайн-курс «Веб-верстка с нуля и до профессионала» — это интенсивный трек, где ты не будешь зубрить теорию, а с первого дня начнешь превращать идеи в чистый HTML и CSS.

Собери свой первый проект под руководством практикующих разработчиков.

Подробнее о курсе

Комбинируем Grid, Flex и SimpleGrid на практике

В реальных проектах редко используется что-то одно — обычно Grid, Flex и ещё один полезный компонент, SimpleGrid, комбинируются друг с другом. SimpleGrid удобен, когда нужно разложить набор одинаковых по важности элементов — например, карточки статистики на дашборде — и вместо ручного указания span для каждой колонки достаточно один раз задать количество колонок через проп cols, в том числе адаптивно.

import { SimpleGrid, Flex, Card, Text } from '@mantine/core';
 
function Dashboard({ stats }: { stats: { label: string; value: number }[] }) {
  return (
    <SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="md">
      {stats.map((stat) => (
        <Card key={stat.label} withBorder padding="lg">
          <Flex direction="column" gap={4}>
            <Text size="sm" c="dimmed">{stat.label}</Text>
            <Text size="xl" fw={700}>{stat.value}</Text>
          </Flex>
        </Card>
      ))}
    </SimpleGrid>
  );
}

В этом примере SimpleGrid отвечает за внешнюю раскладку карточек и их количество в строке на разных экранах, а Flex внутри каждой карточки выстраивает подпись и значение друг под другом с небольшим отступом. Это частый паттерн: внешний контейнер — Grid или SimpleGrid, а внутреннее содержимое каждого блока — Flex, потому что там обычно нужно просто выровнять пару элементов, а не строить сложную сетку.

Рендеря список карточек через map, не выносите логику каждой карточки в новую стрелочную функцию внутри JSX без необходимости — лучше вынести содержимое карточки в отдельный компонент. Так React сможет применить memo к этому компоненту и не перерисовывать все карточки сразу, если поменялись данные только одной из них, а key вроде stat.label должен быть стабильным и уникальным, а не индексом массива.

Итоги

Сегодня мы разобрали два основных способа раскладки контента в Mantine. Grid подходит для строгих макетов с предсказуемым количеством колонок и хорошо работает вместе с адаптивными объектами span и offset. Flex удобен для более свободного расположения элементов и панелей инструментов, а SimpleGrid закрывает частый случай с одинаковыми по ширине карточками. Вместе эти три компонента покрывают подавляющее большинство задач по вёрстке интерфейса без единой строчки CSS с медиа-запросами.

В следующем уроке мы поговорим о том, как сообщать пользователю о результате его действий: разберём модальные окна на основе компонента Modal и всплывающие уведомления через функцию notifications.show().

Теги: