Mantine: Button, ActionIcon и Loader — всё в одном
В прошлом уроке мы изучили все основные компоненты ввода. Теперь пришло время научиться создавать интерактивные элементы, с помощью которых пользователь управляет приложением. В этом уроке мы разберем кнопки (Button), иконки-кнопки (ActionIcon) и индикаторы загрузки (Loader). Эти компоненты вы будете использовать в каждом проекте, поэтому важно знать все их возможности и правильные сценарии применения.
Button – главный элемент управления
Button — это самый важный интерактивный компонент в любом приложении. С его помощью пользователь отправляет формы, подтверждает действия, переходит по ссылкам и выполняет другие ключевые операции.
Основные варианты
Mantine предлагает 6 встроенных вариантов кнопок:
import { Button, Group } from '@mantine/core';
function ButtonVariants() {
return (
<Group>
<Button variant="filled">Filled</Button>
<Button variant="light">Light</Button>
<Button variant="outline">Outline</Button>
<Button variant="subtle">Subtle</Button>
<Button variant="default">Default</Button>
<Button variant="transparent">Transparent</Button>
</Group>
);
}
Когда какой использовать
filled— основной вариант для самых важных действий (отправка формы, сохранение)light— для второстепенных действий (отмена, закрытие)outline— для нейтральных действий, когда кнопка не должна привлекать много вниманияsubtle— для минималистичных интерфейсов, почти как текстовая ссылкаdefault— нейтральный вариант, который не использует основной цвет
Цвета и размеры
Button поддерживает все цвета из темы через проп color , а также предустановленные размеры:
import { Button, Group } from '@mantine/core';
function ButtonColorsAndSizes() {
return (
<>
<Group>
<Button color="blue">Синяя</Button>
<Button color="red">Красная</Button>
<Button color="green">Зеленая</Button>
<Button color="grape">Фиолетовая</Button>
<Button color="orange">Оранжевая</Button>
</Group>
<Group mt="md">
<Button size="xs">XS</Button>
<Button size="sm">SM</Button>
<Button size="md">MD</Button>
<Button size="lg">LG</Button>
<Button size="xl">XL</Button>
</Group>
</>
);
}
Скругление и полная ширина
radius управляет скруглением углов, а fullWidth делает кнопку на всю ширину контейнера:
import { Button, Group } from '@mantine/core';
function ButtonRadiusAndWidth() {
return (
<>
<Group>
<Button radius="xs">XS</Button>
<Button radius="sm">SM</Button>
<Button radius="md">MD</Button>
<Button radius="lg">LG</Button>
<Button radius="xl">XL</Button>
<Button radius={0}>Нет скругления</Button>
</Group>
<Button fullWidth mt="md">
Кнопка на всю ширину
</Button>
</>
);
}
Иконки на кнопках
Mantine рекомендует использовать иконки из библиотеки @tabler/icons-react:
npm install @tabler/icons-react
import { Button, Group } from '@mantine/core';
import { IconHeart, IconShoppingCart, IconUpload } from '@tabler/icons-react';
function ButtonWithIcons() {
return (
<Group>
<Button leftSection={<IconHeart size={18} />}>
Нравится
</Button>
<Button rightSection={<IconShoppingCart size={18} />}>
В корзину
</Button>
<Button
leftSection={<IconUpload size={18} />}
variant="light"
>
Загрузить
</Button>
</Group>
);
}
Градиентный вариант
Для создания ярких акцентных кнопок используйте variant="gradient":
import { Button, Group } from '@mantine/core';
function GradientButton() {
return (
<Group>
<Button
variant="gradient"
gradient={{ from: 'blue', to: 'cyan', deg: 135 }}
>
Градиентная кнопка
</Button>
<Button
variant="gradient"
gradient={{ from: 'grape', to: 'pink', deg: 45 }}
size="lg"
>
Яркая кнопка
</Button>
</Group>
);
}
ActionIcon – иконка как кнопка
ActionIcon — это компонент для создания иконок-кнопок. Он идеально подходит для элементов управления, где нужна только иконка без текста: удаление, редактирование, настройки, переключение тем и т.д.
Базовое использование
import { ActionIcon, Group } from '@mantine/core';
import { IconTrash, IconPencil, IconSettings, IconMoon } from '@tabler/icons-react';
function ActionIconExample() {
return (
<Group>
<ActionIcon aria-label="Удалить">
<IconTrash size={18} />
</ActionIcon>
<ActionIcon aria-label="Редактировать" variant="light">
<IconPencil size={18} />
</ActionIcon>
<ActionIcon aria-label="Настройки" variant="filled" color="blue">
<IconSettings size={18} />
</ActionIcon>
<ActionIcon aria-label="Переключить тему" variant="default" size="lg">
<IconMoon size={20} />
</ActionIcon>
</Group>
);
}
Важно: ActionIcon не имеет встроенной подписи, поэтому всегда используйте aria-label для доступности. Это критично для пользователей скринридеров.
Размеры и цвета
import { ActionIcon, Group } from '@mantine/core';
import { IconHeart } from '@tabler/icons-react';
function ActionIconSizes() {
return (
<Group>
<ActionIcon size="xs" aria-label="XS">
<IconHeart size={12} />
</ActionIcon>
<ActionIcon size="sm" aria-label="SM">
<IconHeart size={14} />
</ActionIcon>
<ActionIcon size="md" aria-label="MD">
<IconHeart size={18} />
</ActionIcon>
<ActionIcon size="lg" aria-label="LG">
<IconHeart size={22} />
</ActionIcon>
<ActionIcon size="xl" aria-label="XL">
<IconHeart size={26} />
</ActionIcon>
</Group>
);
}
Варианты и цвета
import { ActionIcon, Group } from '@mantine/core';
import { IconStar } from '@tabler/icons-react';
function ActionIconVariants() {
return (
<Group>
<ActionIcon variant="filled" color="yellow" aria-label="Звезда">
<IconStar size={18} />
</ActionIcon>
<ActionIcon variant="light" color="yellow" aria-label="Звезда">
<IconStar size={18} />
</ActionIcon>
<ActionIcon variant="outline" color="yellow" aria-label="Звезда">
<IconStar size={18} />
</ActionIcon>
<ActionIcon variant="subtle" color="yellow" aria-label="Звезда">
<IconStar size={18} />
</ActionIcon>
</Group>
);
}
Loader – индикатор загрузки
Loader — это самостоятельный компонент для отображения процесса загрузки. Он используется как внутри кнопок, так и отдельно на страницах и в оверлеях.
Типы загрузчиков
Mantine предлагает 3 встроенных типа: oval, bars и dots:
import { Loader, Group } from '@mantine/core';
function LoaderTypes() {
return (
<Group>
<Loader type="oval" />
<Loader type="bars" />
<Loader type="dots" />
</Group>
);
}
Цвета и размеры
import { Loader, Group } from '@mantine/core';
function LoaderColorsAndSizes() {
return (
<>
<Group>
<Loader color="blue" />
<Loader color="red" />
<Loader color="green" />
<Loader color="grape" />
</Group>
<Group mt="md">
<Loader size={20} />
<Loader size={30} />
<Loader size={40} />
<Loader size={50} />
</Group>
</>
);
}
size принимает числа (переводятся в rem) или CSS-значения, например size="2rem".
Состояние загрузки в кнопках
Самая важная практическая задача — показывать загрузку во время отправки формы или выполнения асинхронной операции.
import { Button, Group } from '@mantine/core';
import { useState } from 'react';
function ButtonLoading() {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
// Имитация асинхронной операции
await new Promise((resolve) => setTimeout(resolve, 2000));
setLoading(false);
};
return (
<Group>
<Button loading onClick={handleClick}>
Загрузить
</Button>
<Button
loading={loading}
onClick={handleClick}
loaderProps={{ type: 'bars' }}
>
{loading ? 'Загрузка...' : 'Нажми меня'}
</Button>
<Button
loading
loaderPosition="right"
leftSection={<span>✨</span>}
>
Справа
</Button>
</Group>
);
}
Важные пропсы для загрузки кнопок
loading—boolean, включает состояние загрузкиloaderPosition—'left'или'right'(по умолчанию'left')loaderProps— передает пропсы вLoader(тип, размер, цвет)
Собери свой код. Запусти сайт!
От наброска на салфетке до первого работающего лендинга. Наш онлайн-курс «Веб-верстка с нуля и до профессионала» — это интенсивный трек, где ты не будешь зубрить теорию, а с первого дня начнешь превращать идеи в чистый HTML и CSS.
Собери свой первый проект под руководством практикующих разработчиков.
Состояние загрузки в ActionIcon
ActionIcon также поддерживает состояние загрузки:
import { ActionIcon, Group } from '@mantine/core';
import { IconRefresh, IconCheck } from '@tabler/icons-react';
import { useState } from 'react';
function ActionIconLoading() {
const [loading, setLoading] = useState(false);
const handleRefresh = async () => {
setLoading(true);
await new Promise((resolve) => setTimeout(resolve, 1500));
setLoading(false);
};
return (
<Group>
<ActionIcon
loading={loading}
onClick={handleRefresh}
color="blue"
aria-label="Обновить"
>
<IconRefresh size={18} />
</ActionIcon>
<ActionIcon
loading
loaderProps={{ type: 'bars' }}
aria-label="Загрузка"
>
<IconCheck size={18} />
</ActionIcon>
</Group>
);
}
LoadingOverlay – оверлей с загрузкой
LoadingOverlay используется, чтобы заблокировать часть интерфейса во время загрузки данных. Очень полезен для форм и списков.
import { useDisclosure } from '@mantine/hooks';
import { LoadingOverlay, Button, Box, Text } from '@mantine/core';
function LoadingOverlayExample() {
const [visible, { toggle }] = useDisclosure(false);
return (
<Box pos="relative" p="xl" maw={400} mx="auto" style={{ border: '1px solid #ccc', borderRadius: 8 }}>
<LoadingOverlay
visible={visible}
zIndex={1000}
overlayProps={{ radius: 'sm', blur: 2 }}
loaderProps={{ color: 'blue', type: 'bars' }}
/>
<Text ta="center" size="lg" fw={500}>Контент страницы</Text>
<Text ta="center" c="dimmed" mt="sm">
Этот контент блокируется оверлеем во время загрузки
</Text>
<Button onClick={toggle} fullWidth mt="md">
{visible ? 'Скрыть' : 'Показать'} загрузку
</Button>
</Box>
);
}
Важные моменты
- Родительский элемент должен иметь
pos="relative" loaderPropsпередаются вLoader- Элементы под оверлеем остаются фокусируемыми с клавиатуры — это следует учитывать в UX
Полный пример: форма с обратной связью
Давайте объединим все изученное в этом уроке и создадим полноценную форму с индикацией загрузки:
import { useForm } from '@mantine/form';
import {
Button,
ActionIcon,
Loader,
LoadingOverlay,
TextInput,
Box,
Group,
Paper,
Text,
Stack,
Alert,
} from '@mantine/core';
import { IconSend, IconTrash, IconRefresh, IconCheck, IconAlertCircle } from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { useState } from 'react';
interface FormValues {
name: string;
email: string;
}
function CompleteFeedbackForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const form = useForm<FormValues>({
mode: 'uncontrolled',
initialValues: {
name: '',
email: '',
},
validate: {
name: (value) => (value.length < 2 ? 'Имя должно быть минимум 2 символа' : null),
email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Некорректный email'),
},
});
const handleSubmit = async (values: FormValues) => {
setIsSubmitting(true);
setIsSuccess(false);
try {
// Имитация отправки на сервер
await new Promise((resolve) => setTimeout(resolve, 2500));
setIsSuccess(true);
notifications.show({
title: 'Успешно!',
message: `Данные для ${values.name} отправлены`,
color: 'green',
icon: <IconCheck size={18} />,
});
form.reset();
} catch (error) {
notifications.show({
title: 'Ошибка',
message: 'Не удалось отправить данные',
color: 'red',
icon: <IconAlertCircle size={18} />,
});
} finally {
setIsSubmitting(false);
}
};
const handleReset = () => {
form.reset();
setIsSuccess(false);
notifications.show({
title: 'Сброшено',
message: 'Форма очищена',
color: 'blue',
});
};
return (
<Paper pos="relative" p="xl" withBorder maw={500} mx="auto">
<LoadingOverlay
visible={isSubmitting}
loaderProps={{ type: 'bars', color: 'blue' }}
overlayProps={{ blur: 2 }}
/>
<Box component="form" onSubmit={form.onSubmit(handleSubmit)}>
<Stack gap="md">
{isSuccess && (
<Alert
icon={<IconCheck size={18} />}
title="Готово!"
color="green"
withCloseButton
onClose={() => setIsSuccess(false)}
>
Форма успешно отправлена
</Alert>
)}
<TextInput
label="Имя"
placeholder="Введите имя"
withAsterisk
disabled={isSubmitting}
key={form.key('name')}
{...form.getInputProps('name')}
/>
<TextInput
label="Email"
placeholder="your@email.com"
withAsterisk
disabled={isSubmitting}
key={form.key('email')}
{...form.getInputProps('email')}
/>
<Group justify="space-between" mt="md">
<Group>
<Button
type="submit"
leftSection={isSubmitting ? <Loader size={16} /> : <IconSend size={18} />}
loading={isSubmitting}
>
{isSubmitting ? 'Отправка...' : 'Отправить'}
</Button>
<ActionIcon
onClick={handleReset}
variant="subtle"
color="gray"
disabled={isSubmitting}
aria-label="Сбросить форму"
>
<IconRefresh size={18} />
</ActionIcon>
</Group>
<ActionIcon
onClick={handleReset}
color="red"
variant="light"
disabled={isSubmitting}
aria-label="Очистить все"
>
<IconTrash size={18} />
</ActionIcon>
</Group>
</Stack>
</Box>
</Paper>
);
}
export default CompleteFeedbackForm;
Итоги
Сегодня вы освоили все основные компоненты для создания интерактивных элементов и обратной связи:
Button— основной элемент управления с 6 вариантами, настройкой цвета, размера и иконокActionIcon— компактная иконка-кнопка для второстепенных действийLoader— индикатор загрузки с 3 встроенными типамиLoadingOverlay— блокировка интерфейса во время загрузки- Состояние
loadingв кнопках и иконках для асинхронных операций
Что должно быть в вашем проекте после этого урока
- Создана форма с кнопкой отправки, имеющей состояние загрузки
- Реализована иконка-кнопка (
ActionIcon) для вспомогательного действия - Использован
Loaderотдельно от кнопок (в оверлее или на странице) - Настроены уведомления (
notifications) для обратной связи с пользователем - Все
ActionIconимеютaria-labelдля доступности - Реализован
LoadingOverlayдля блокировки интерфейса во время загрузки
В следующем уроке мы разберем компоновку и навигацию — научимся строить каркас приложения с AppShell, Navbar, Header и создавать отзывчивые макеты с Grid и Flex.