A fully featured React components library
Note that if you've already started using uncontrolled form mode introduced in 7.8.0, you need to include form.key()
as described in the documentation.
[@mantine/form]
Add defaultValue
to form.getInputProps
return type[@mantine/form]
Replace key
spread with form.getInputProps
with form.key()
function[@mantine/dropzone]
Fix keyboard activation not working (#6095)[@mantine/dates]
DatePicker: Fix date range being stuck in incorrect state when controlled state changes to an empty value (#6092)[@mantine/core]
Radio: Allow null
to be passed to Radio.Group value to clear the value (#6102)[@mantine/core]
NumberInput: Fix incorrect cursor position when backspace is pressed (#6072)[@mantine/core]
Fix incorrect empty string handling in style props (#6078)Full Changelog: https://github.com/mantinedev/mantine/compare/7.8.0...7.8.1
View changelog with demos on mantine.dev website
Start from version 1.14.4
postcss-preset-mantine
supports autoRem
option that can be used to automatically convert all px
values
to rem
units in .css
files.
module.exports = {
plugins: {
'postcss-preset-mantine': {
autoRem: true,
},
},
};
This option works similar to rem
function. The following code:
.demo {
font-size: 16px;
@media (min-width: 320px) {
font-size: 32px;
}
}
Will be transformed to:
.demo {
font-size: calc(1rem * var(--mantine-scale));
@media (min-width: 320px) {
font-size: calc(2rem * var(--mantine-scale));
}
}
Note that autoRem
converts only CSS properties, values in @media
queries are
not converted automatically – you still need to use em
function to convert them.
autoRem
option does not convert values in the following cases:
calc()
, var()
, clamp()
and url()
functionscontent
propertyrgb()
, rgba()
, hsl()
, hsla()
colorsIf you want to convert above values to rem units, use rem
function manually.
useForm hook now supports uncontrolled mode.
Uncontrolled mode provides a significant performance improvement by reducing
the number of re-renders and the amount of state updates almost to 0. Uncontrolled
mode is now the recommended way to use the useForm
hook for almost all use cases.
Example of uncontrolled form (form.values
are not updated):
import { useState } from 'react';
import { Button, Code, Text, TextInput } from '@mantine/core';
import { hasLength, isEmail, useForm } from '@mantine/form';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: { name: '', email: '' },
validate: {
name: hasLength({ min: 3 }, 'Must be at least 3 characters'),
email: isEmail('Invalid email'),
},
});
const [submittedValues, setSubmittedValues] = useState<typeof form.values | null>(null);
return (
<form onSubmit={form.onSubmit(setSubmittedValues)}>
<TextInput {...form.getInputProps('name')} label="Name" placeholder="Name" />
<TextInput {...form.getInputProps('email')} mt="md" label="Email" placeholder="Email" />
<Button type="submit" mt="md">
Submit
</Button>
<Text mt="md">Form values:</Text>
<Code block>{JSON.stringify(form.values, null, 2)}</Code>
<Text mt="md">Submitted values:</Text>
<Code block>{submittedValues ? JSON.stringify(submittedValues, null, 2) : '–'}</Code>
</form>
);
}
With uncontrolled mode, you can not access form.values
as a state variable,
instead, you can use form.getValues()
method to get current form values at any time:
import { useForm } from '@mantine/form';
const form = useForm({
mode: 'uncontrolled',
initialValues: { name: 'John Doe' },
});
form.getValues(); // { name: 'John Doe' }
form.setValues({ name: 'John Smith' });
form.getValues(); // { name: 'John Smith' }
form.getValues()
always returns the latest form values, it is safe to use it
after state updates:
import { useForm } from '@mantine/form';
const form = useForm({
mode: 'uncontrolled',
initialValues: { name: 'John Doe' },
});
const handleNameChange = () => {
form.setFieldValue('name', 'Test Name');
// ❌ Do not use form.values to get the current form values
// form.values has stale name value until next rerender in controlled mode
// and is always outdated in uncontrolled mode
console.log(form.values); // { name: 'John Doe' }
// ✅ Use form.getValues to get the current form values
// form.getValues always returns the latest form values
console.log(form.getValues()); // { name: 'Test Name' }
};
form.watch
is an effect function that allows subscribing to changes of a
specific form field. It accepts field path and a callback function that is
called with new value, previous value, touched and dirty field states:
import { TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: {
name: '',
email: '',
},
});
form.watch('name', ({ previousValue, value, touched, dirty }) => {
console.log({ previousValue, value, touched, dirty });
});
return (
<div>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<TextInput mt="md" label="Email" placeholder="Email" {...form.getInputProps('email')} />
</div>
);
}
You can now customize middlewares
options in Popover component and
in other components (Menu, Select, Combobox, etc.)
based on Popover.
To customize Floating UI middlewares options, pass them as
an object to the middlewares
prop. For example, to change shift
middleware padding to 20px
use the following configuration:
import { Popover } from '@mantine/core';
function Demo() {
return (
<Popover middlewares={{ shift: { padding: 20 } }} position="bottom">
{/* Popover content */}
</Popover>
);
}
New use-fetch hook:
import { Box, Button, Code, Group, LoadingOverlay, Text } from '@mantine/core';
import { useFetch } from '@mantine/hooks';
interface Item {
userId: number;
id: number;
title: string;
completed: boolean;
}
function Demo() {
const { data, loading, error, refetch, abort } = useFetch<Item[]>(
'https://jsonplaceholder.typicode.com/todos/'
);
return (
<div>
{error && <Text c="red">{error.message}</Text>}
<Group>
<Button onClick={refetch} color="blue">
Refetch
</Button>
<Button onClick={abort} color="red">
Abort
</Button>
</Group>
<Box pos="relative" mt="md">
<Code block>{data ? JSON.stringify(data.slice(0, 3), null, 2) : 'Fetching'}</Code>
<LoadingOverlay visible={loading} />
</Box>
</div>
);
}
New use-map hook:
import { IconPlus, IconTrash } from '@tabler/icons-react';
import { ActionIcon, Group, Table } from '@mantine/core';
import { useMap } from '@mantine/hooks';
function Demo() {
const map = useMap([
['/hooks/use-media-query', 4124],
['/hooks/use-clipboard', 8341],
['/hooks/use-fetch', 9001],
]);
const rows = Array.from(map.entries()).map(([key, value]) => (
<Table.Tr key={key}>
<Table.Td>{key}</Table.Td>
<Table.Td>{value}</Table.Td>
<Table.Td>
<Group>
<ActionIcon variant="default" onClick={() => map.set(key, value + 1)} fw={500}>
<IconPlus stroke={1.5} size={18} />
</ActionIcon>
<ActionIcon variant="default" onClick={() => map.delete(key)} c="red">
<IconTrash stroke={1.5} size={18} />
</ActionIcon>
</Group>
</Table.Td>
</Table.Tr>
));
return (
<Table layout="fixed">
<Table.Thead>
<Table.Tr>
<Table.Th>Page</Table.Th>
<Table.Th>Views last month</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
);
}
New use-set hook:
import { useState } from 'react';
import { Code, Stack, TextInput } from '@mantine/core';
import { useSet } from '@mantine/hooks';
function Demo() {
const [input, setInput] = useState('');
const scopes = useSet<string>(['@mantine', '@mantine-tests', '@mantinex']);
const isDuplicate = scopes.has(input.trim().toLowerCase());
const items = Array.from(scopes).map((scope) => <Code key={scope}>{scope}</Code>);
return (
<>
<TextInput
label="Add new scope"
placeholder="Enter scope"
description="Duplicate scopes are not allowed"
value={input}
onChange={(event) => setInput(event.currentTarget.value)}
error={isDuplicate && 'Scope already exists'}
onKeyDown={(event) => {
if (event.nativeEvent.code === 'Enter' && !isDuplicate) {
scopes.add(input.trim().toLowerCase());
setInput('');
}
}}
/>
<Stack gap={5} align="flex-start" mt="md">
{items}
</Stack>
</>
);
}
New use-debounced-callback hook:
import { useState } from 'react';
import { Loader, Text, TextInput } from '@mantine/core';
import { useDebouncedCallback } from '@mantine/hooks';
function getSearchResults(query: string): Promise<{ id: number; title: string }[]> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(
query.trim() === ''
? []
: Array(5)
.fill(0)
.map((_, index) => ({ id: index, title: `${query} ${index + 1}` }))
);
}, 1000);
});
}
function Demo() {
const [search, setSearch] = useState('');
const [searchResults, setSearchResults] = useState<{ id: number; title: string }[]>([]);
const [loading, setLoading] = useState(false);
const handleSearch = useDebouncedCallback(async (query: string) => {
setLoading(true);
setSearchResults(await getSearchResults(query));
setLoading(false);
}, 500);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearch(event.currentTarget.value);
handleSearch(event.currentTarget.value);
};
return (
<>
<TextInput
value={search}
onChange={handleChange}
placeholder="Search..."
rightSection={loading && <Loader size={20} />}
/>
{searchResults.map((result) => (
<Text key={result.id} size="sm">
{result.title}
</Text>
))}
</>
);
}
New use-throttled-state hook:
import { Text, TextInput } from '@mantine/core';
import { useThrottledState } from '@mantine/hooks';
function Demo() {
const [throttledValue, setThrottledValue] = useThrottledState('', 1000);
return (
<>
<TextInput
placeholder="Search"
onChange={(event) => setThrottledValue(event.currentTarget.value)}
/>
<Text>Throttled value: {throttledValue || '–'}</Text>
</>
);
}
New use-throttled-value hook:
import { Text, TextInput } from '@mantine/core';
import { useThrottledValue } from '@mantine/hooks';
function Demo() {
const [value, setValue] = useState('');
const throttledValue = useThrottledValue(value, 1000);
return (
<>
<TextInput placeholder="Search" onChange={(event) => setValue(event.currentTarget.value)} />
<Text>Throttled value: {throttledValue || '–'}</Text>
</>
);
}
New use-throttled-callback hook:
import { Text, TextInput } from '@mantine/core';
import { useThrottledCallback } from '@mantine/hooks';
function Demo() {
const [throttledValue, setValue] = useState('');
const throttledSetValue = useThrottledCallback((value) => setValue(value), 1000);
return (
<>
<TextInput
placeholder="Search"
onChange={(event) => throttledSetValue(event.currentTarget.value)}
/>
<Text>Throttled value: {throttledValue || '–'}</Text>
</>
);
}
New use-orientation hook:
import { Code, Text } from '@mantine/core';
import { useOrientation } from '@mantine/hooks';
function Demo() {
const { angle, type } = useOrientation();
return (
<>
<Text>
Angle: <Code>{angle}</Code>
</Text>
<Text>
Type: <Code>{type}</Code>
</Text>
</>
);
}
New use-is-first-render hook:
import { useState } from 'react';
import { Button, Text } from '@mantine/core';
import { useIsFirstRender } from '@mantine/hooks';
function Demo() {
const [counter, setCounter] = useState(0);
const firstRender = useIsFirstRender();
return (
<div>
<Text>
Is first render:{' '}
<Text span c={firstRender ? 'teal' : 'red'}>
{firstRender ? 'Yes' : 'No!'}
</Text>
</Text>
<Button onClick={() => setCounter((c) => c + 1)} mt="sm">
Rerendered {counter} times, click to rerender
</Button>
</div>
);
}
withKeyboardEvents={false}
to disable up/down arrow keys handling[@mantine/core]
CloseButton: Add missing disabled styles (#6044)[@mantine/core]
AppShell: Fix incorrect app safe area handling by AppShell.Footer (#6060)[@mantine/core]
NumberInput: Fix cursor position changing when the value is incremented/decremented (#6004)[@mantine/core]
TagsInput: Fix incorrect IME keyboard input handling for Backspace
key (#6011)[@mantine/charts]
Fix incorrect overflow styles of svg element (#6040)[@mantine/core]
PinInput: Add rootRef
prop support (#6032)[@mantine/core]
ScrollArea: Fix viewportProps.onScroll
not working (#6055)[@mantine/core]
ScrollArea: Fix incorrect inset position of the horizontal scrollbar (#6059)[@mantine/hooks]
use-local-storage: Fix infinite rerendering with object values (#6022)Full Changelog: https://github.com/mantinedev/mantine/compare/7.7.1...7.7.2
[@mantine/tiptap]
Improve toolbar items alignment for non-native elements (#5993)[@mantine/spotlight]
Fix incorrect down key handling when the spotlight is opened repeatedly (#5995)[@mantine/core]
Image: Fix ref not being assigned for fallback images (#5989)[@mantine/core]
PinInput: Fix incorrect focus logic (#5963)[@mantine/core]
Table: Fix highlightOnHoverColor
prop not working[@mantine/core]
AppShell: Adjust footer position to include env(safe-area-inset-bottom) (#5502)[@mantine/core]
PinInput: Fix placeholder not being visible on the element that had focus when the component becomes disabled (#5831)[@mantine/dates]
Calendar: Fix double timezone shift (#5916)[@mantine/hooks]
use-local-storage: Fix value not being updated when key is changed (#5910)[@mantine/charts]
Fix incorrect charts legends height for multiline values (#5923)[@mantine/core]
NumberInput: Fix incorrect increment/decrement functions logic when step
is a float value (#5926)[@mantine/core]
Combobox: Fix incorrect IME input handling (#5935)[@mantine/core]
Menu: Fix unexpected focus styles in the dropdown element in Firefox (#5957)[@mantine/core]
Fix incorrect disabled
prop handling in TagsInput and MultiSelect (#5959)[@mantine/core]
Fix renderOption
not working for grouped items in Combobox-based components (#5952)[@mantine/core]
AppShell: Fix error when used inside Suspense (#5979)[@mantine/core]
Update CSS selectors hashing algorithm to prevent collisions with other libraries (#5968)[@mantine/carousel]
Fix specificity issues of some selectors (#5973)[@mantine/core]
AppShell: Fix missing Aside offset in Header and Footer for layout=alt
(#5974)Full Changelog: https://github.com/mantinedev/mantine/compare/7.7.0...7.7.1
View changelog with demos on mantine.dev website
Virtual color is a special color which values should be different for light and dark color schemes.
To define a virtual color, use virtualColor
function which accepts an object with the following
properties as a single argument:
name
– color name, must be the same as the key in theme.colors
objectlight
– a key of theme.colors
object for light color schemedark
– a key of theme.colors
object for dark color schemeTo see the demo in action, switch between light and dark color schemes (Ctrl + J
):
import { createTheme, MantineProvider, virtualColor } from '@mantine/core';
const theme = createTheme({
colors: {
primary: virtualColor({
name: 'primary',
dark: 'pink',
light: 'cyan',
}),
},
});
function App() {
return <MantineProvider theme={theme}>{/* Your app here */}</MantineProvider>;
}
New FloatingIndicator component:
import { useState } from 'react';
import {
IconArrowDown,
IconArrowDownLeft,
IconArrowDownRight,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpLeft,
IconArrowUpRight,
IconCircle,
} from '@tabler/icons-react';
import { FloatingIndicator, UnstyledButton } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
const [active, setActive] = useState('center');
const setControlRef = (name: string) => (node: HTMLButtonElement) => {
controlsRefs[name] = node;
setControlsRefs(controlsRefs);
};
return (
<div className={classes.root} dir="ltr" ref={setRootRef}>
<FloatingIndicator
target={controlsRefs[active]}
parent={rootRef}
className={classes.indicator}
/>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-left')}
ref={setControlRef('up-left')}
mod={{ active: active === 'up-left' }}
>
<IconArrowUpLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up')}
ref={setControlRef('up')}
mod={{ active: active === 'up' }}
>
<IconArrowUp size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-right')}
ref={setControlRef('up-right')}
mod={{ active: active === 'up-right' }}
>
<IconArrowUpRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('left')}
ref={setControlRef('left')}
mod={{ active: active === 'left' }}
>
<IconArrowLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('center')}
ref={setControlRef('center')}
mod={{ active: active === 'center' }}
>
<IconCircle size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('right')}
ref={setControlRef('right')}
mod={{ active: active === 'right' }}
>
<IconArrowRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-left')}
ref={setControlRef('down-left')}
mod={{ active: active === 'down-left' }}
>
<IconArrowDownLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down')}
ref={setControlRef('down')}
mod={{ active: active === 'down' }}
>
<IconArrowDown size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-right')}
ref={setControlRef('down-right')}
mod={{ active: active === 'down-right' }}
>
<IconArrowDownRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
</div>
);
}
New ScatterChart component:
import { useState } from 'react';
import {
IconArrowDown,
IconArrowDownLeft,
IconArrowDownRight,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpLeft,
IconArrowUpRight,
IconCircle,
} from '@tabler/icons-react';
import { FloatingIndicator, UnstyledButton } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
const [active, setActive] = useState('center');
const setControlRef = (name: string) => (node: HTMLButtonElement) => {
controlsRefs[name] = node;
setControlsRefs(controlsRefs);
};
return (
<div className={classes.root} dir="ltr" ref={setRootRef}>
<FloatingIndicator
target={controlsRefs[active]}
parent={rootRef}
className={classes.indicator}
/>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-left')}
ref={setControlRef('up-left')}
mod={{ active: active === 'up-left' }}
>
<IconArrowUpLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up')}
ref={setControlRef('up')}
mod={{ active: active === 'up' }}
>
<IconArrowUp size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-right')}
ref={setControlRef('up-right')}
mod={{ active: active === 'up-right' }}
>
<IconArrowUpRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('left')}
ref={setControlRef('left')}
mod={{ active: active === 'left' }}
>
<IconArrowLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('center')}
ref={setControlRef('center')}
mod={{ active: active === 'center' }}
>
<IconCircle size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('right')}
ref={setControlRef('right')}
mod={{ active: active === 'right' }}
>
<IconArrowRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-left')}
ref={setControlRef('down-left')}
mod={{ active: active === 'down-left' }}
>
<IconArrowDownLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down')}
ref={setControlRef('down')}
mod={{ active: active === 'down' }}
>
<IconArrowDown size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-right')}
ref={setControlRef('down-right')}
mod={{ active: active === 'down-right' }}
>
<IconArrowDownRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
</div>
);
}
New colorsTuple
function can be used to:
import { colorsTuple, createTheme } from '@mantine/core';
const theme = createTheme({
colors: {
custom: colorsTuple('#FFC0CB'),
dynamic: colorsTuple(Array.from({ length: 10 }, (_, index) => '#FFC0CB')),
},
});
New useMutationObserver hook:
import { useState } from 'react';
import { Kbd, Text } from '@mantine/core';
import { useMutationObserver } from '@mantine/hooks';
function Demo() {
const [lastMutation, setLastMutation] = useState('');
useMutationObserver(
(mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'dir') {
mutation.target instanceof HTMLElement &&
setLastMutation(mutation.target.getAttribute('dir') || '');
}
});
},
{
attributes: true,
attributeFilter: ['dir'],
},
() => document.documentElement
);
return (
<>
<Text>
Press <Kbd>Ctrl</Kbd> + <Kbd>Shift</Kbd> + <Kbd>L</Kbd> to change direction
</Text>
<Text mt={10}>Direction was changed to: {lastMutation || 'Not changed yet'}</Text>
</>
);
}
New useStateHistory hook:
import { Button, Code, Group, Text } from '@mantine/core';
import { useStateHistory } from '@mantine/hooks';
function Demo() {
const [value, handlers, history] = useStateHistory(1);
return (
<>
<Text>Current value: {value}</Text>
<Group my="md">
<Button onClick={() => handlers.set(Math.ceil(Math.random() * 100) + 1)}>Set value</Button>
<Button onClick={() => handlers.back()}>Back</Button>
<Button onClick={() => handlers.forward()}>Forward</Button>
</Group>
<Code block>{JSON.stringify(history, null, 2)}</Code>
</>
);
}
AreaChart, BarChart and LineChart
components now support xAxisLabel
and yAxisLabel
props:
import { AreaChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<AreaChart
h={300}
data={data}
dataKey="date"
type="stacked"
xAxisLabel="Date"
yAxisLabel="Amount"
series={[
{ name: 'Apples', color: 'indigo.6' },
{ name: 'Oranges', color: 'blue.6' },
{ name: 'Tomatoes', color: 'teal.6' },
]}
/>
);
}
mantine-hidden-from-{x}
and mantine-visible-from-{x}
classes.window.HTMLElement.prototype.scrollIntoView
New articles added to the help center:
transform: scale()
.connectNulls
and areaProps
propsscrollAreaProps
prop to pass props down to the ScrollArea component in the dropdownfade-up
, fade-down
, fade-left
, fade-right
fade-down
. This change resolves issues with SegmentedControl indicator positioning when used inside modals.fz
and lh
style props with h1
, h2
, h3
, h4
, h5
, h6
values[@mantine/hooks]
use-resize-observer: Fix types (#5847)[@mantine/hooks]
use-local-storage: Fix undefined
being written to the local storage when defaultValue
is not defined (#5848)[@mantine/core]
NumberInput: Fix onValueChange
not being called in increment/decrement functions (#5856)[@mantine/core]
InputWrapper: Fix className
specified in labelProps
, descriptionProps
and errorProps
not being passed to the corresponding element (#5862)[@mantine/core]
Fix some functions not working correctly with TypeScript 5.4 (#5891)[@mantine/form]
Fix onValuesChange
not using updated function (#5901)[@mantine/core]
Popover: Fix incorrect dropdown selectors (#5903)[@mantine/core]
Indicator: Fix processing animation in Safari (#5908)[@mantine/hooks]
use-headroom: Fix incorrect pinning logic when scrolling up (#5793)[@mantine/dropzone]
Add heic images format to default mime types (#5867)[@mantine/core]
Transition: Fix transitions resolving instantly in some cases (#5873)[@mantine/dropzone]
Add inputProps
prop support to pass props down to the underlying hidden input element (#5880)[@mantine/core]
Timeline: Fix autoContrast
being passed to the dom node as attribute (#5890)Full Changelog: https://github.com/mantinedev/mantine/compare/7.6.1...7.6.2
[@mantine/core]
Fix incorrect focus ring styles in Button.Group and ActionIcon.Group components (#5736)[@mantine/core]
Progress: Fix incorrect border-radius with multiple sections[@mantine/dates]
DateTimePicker: Fix minDate
and maxDate
not being respected in time input (#5819)[@mantine/core]
Switch: Use role="switch"
for better accessibility (#5746)[@mantine/hooks]
use-resize-observer: Fix incorrect ref type (#5780)[@mantine/dates]
Fix popoverProps.onClose
overriding original component value in DatePickerInput and other similar components (#4105)[@mantine/core]
Fix incorrect Escape key handling in Modal and Drawer components in some cases (#2827)[@mantine/core]
Combobox: Fix incorrect Escape key handling in Modal, Drawer and Popover[@mantine/core]
Transition: Fix transition resolving instantly in some cases (#3126, #5193)[@mantine/core]
Remove loader from the DOM if loading
prop is not set on ActionIcon and Button components (#5795)[@mantine/hooks]
use-local-storage: Fix inconsistent default value persistence if getInitialValueInEffect
is set (#5796)[@mantine/core]
Select: Fix autoComplete
prop not working (#5813)[@mantine/core]
Tabs: Fix incorrect border styles in outline variant[@mantine/core]
Checkbox: Fix incorrect indeterminate
+ disabled
styles for outline variant (#5806)[@mantine/core]
SegmentedControl: Fix indicator state not being updated correctly when controlled state changes to a value that is not present in the data array (#5689)[@mantine/core]
Fix incorrect label offset with left label position in Checkbox, Switch and Radio components (#5823)[@mantine/core]
PinInput: Fix updating controlled value to an empty string working incorrectly[@mantine/core]
Menu: Fix incorrect role of dropdown elementsFull Changelog: https://github.com/mantinedev/mantine/compare/7.6.0...7.6.1
View changelog with demos on mantine.dev website
You can now use container queries
with Mantine components. rem
and em
functions from postcss-preset-mantine
are available in container queries staring from [email protected]
.
.root {
min-width: rem(200px);
max-width: 100%;
min-height: rem(120px);
container-type: inline-size;
overflow: auto;
resize: horizontal;
}
.child {
background-color: var(--mantine-color-dimmed);
color: var(--mantine-color-white);
padding: var(--mantine-spacing-md);
@container (max-width: rem(500px)) {
background-color: var(--mantine-color-blue-filled);
}
@container (max-width: rem(300px)) {
background-color: var(--mantine-color-red-filled);
}
}
New RadarChart component:
import { RadarChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<RadarChart
h={300}
data={multiData}
dataKey="product"
withPolarRadiusAxis
series={[
{ name: 'sales_january', color: 'lime.4', opacity: 0.1 },
{ name: 'sales_february', color: 'cyan.4', opacity: 0.1 },
]}
/>
);
}
FocusTrap.InitialFocus is a new component that adds a visually hidden
element which will receive the focus when the focus trap is activated.
Once FocusTrap.InitialFocus
loses focus, it is removed from the tab order.
For example, it is useful if you do not want to focus any elements inside the Modal when it is opened:
import { Button, FocusTrap, Modal, TextInput } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
function Demo() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} title="Focus demo">
<FocusTrap.InitialFocus />
<TextInput label="First input" placeholder="First input" />
<TextInput
data-autofocus
label="Input with initial focus"
placeholder="It has data-autofocus attribute"
mt="md"
/>
</Modal>
<Button onClick={open}>Open modal</Button>
</>
);
}
MantineProvider now includes more props to control how styles are generated and injected. These props are useful if you use Mantine as a headless library and in test environments.
deduplicateCssVariables
prop determines whether CSS variables should be deduplicated: if CSS variable has the same value as in default theme, it is not added in the runtime.
By default, it is set to true
. If set to false
, all Mantine CSS variables will be added in <style />
tag
even if they have the same value as in the default theme.
import { MantineProvider } from '@mantine/core';
function Demo() {
return (
<MantineProvider deduplicateCssVariables={false}>
<App />
</MantineProvider>
);
}
withStaticClasses
determines whether components should have static classes, for example, mantine-Button-root
.
By default, static classes are enabled, to disable them set withStaticClasses
to false
:
import { MantineProvider } from '@mantine/core';
function Demo() {
return (
<MantineProvider withStaticClasses={false}>
<App />
</MantineProvider>
);
}
withGlobalClasses
determines whether global classes should be added with <style />
tag.
Global classes are required for hiddenFrom
/visibleFrom
and lightHidden
/darkHidden
props to work.
By default, global classes are enabled, to disable them set withGlobalClasses
to false
. Note that
disabling global classes may break styles of some components.
import { MantineProvider } from '@mantine/core';
function Demo() {
return (
<MantineProvider withGlobalClasses={false}>
<App />
</MantineProvider>
);
}
HeadlessMantineProvider
is an alternative to MantineProvider
that should be used when you want to use Mantine as a headless UI library. It removes all
features that are related to Mantine styles:
style
attributeLimitations of HeadlessMantineProvider
:
color
, radius
, size
, etc.) will have no effect.lightHidden
/darkHidden
, visibleFrom
/hiddenFrom
props will not work.mt="xs"
will not work, but mt={5}
will.To use HeadlessMantineProvider
, follow getting started guide and replace MantineProvider
with HeadlessMantineProvider
.
Note that you do not need to use ColorSchemeScript in your application, it will not have any effect,
you can ignore this part of the guide.
import { HeadlessMantineProvider } from '@mantine/core';
function App() {
return <HeadlessMantineProvider>{/* Your application */}</HeadlessMantineProvider>;
}
Sparkline now supports trendColors
prop to change chart color depending on the trend.
The prop accepts an object with positive
, negative
and neutral
properties:
positive
- color for positive trend (first value is less than the last value in data
array)negative
- color for negative trend (first value is greater than the last value in data
array)neutral
- color for neutral trend (first and last values are equal)neutral
is optional, if not provided, the color will be the same as positive
.
import { Sparkline } from '@mantine/charts';
import { Stack, Text } from '@mantine/core';
const positiveTrend = [10, 20, 40, 20, 40, 10, 50];
const negativeTrend = [50, 40, 20, 40, 20, 40, 10];
const neutralTrend = [10, 20, 40, 20, 40, 10, 50, 5, 10];
function Demo() {
return (
<Stack gap="sm">
<Text>Positive trend:</Text>
<Sparkline
w={200}
h={60}
data={positiveTrend}
trendColors={{ positive: 'teal.6', negative: 'red.6', neutral: 'gray.5' }}
fillOpacity={0.2}
/>
<Text mt="md">Negative trend:</Text>
<Sparkline
w={200}
h={60}
data={negativeTrend}
trendColors={{ positive: 'teal.6', negative: 'red.6', neutral: 'gray.5' }}
fillOpacity={0.2}
/>
<Text mt="md">Neutral trend:</Text>
<Sparkline
w={200}
h={60}
data={neutralTrend}
trendColors={{ positive: 'teal.6', negative: 'red.6', neutral: 'gray.5' }}
fillOpacity={0.2}
/>
</Stack>
);
}
RichTextEditor now supports tasks tiptap extension:
import TaskItem from '@tiptap/extension-task-item';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { getTaskListExtension, RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [
StarterKit,
getTaskListExtension(TipTapTaskList),
TaskItem.configure({
nested: true,
HTMLAttributes: {
class: 'test-item',
},
}),
],
content: `
<ul data-type="taskList">
<li data-type="taskItem" data-checked="true">A list item</li>
<li data-type="taskItem" data-checked="false">And another one</li>
</ul>
`,
});
return (
<div style={{ padding: 40 }}>
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.ControlsGroup>
<RichTextEditor.TaskList />
<RichTextEditor.TaskListLift />
<RichTextEditor.TaskListSink />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
</div>
);
}
Select, MultiSelect, TagsInput and Autocomplete
components now support renderOption
prop that allows to customize option rendering:
import {
IconAlignCenter,
IconAlignJustified,
IconAlignLeft,
IconAlignRight,
IconCheck,
} from '@tabler/icons-react';
import { Group, Select, SelectProps } from '@mantine/core';
const iconProps = {
stroke: 1.5,
color: 'currentColor',
opacity: 0.6,
size: 18,
};
const icons: Record<string, React.ReactNode> = {
left: <IconAlignLeft {...iconProps} />,
center: <IconAlignCenter {...iconProps} />,
right: <IconAlignRight {...iconProps} />,
justify: <IconAlignJustified {...iconProps} />,
};
const renderSelectOption: SelectProps['renderOption'] = ({ option, checked }) => (
<Group flex="1" gap="xs">
{icons[option.value]}
{option.label}
{checked && <IconCheck style={{ marginInlineStart: 'auto' }} {...iconProps} />}
</Group>
);
function Demo() {
return (
<Select
label="Select with renderOption"
placeholder="Select text align"
data={[
{ value: 'left', label: 'Left' },
{ value: 'center', label: 'Center' },
{ value: 'right', label: 'Right' },
{ value: 'justify', label: 'Justify' },
]}
renderOption={renderSelectOption}
/>
);
}
All Mantine components have been migrated to logical CSS properties
(as a replacement for rtl styles) and :where pseudo-class
(as a replacement for private CSS variables). These changes
should not impact the usage of Mantine components, but now Mantine CSS files have smaller size. For example,
@mantine/core/styles.css
now has ~ 8% smaller size (192kb -> 177kb).
You can now pass props down to recharts Bar, Area and Line components
with barProps
, areaProps
and lineProps
props on BarChart, AreaChart and LineChart components.
All props accepts either an object with props or a function that receives series data as an argument and returns an object with props.
PieChart now supports percent
labels:
import { BarChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<BarChart
h={200}
data={data}
dataKey="month"
orientation="vertical"
yAxisProps={{ width: 80 }}
barProps={{ radius: 10 }}
series={[{ name: 'Smartphones', color: 'blue.6' }]}
/>
);
}
New articles added to the help center:
swap
handlerform.setFieldValue
now supports callback function as an argumentpx
, py
, mx
and my
style props now use logical CSS properties padding-inline
, padding-block
, margin-inline
and margin-block
instead of padding-left
, padding-right
, etc.me
, ms
, ps
, pe
style props to set margin-inline-end
, margin-inline-start
, padding-inline-start
and padding-inline-end
CSS propertiesPopover
now support floatingStrategy
prop to control Floating UI strategy
@mantine/charts
components now support children
prop which passes children to the root recharts componentonClear
prop, the function is called when clear button is clickedonRemove
prop, the function is called with removed item value when one of the items is deselected[@mantine/core]
NumberInput: Fix double border between controls appeared on low resolution screens (#5753)[@mantine/hooks]
use-hotkeys: Fix incorrect HotkeyItem
type (#5705)[@mantine/hooks]
use-resize-observer: Fix incorrect ref type (#5759)[@mantine/core]
ScrollArea: Fix offsetScrollbars
not working on y-axis (#5762)[@mantine/core]
NavLink: Add collapse
Styles API selector (#5776)[@mantine/hooks]
Fixed initial value of the online
attribute returned by useNetwork()
in Firefox (#5766)[@mantine/core]
PinInput: Fix inputs not being updated with length
prop changes[@mantine/core]
PinInput: Fix incorrect onComplete
behavior (#5774, #5771)[@mantine/core]
Card: Fix incorrect margins in first and last sections when CardSection
component is used instead of Card.Section
(#5742)[@mantine/core]
Tooltip: Fix multiline prop not working correctly in Tooltip.Floating
componentFull Changelog: https://github.com/mantinedev/mantine/compare/7.5.2...7.5.3
[@mantine/core]
ActionIcon: Fix icon width and height defined in % not working correctly[@mantine/core]
ScrollArea: Fix offsetScrollbars
not working (#5733)[@mantine/tiptap]
Fix initialExternal
on RichTextEditor.Link
control not working correctly[@mantine/core]
FileInput: Fix incorrect extend
function type[@mantine/core]
PinInput: Fix various issues related to user input and pasting into the input (#5704)[@mantine/form]
Add callback argument support to form.setFieldValue
handler (#5696)[@mantine/core]
Add explicit extension to exports to support NodeNext TypeScript resolution (#5697)[@mantine/hooks]
use-list-state: Add swap
handler support (#5716)[@mantine/core]
Fix NodeNext TypeScript resolution not working correctly for PolymorphicComponentProps and PolymorphicRef types (#5730)[@mantine/core]
Fix cjs builds unable to resolve third-party dependencies with certain TypeScript settings (#5741)[@mantine/core]
Transition: Fix skew-up transition not working (#5714)[@mantine/core]
Select: Fix active option not being scrolled into view when the dropdown opens[@mantine/core]
Menu: Fix unexpected focus trap when keepMounted is false (#4502)[@mantine/core]
ScrollArea: Fix style
prop not being passed to the element when used in viewportProps
(#5594)[@mantine/core]
Divider: Fix poor color contrast with light color scheme[@mantine/core]
Modal: Fix incorrect content border-radius when fullScreen
prop is set[@mantine/core]
Modal: Fix scroll container not working correctly when ScrollArea is used as a scroll container for a full screen modal[@mantine/notifications]
Fix notifications handlers not allowing passing data-* attributes (#5640)Full Changelog: https://github.com/mantinedev/mantine/compare/7.5.1...7.5.2