View changelog with demos on mantine.dev website
Virtual colors
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 intheme.colors
objectlight
– a key oftheme.colors
object for light color schemedark
– a key oftheme.colors
object for dark color scheme
To 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>;
}
FloatingIndicator component
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>
);
}
ScatterChart component
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>
);
}
colorsTuple function
New colorsTuple
function can be used to:
- Use single color as the same color for all shades
- Transform dynamic string arrays to Mantine color tuple (the array should still have 10 values)
import { colorsTuple, createTheme } from '@mantine/core';
const theme = createTheme({
colors: {
custom: colorsTuple('#FFC0CB'),
dynamic: colorsTuple(Array.from({ length: 10 }, (_, index) => '#FFC0CB')),
},
});
use-mutation-observer hook
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>
</>
);
}
use-state-history hook
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>
</>
);
}
Axis labels
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' },
]}
/>
);
}
Documentation updates
- New section has been added to the responsive guide on how to use
mantine-hidden-from-{x}
andmantine-visible-from-{x}
classes. - Remix guide has been updated to use new Vite bundler
- Jest and Vitest guides configuration has been updated to include mocks for
window.HTMLElement.prototype.scrollIntoView
- CSS variables documentation has been updated to include more information about typography and colors variables
Help center updates
New articles added to the help center:
- Can I use SegmentedControl with empty value?
- Is there a comparison with other libraries?
- Can I use Mantine with Vue/Svelte/Angular/etc.?
- How can I test Select/MultiSelect components?
Other changes
- SegmentedControl indicator positioning logic has been migrated to FloatingIndicator. It is now more performant and works better when used inside elements with
transform: scale()
. - New use-mounted hook
- Sparkline now supports
connectNulls
andareaProps
props - Remix template has been updated to use new Vite bundler
- Select, MultiSelect, Autocomplete and TagsInput components now support
scrollAreaProps
prop to pass props down to the ScrollArea component in the dropdown - Transition component now supports 4 new transitions:
fade-up
,fade-down
,fade-left
,fade-right
- Default Modal transition was changed to
fade-down
. This change resolves issues with SegmentedControl indicator positioning when used inside modals. - You can now reference headings font sizes and line heights in
fz
andlh
style props withh1
,h2
,h3
,h4
,h5
,h6
values