7.17.1
[@mantine/core]
Select: Fix caret displayed when the readonly input is clicked (#7476)[@mantine/charts]
Fix incorrect types ofclassNames
prop of PieChart and DonutChart components (#7475)[@mantine/charts]
BubbleChart: Fix broken layout whenlabel
prop is used with React 19[@mantine/form]
Fix missingisJSONString
export (#7508)[@mantine/core]
Fix MultiSelect and TagsInputs dropdowns still being opened on click when components were used inside disabled fieldset (#7528)[@mantine/code-highlight]
Fallback unsupported code to plaintext (#7497)[@mantine/emotion]
Improve "Go to definition" support forcreateStyles
classes (#7526)
- @kino90 made their first contribution in https://github.com/mantinedev/mantine/pull/7513
- @tranhl made their first contribution in https://github.com/mantinedev/mantine/pull/7526
- @imcvampire made their first contribution in https://github.com/mantinedev/mantine/pull/7497
- @ched-dev made their first contribution in https://github.com/mantinedev/mantine/pull/7480
- @ysalo made their first contribution in https://github.com/mantinedev/mantine/pull/7508
Full Changelog: https://github.com/mantinedev/mantine/compare/7.17.0...7.17.1
7.17.0 🌶️
View changelog with demos on mantine.dev website
This is the last minor release in the 7.x
series. The next release will be 8.0
with breaking changes and new features.
You are welcome to review the changelog/code and provide feedback and bug reports in Discord or GitHub discussions:
- Changelog – https://alpha.mantine.dev/changelog/8-0-0/
- Source code – https://github.com/mantinedev/mantine/tree/8.0
How to update your project dependencies to the new alpha version:
- Open your
package.json
- Find all
@mantine/
packages - Update version of all
@mantine/
packages to8.0.0-alpha.0
- Install dependencies with your package manager, for example,
yarn
ornpm install
Important notes:
- 8.0 release is scheduled for May 5th. Until then you can influence the included breaking changes by proving feedback using Discord or GitHub discussions.
- Currently most of planned breaking changes are implemented – it is not planned to introduce other significant breaking changes (unless new versions of recharts or tiptap are released before May 5th).
- You can find the updated source code in 8.0 branch on GitHub
- Since the changes to codebase are not that significant compared to previous major releases, it is not planned to deprecate 7.x version as soon as 8.0 is release. 7.17.x patches are planned to be released for some time – if you are not ready to migrate to 8.x, you will still be able to receive bug fixes for 7.x (no new features though).
Portal component now supports reuseTargetNode
prop which allows to reuse the same target node for all instances. This option is more performant than the previous behavior, it is recommended to be enabled. This option will be enabled by default in the 8.0
major release.
To enable reuseTargetNode option in all components that depend on Portal, add the following code to your theme:
import { createTheme, Portal } from '@mantine/core';
export const theme = createTheme({
components: {
Portal: Portal.extend({
defaultProps: {
reuseTargetNode: true,
},
}),
},
});
Example usage. In the following example, all three paragraphs will be rendered in the same target node:
import { Portal } from '@mantine/core';
function Demo() {
return (
<>
<Portal reuseTargetNode>
<p>First</p>
</Portal>
<Portal reuseTargetNode>
<p>Second</p>
</Portal>
<Portal reuseTargetNode>
<p>Third</p>
</Portal>
</>
);
}
formRootRule
is a special rule path that can be used to validate objects and arrays alongside with their nested fields. For example, it is useful when you want to capture a list of values, validate each value individually and then validate the list itself to not be empty:
import { IconTrash } from '@tabler/icons-react';
import { ActionIcon, Button, Group, Switch, Text, TextInput } from '@mantine/core';
import { formRootRule, isNotEmpty, useForm } from '@mantine/form';
import { randomId } from '@mantine/hooks';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: {
employees: [{ name: '', active: false, key: randomId() }],
},
validate: {
employees: {
[formRootRule]: isNotEmpty('At least one employee is required'),
name: isNotEmpty('Name is required'),
},
},
});
const fields = form.getValues().employees.map((item, index) => (
<Group key={item.key} mt="xs">
<TextInput
placeholder="John Doe"
withAsterisk
style={{ flex: 1 }}
key={form.key(`employees.${index}.name`)}
{...form.getInputProps(`employees.${index}.name`)}
/>
<Switch
label="Active"
key={form.key(`employees.${index}.active`)}
{...form.getInputProps(`employees.${index}.active`, { type: 'checkbox' })}
/>
<ActionIcon color="red" onClick={() => form.removeListItem('employees', index)}>
<IconTrash size={16} />
</ActionIcon>
</Group>
));
return (
<form onSubmit={form.onSubmit(() => {})}>
{fields.length > 0 ? (
<Group mb="xs">
<Text fw={500} size="sm" style={{ flex: 1 }}>
Name
</Text>
<Text fw={500} size="sm" pr={90}>
Status
</Text>
</Group>
) : (
<Text c="dimmed" ta="center">
No one here...
</Text>
)}
{fields}
{form.errors.employees && (
<Text c="red" size="sm" mt="sm">
{form.errors.employees}
</Text>
)}
<Group justify="space-between" mt="md">
<Button
variant="default"
onClick={() => {
form.insertListItem('employees', { name: '', active: false, key: randomId() });
form.clearFieldError('employees');
}}
>
Add employee
</Button>
<Button type="submit">Submit</Button>
</Group>
</form>
);
}
Another example is to validate an object fields combination:
import { Button, Text, TextInput } from '@mantine/core';
import { formRootRule, isNotEmpty, useForm } from '@mantine/form';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: {
user: {
firstName: '',
lastName: '',
},
},
validate: {
user: {
[formRootRule]: (value) =>
value.firstName.trim().length > 0 && value.firstName === value.lastName
? 'First name and last name cannot be the same'
: null,
firstName: isNotEmpty('First name is required'),
lastName: isNotEmpty('Last name is required'),
},
},
});
return (
<form onSubmit={form.onSubmit(() => {})}>
<TextInput
label="First name"
placeholder="First name"
{...form.getInputProps('user.firstName')}
/>
<TextInput
label="Last name"
placeholder="Last name"
mt="md"
{...form.getInputProps('user.lastName')}
/>
{form.errors.user && (
<Text c="red" mt={5} fz="sm">
{form.errors.user}
</Text>
)}
<Button type="submit" mt="lg">
Submit
</Button>
</form>
);
}
New isJSONString
and isNotEmptyHTML
form validators:
isNotEmptyHTML
checks that form value is not an empty HTML string. Empty string, string with only HTML tags and whitespace are considered to be empty.isJSONString
checks that form value is a valid JSON string.
import { isJSONString, useForm } from '@mantine/form';
const form = useForm({
mode: 'uncontrolled',
initialValues: {
json: '',
html: '',
},
validate: {
json: isJSONString('Invalid JSON string'),
html: isNotEmptyHTML('HTML cannot be empty'),
},
});
Popover now supports onDismiss
prop, which makes it easier to subscribe to outside clicks and escape key presses to close popover:
import { useState } from 'react';
import { Button, Popover } from '@mantine/core';
function Demo() {
const [opened, setOpened] = useState(false);
return (
<Popover opened={opened} onDismiss={() => setOpened(false)}>
<Popover.Target>
<Button onClick={() => setOpened((o) => !o)}>Toggle popover</Button>
</Popover.Target>
<Popover.Dropdown>Dropdown</Popover.Dropdown>
</Popover>
);
}
MantineProvider component now supports env
prop. It can be used in test environment to disable some features that might impact tests and/or make it harder to test components:
- transitions that mount/unmount child component with delay
- portals that render child component in a different part of the DOM
To enable test environment, set env
to test
:
import { MantineProvider } from '@mantine/core';
function Demo() {
return <MantineProvider env="test">{/* Your app here */}</MantineProvider>;
}
New use-file-dialog allows capturing one or more files from the user without file input element:
import { Button, Group, List } from '@mantine/core';
import { useFileDialog } from '@mantine/hooks';
function Demo() {
const fileDialog = useFileDialog();
const pickedFiles = Array.from(fileDialog.files || []).map((file) => (
<List.Item key={file.name}>{file.name}</List.Item>
));
return (
<div>
<Group>
<Button onClick={fileDialog.open}>Pick files</Button>
{pickedFiles.length > 0 && (
<Button variant="default" onClick={fileDialog.reset}>
Reset
</Button>
)}
</Group>
{pickedFiles.length > 0 && <List mt="lg">{pickedFiles}</List>}
</div>
);
}
Remix is deprecated, the documentation related to Remix integration was removed, use React Router instead. To simplify maintenance, Remix/React Router templates were archived and will not be updated.
- I get hydration warning about data-mantine-color-scheme attribute, what does it mean? question
- How can I apply styles to all Mantine components? question
- Tooltip now supports customizing
middlewares
- ScrollArea now supports
overscrollBehavior
prop - Affix now supports
theme.spacing
values forposition
prop - Anchor now supports
underline="not-hover"
option to display underline only when the link is not hovered
7.16.3
[@mantine/core]
Removeuse client
fromisLightColor
function[@mantine/core]
TextInput: Fix autocomplete forvariant
prop not working[@mantine/carousel]
Fix aria-hidden warning displayed in Chrome console when indicator is clicked (#7414)[@mantine/core]
Fix clear button overlaying input content in Autocomplete and other similar components (#7431)[@mantine/core]
Combobox: Fix incorrect dropdown padding when used with ScrollArea (#7450)[@mantine/core]
Fix0
gradientdeg
value not working correctly (#7444)[@mantine/core]
ScrollArea: Fix user-select being left as none after interaction with scrollbar in some edge cases (#7423)[@mantine/dates]
DateInput: Fix infinite loop in Safari (#7442)
- @kingflamez made their first contribution in https://github.com/mantinedev/mantine/pull/7442
- @webkonstantin made their first contribution in https://github.com/mantinedev/mantine/pull/7423
- @marcinlukanus made their first contribution in https://github.com/mantinedev/mantine/pull/7444
Full Changelog: https://github.com/mantinedev/mantine/compare/7.16.2...7.16.3
7.16.2
[@mantine/core]
Tooltip: Migrate from deprecateduseDelayGroupContext
hook touseDelayGroup
[@mantine/core]
Tabs: FixtabIndex={0}
set onTabs.Tab
being ignored (#7407)[@mantine/core]
Fix chevron icon not being clickable in Select and MultiSelect components (#7395)[@mantine/dates]
MonthPicker: Fix infinite useEffect with use-form in some cases (#7389)[@mantine/hooks]
use-hotkeys: Add better support for non-QUERTY keyboards (#7390)[@mantine/dates]
DateTimePicker: Fix timezone conversion being applied twice (#7400)[@mantine/hooks]
Fix potential dangerous access of ref value in useEffect cleanup (#7404)
- @ohansFavour made their first contribution in https://github.com/mantinedev/mantine/pull/7402
- @danpeavey-classdojo made their first contribution in https://github.com/mantinedev/mantine/pull/7390
- @openscript made their first contribution in https://github.com/mantinedev/mantine/pull/7389
Full Changelog: https://github.com/mantinedev/mantine/compare/7.16.1...7.16.2
7.16.1
[@mantine/core]
Menu: AddwithInitialFocusPlaceholder
prop support[@mantine/core]
Slider: FixonChangeEnd
being called whendisabled
prop is set[@mantine/core]
Add option to customize chevron color withchevronColor
prop in Select and MultiSelect components[@mantine/core]
Fix incorrect styles of nested tables (#7354)[@mantine/core]
: NumberInput: FixonChange
being called inonBlur
if the value has not been changed (#7383)[@mantine/core]
Menu: Adddata-disabled
prop handling to Menu.Item component (#7377)[@mantine/form]
Fix incorrect values handling inform.resetDirty
(#7373)[@mantine/chart]
AreaChart: FixgridColor
andtextColor
props being passed as attributes to the DOM node (#7378)[@mantine/hooks]
use-in-viewport: Fix tracking being stopped when used with a dnd library (#7359)[@mantine/core]
MantineProvider: Fix jest tests not running in case there is incorrectwindow.matchMedia
polyfill implementation (#7360)[@mantine/core]
Modal: Fix Escape key not working in old Safari versions (#7358)
- @kidonng made their first contribution in https://github.com/mantinedev/mantine/pull/7364
- @Benjaminsson made their first contribution in https://github.com/mantinedev/mantine/pull/7360
- @cameronrdecker made their first contribution in https://github.com/mantinedev/mantine/pull/7380
- @tomrehnstrom made their first contribution in https://github.com/mantinedev/mantine/pull/7373
Full Changelog: https://github.com/mantinedev/mantine/compare/7.16.0...7.16.1
7.16.0 🌶️
View changelog with demos on mantine.dev website
New use-scroll-spy hook tracks scroll position and returns index of the element that is currently in the viewport. It is useful for creating table of contents components (like in mantine.dev sidebar on the right side) and similar features.
import { Text, UnstyledButton } from '@mantine/core';
import { useScrollSpy } from '@mantine/hooks';
function Demo() {
const spy = useScrollSpy({
selector: '#mdx :is(h1, h2, h3, h4, h5, h6)',
});
const headings = spy.data.map((heading, index) => (
<li
key={heading.id}
style={{
listStylePosition: 'inside',
paddingInlineStart: heading.depth * 20,
background: index === spy.active ? 'var(--mantine-color-blue-light)' : undefined,
}}
>
<UnstyledButton onClick={() => heading.getNode().scrollIntoView()}>
{heading.value}
</UnstyledButton>
</li>
));
return (
<div>
<Text>Scroll to heading:</Text>
<ul style={{ margin: 0, padding: 0 }}>{headings}</ul>
</div>
);
}
New TableOfContents component is built on top of use-scroll-spy
hook and can be used to create table of contents components like the one on the right side of mantine.dev documentation sidebar:
import { TableOfContents } from '@mantine/core';
function Demo() {
return (
<TableOfContents
variant="filled"
color="blue"
size="sm"
radius="sm"
scrollSpyOptions={{
selector: '#mdx :is(h1, h2, h3, h4, h5, h6)',
}}
getControlProps={({ data }) => ({
onClick: () => data.getNode().scrollIntoView(),
children: data.value,
})}
/>
);
}
New Input.ClearButton
component can be used to add clear button to custom inputs based on Input
component. size
of the clear button is automatically inherited from the input:
import { Input } from '@mantine/core';
function Demo() {
const [value, setValue] = useState('clearable');
return (
<Input
placeholder="Clearable input"
value={value}
onChange={(event) => setValue(event.currentTarget.value)}
rightSection={value !== '' ? <Input.ClearButton onClick={() => setValue('')} /> : undefined}
rightSectionPointerEvents="auto"
size="sm"
/>
);
}
Popover and other components based on it now support withOverlay
prop:
import { Anchor, Avatar, Group, Popover, Stack, Text } from '@mantine/core';
function Demo() {
return (
<Popover
width={320}
shadow="md"
withArrow
withOverlay
overlayProps={{ zIndex: 10000, blur: '8px' }}
zIndex={10001}
>
<Popover.Target>
<UnstyledButton style={{ zIndex: 10001, position: 'relative' }}>
<Avatar src="https://avatars.githubusercontent.com/u/79146003?s=200&v=4" radius="xl" />
</UnstyledButton>
</Popover.Target>
<Popover.Dropdown>
<Group>
<Avatar src="https://avatars.githubusercontent.com/u/79146003?s=200&v=4" radius="xl" />
<Stack gap={5}>
<Text size="sm" fw={700} style={{ lineHeight: 1 }}>
Mantine
</Text>
<Anchor href="https://x.com/mantinedev" c="dimmed" size="xs" style={{ lineHeight: 1 }}>
@mantinedev
</Anchor>
</Stack>
</Group>
<Text size="sm" mt="md">
Customizable React components and hooks library with focus on usability, accessibility and
developer experience
</Text>
<Group mt="md" gap="xl">
<Text size="sm">
<b>0</b> Following
</Text>
<Text size="sm">
<b>1,174</b> Followers
</Text>
</Group>
</Popover.Dropdown>
</Popover>
);
}
You can now use container queries in Carousel component. With container queries, all responsive values are adjusted based on the container width, not the viewport width.
Example of using container queries. To see how the grid changes, resize the root element of the demo with the resize handle located at the bottom right corner of the demo:
import { Carousel } from '@mantine/carousel';
function Demo() {
return (
// Wrapper div is added for demonstration purposes only,
// It is not required in real projects
<div
style={{
resize: 'horizontal',
overflow: 'hidden',
maxWidth: '100%',
minWidth: 250,
padding: 10,
}}
>
<Carousel
withIndicators
height={200}
type="container"
slideSize={{ base: '100%', '300px': '50%', '500px': '33.333333%' }}
slideGap={{ base: 0, '300px': 'md', '500px': 'lg' }}
loop
align="start"
>
<Carousel.Slide>1</Carousel.Slide>
<Carousel.Slide>2</Carousel.Slide>
<Carousel.Slide>3</Carousel.Slide>
{/* ...other slides */}
</Carousel>
</div>
);
}
RangeSlider component now supports restrictToMarks
prop:
import { Slider } from '@mantine/core';
function Demo() {
return (
<Stack>
<Slider
restrictToMarks
defaultValue={25}
marks={Array.from({ length: 5 }).map((_, index) => ({ value: index * 25 }))}
/>
<RangeSlider
restrictToMarks
defaultValue={[5, 15]}
marks={[
{ value: 5 },
{ value: 15 },
{ value: 25 },
{ value: 35 },
{ value: 70 },
{ value: 80 },
{ value: 90 },
]}
/>
</Stack>
);
}
Pagination component now supports withPages
prop which allows hiding pages controls and displaying only previous and next buttons:
import { useState } from 'react';
import { Group, Pagination, Text } from '@mantine/core';
const limit = 10;
const total = 145;
const totalPages = Math.ceil(total / limit);
function Demo() {
const [page, setPage] = useState(1);
const message = `Showing ${limit * (page - 1) + 1} – ${Math.min(total, limit * page)} of ${total}`;
return (
<Group justify="flex-end">
<Text size="sm">{message}</Text>
<Pagination total={totalPages} value={page} onChange={setPage} />
</Group>
);
}
use-form hook now supports touchTrigger
option that allows customizing events that change touched state. It accepts two options:
change
(default) – field will be considered touched when its value changes or it has been focusedfocus
– field will be considered touched only when it has been focused
Example of using focus
trigger:
import { useForm } from '@mantine/form';
const form = useForm({
mode: 'uncontrolled',
initialValues: { a: 1 },
touchTrigger: 'focus',
});
form.isTouched('a'); // -> false
form.setFieldValue('a', 2);
form.isTouched('a'); // -> false
// onFocus is called automatically when the user focuses the field
form.getInputProps('a').onFocus();
form.isTouched('a'); // -> true
- Native browser validation does not work in some components, what should I do? question
- My styles are broken with disabled JavaScript. What should I do? question
- How can I add fuzzy search to Select component? question
- use-local-storage hook returns real value only after mounting, is it a bug? question
- How can I upload files from Dropzone component? question
- Autocomplete now supports
clearable
prop - where-* mixins documentation has been added
- use-local-storage hook now supports
sync
option which allows disabling synchronization between browser tabs
7.15.3
[@mantine/charts]
BarChart: FixtextColor
prop being passed down as attribute to the DOM node[@mantine/core]
TypographyStylesProvider: Fix incorrect top and bottom margins of first and last elements (#7334)[@mantine/core]
Transition: Fix some transitions being incompatible with headless mode (#7306)[@mantine/dates]
DateTimePicker: Set milliseconds to 0 on the result date object (#7328)[@mantine/dates]
FixhasNextLevel
prop type leak to DateTimePicker component (#7319)[@mantine/core]
Avatar: Change initials function to use the full name to generate color (#7322)[@mantine/hooks]
use-merged-ref: Add support for ref cleanup function in React 19 (#7304)[@mantine/hooks]
use-debounced-callback: Addflush
method to returned callback (#7272)[@mantine/dates]
Improve compatibility with dayjs plugins in all components (#7302)[@mantine/core]
Update peer dependencies to support React 19 (#7321)
- @baravak made their first contribution in https://github.com/mantinedev/mantine/pull/7302
- @scamden made their first contribution in https://github.com/mantinedev/mantine/pull/7272
- @YukiKoNeko made their first contribution in https://github.com/mantinedev/mantine/pull/7322
- @yuki0410-dev made their first contribution in https://github.com/mantinedev/mantine/pull/7319
- @zoubingwu made their first contribution in https://github.com/mantinedev/mantine/pull/7323
- @clarknova made their first contribution in https://github.com/mantinedev/mantine/pull/7328
Full Changelog: https://github.com/mantinedev/mantine/compare/7.15.2...7.15.3
7.15.2
[@mantine/dates]
DatePicker: Fix incorrect handling of receiving partial value whentype="range"
(#7278)[@mantine/hooks]
use-local-storage: Fix value not being updated whenkey
changes (#7286)[@mantine/charts]
FixgridColor
prop being passed down as attribute to html element (#7288)[@mantine/core]
Update react-textarea-autosize to support React 19 (#7297)[@mantine/core]
TypographyStylesProvider: Fix margin removal affecting non-typography elements (#7290)[@mantine/core]
Tooltip: Addmiddlewares
prop support (#7281)[@mantine/core]
FloatingIndicator: Fix incorrect position calculations when the parent element has border (#7267)[@mantine/core]
ScrollArea: Fix scrollbar not changing with the scroll position on first render (#7257, #7260)[@mantine/tiptap]
Fix incorrect paragraph styles inside lists (#7255)[@mantine/hooks]
Fix incorrect ref types in use-move, use-radial-move, use-in-viewport and use-scroll-into-view (#7252)[@mantine/form]
Fix incorrect validators types (#7242)
- @QuinnStraus made their first contribution in https://github.com/mantinedev/mantine/pull/7255
- @tamago324 made their first contribution in https://github.com/mantinedev/mantine/pull/7260
- @StyxOfDynamite made their first contribution in https://github.com/mantinedev/mantine/pull/7270
- @lamualfa made their first contribution in https://github.com/mantinedev/mantine/pull/7267
- @angristan made their first contribution in https://github.com/mantinedev/mantine/pull/7284
Full Changelog: https://github.com/mantinedev/mantine/compare/7.15.1...7.15.2
7.15.1
[@mantine/dates]
Improve focus behavior of DatePickerInput, DateInput and other components[@mantine/form]
AddtouchTrigger
option support[@mantine/hooks]
Add option to specify prefix inrandonId
function[@mantine/core]
FixwithProps
function requiring all component props instead of partial[@mantine/core]
AdduseModalStackContext
anduseDrawerStackContext
hooks exports[@mantine/core]
ActionIcon: Addinput-*
autocomplete forsize
prop[@mantine/core]
AppShell: Fix incorrect defaultoffsetScrollbars
value forlayout="alt"
[@mantine/core]
FixvirtualColor
function not working in server components (#7184)[@mantine/core]
Checkbox: Fix incorrect Checkbox.Card behavior inside Checkbox.Group (#7187)[@mantine/core]
Checkbox: Fix incorrect Checkbox.Card behavior inside Checkbox.Group (#7187)[@mantine/core]
Slider: Add option to pass attributes down to thumb withthumbProps
(#7214)[@mantine/core]
Switch: Add data-checked attribute to the input (#7228)[@mantine/dates]
FixhasNextLevel
prop type leak to DatePicker component (#7229)[@mantine/dates]
Fix timezone not being applied to the formatted value (#7162)[@mantine/modals]
FixmodalId
being passed to the DOM node as attribute (#7189)[@mantine/core]
TypographyStylesProvider: Fix incorrect paragraphs inside lists styles (#7226)[@mantine/core]
Slider: Fix icon used as thumb child not being visible with the dark color scheme (#7231, #7232)[@mantine/tiptap]
Fix missing border in custom controls (#7239)
- @jnachtigall made their first contribution in https://github.com/mantinedev/mantine/pull/7238
- @onkar-deshpande made their first contribution in https://github.com/mantinedev/mantine/pull/7226
- @anthonyalayo made their first contribution in https://github.com/mantinedev/mantine/pull/7188
- @NyxinU made their first contribution in https://github.com/mantinedev/mantine/pull/7162
Full Changelog: https://github.com/mantinedev/mantine/compare/7.15.0...7.15.1
7.15.0 💋
View changelog with demos on mantine.dev website
You can now sponsor Mantine development with OpenCollective. All funds will be used to improve Mantine and create new features and components.
New use-radial-move hook can be used to create custom radial sliders:
import { useState } from 'react';
import { Box } from '@mantine/core';
import { useRadialMove } from '@mantine/hooks';
import classes from './Demo.module.css';
function Demo() {
const [value, setValue] = useState(115);
const { ref } = useRadialMove(setValue);
return (
<Box className={classes.root} ref={ref} style={{ '--angle': `${value}deg` }}>
<div className={classes.value}>{value}°</div>
<div className={classes.thumb} />
</Box>
);
}
BarChart component now supports getBarColor
prop to assign color based on value. getBarColor
function is called with two arguments: value and series object. It should return a color string (theme color reference or any valid CSS color value).
import { BarChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<BarChart
h={300}
data={data}
dataKey="month"
getBarColor={(value) => (value > 700 ? 'teal.8' : 'red.8')}
series={[{ name: 'Laptops', color: 'gray.6' }]}
/>
);
}
ActionIcon.GroupSection
and Button.GroupSection
are new components that can be used in ActionIcon.Group
/Button.Group
to create sections that are not ActionIcon
/Button
components:
import { IconChevronDown, IconChevronUp } from '@tabler/icons-react';
import { ActionIcon } from '@mantine/core';
import { useCounter } from '@mantine/hooks';
function Demo() {
const [value, { increment, decrement }] = useCounter(135, { min: 0 });
return (
<ActionIcon.Group>
<ActionIcon variant="default" size="lg" radius="md" onClick={decrement}>
<IconChevronDown color="var(--mantine-color-red-text)" />
</ActionIcon>
<ActionIcon.GroupSection variant="default" size="lg" bg="var(--mantine-color-body)" miw={60}>
{value}
</ActionIcon.GroupSection>
<ActionIcon variant="default" size="lg" radius="md" onClick={increment}>
<IconChevronUp color="var(--mantine-color-teal-text)" />
</ActionIcon>
</ActionIcon.Group>
);
}
Table component now support variant="vertical"
:
import { Table } from '@mantine/core';
export function Demo() {
return (
<Table variant="vertical" layout="fixed" withTableBorder>
<Table.Tbody>
<Table.Tr>
<Table.Th w={160}>Epic name</Table.Th>
<Table.Td>7.x migration</Table.Td>
</Table.Tr>
<Table.Tr>
<Table.Th>Status</Table.Th>
<Table.Td>Open</Table.Td>
</Table.Tr>
<Table.Tr>
<Table.Th>Total issues</Table.Th>
<Table.Td>135</Table.Td>
</Table.Tr>
<Table.Tr>
<Table.Th>Total story points</Table.Th>
<Table.Td>874</Table.Td>
</Table.Tr>
<Table.Tr>
<Table.Th>Last updated at</Table.Th>
<Table.Td>September 26, 2024 17:41:26</Table.Td>
</Table.Tr>
</Table.Tbody>
</Table>
);
}
Table component now supports tabularNums
prop to render numbers in tabular style. It sets font-variant-numeric: tabular-nums
which makes numbers to have equal width. This is useful when you have columns with numbers and you want them to be aligned:
import { NumberFormatter, Table } from '@mantine/core';
const data = [
{ product: 'Apples', unitsSold: 2214411234 },
{ product: 'Oranges', unitsSold: 9983812411 },
{ product: 'Bananas', unitsSold: 1234567890 },
{ product: 'Pineapples', unitsSold: 9948810000 },
{ product: 'Pears', unitsSold: 9933771111 },
];
function Demo() {
const rows = data.map((item) => (
<Table.Tr key={item.product}>
<Table.Td>{item.product}</Table.Td>
<Table.Td>
<NumberFormatter value={item.unitsSold} thousandSeparator />
</Table.Td>
</Table.Tr>
));
return (
<Table tabularNums>
<Table.Thead>
<Table.Tr>
<Table.Th>Product</Table.Th>
<Table.Th>Units sold</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
);
}
Modals manager now supports modals.updateModal
and modals.updateContextModal
function to update modal after it was opened:
import { Button } from '@mantine/core';
import { modals } from '@mantine/modals';
function Demo() {
return (
<Button
onClick={() => {
const modalId = modals.open({
title: 'Initial Modal Title',
children: <Text>This text will update in 2 seconds.</Text>,
});
setTimeout(() => {
modals.updateModal({
modalId,
title: 'Updated Modal Title',
children: (
<Text size="sm" c="dimmed">
This is the updated content of the modal.
</Text>
),
});
}, 2000);
}}
>
Open updating modal
</Button>
);
}
use-form hook now supports form.submitting
field and form.setSubmitting
function to track form submission state.
form.submitting
field will be set to true
if function passed to form.onSubmit
returns a promise. After the promise is resolved or rejected, form.submitting
will be set to false
:
import { useState } from 'react';
import { Button, Group, Stack, Text, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
const asyncSubmit = (values: any) =>
new Promise((resolve) => setTimeout(() => resolve(values), 3000));
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: { name: 'John' },
});
const [completed, setCompleted] = useState(false);
const handleSubmit = async (values: typeof form.values) => {
await asyncSubmit(values);
setCompleted(true);
};
if (completed) {
return (
<Stack>
<Text>Form submitted!</Text>
<Button onClick={() => setCompleted(false)}>Reset to initial state</Button>
</Stack>
);
}
return (
<form onSubmit={form.onSubmit(handleSubmit)}>
<TextInput
withAsterisk
label="Name"
placeholder="Your name"
key={form.key('name')}
disabled={form.submitting}
{...form.getInputProps('name')}
/>
<Group justify="flex-end" mt="md">
<Button type="submit" loading={form.submitting}>
Submit
</Button>
</Group>
</form>
);
}
You can also manually set form.submitting
to true
or false
:
import { useForm } from '@mantine/form';
const form = useForm({ mode: 'uncontrolled' });
form.submitting; // -> false
form.setSubmitting(true);
form.submitting; // -> true
form.setSubmitting(false);
form.submitting; // -> false
use-form hook now supports onSubmitPreventDefault
option. This option is useful if you want to integrate useForm
hook with server actions. By default, event.preventDefault()
is called on the form onSubmit
handler. If you want to change this behavior, you can pass onSubmitPreventDefault
option to useForm
hook. It can have the following values:
always
(default) - always callevent.preventDefault()
never
- never callevent.preventDefault()
validation-failed
- callevent.preventDefault()
only if validation failed
import { useForm } from '@mantine/form';
const form = useForm({
mode: 'uncontrolled',
onSubmitPreventDefault: 'never',
});
RichTextEditor component now supports subtle
variant:
import Highlight from '@tiptap/extension-highlight';
import Underline from '@tiptap/extension-underline';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
const content = '<p>Subtle rich text editor variant</p>';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, Underline, Highlight],
content,
});
return (
<RichTextEditor editor={editor} variant="subtle">
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.Code />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
Modal and Drawer components now support onExitTransitionEnd
and onEnterTransitionEnd
props, which can be used to run code after exit/enter transition is finished. For example, this is useful when you want to clear data after modal is closed:
import { useState } from 'react';
import { Button, Group, Modal } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
function Demo() {
const [firstOpened, firstHandlers] = useDisclosure(false);
const [secondOpened, secondHandlers] = useDisclosure(false);
const [modalData, setModalData] = useState({
title: '',
message: '',
});
return (
<>
<Modal
opened={firstOpened}
onClose={() => {
firstHandlers.close();
setModalData({ title: '', message: '' });
}}
title={modalData.title}
>
{modalData.message}
</Modal>
<Modal
opened={secondOpened}
onClose={secondHandlers.close}
onExitTransitionEnd={() => setModalData({ title: '', message: '' })}
title={modalData.title}
>
{modalData.message}
</Modal>
<Group>
<Button
onClick={() => {
firstHandlers.open();
setModalData({ title: 'Edit your profile', message: 'Imagine a form here' });
}}
>
Clear data in onClose
</Button>
<Button
onClick={() => {
secondHandlers.open();
setModalData({ title: 'Edit your profile', message: 'Imagine a form here' });
}}
>
Clear data in onExitTransitionEnd
</Button>
</Group>
</>
);
}
DatePicker and other components based on Calendar component now support withWeekNumbers
prop to display week numbers:
import { DatePicker } from '@mantine/dates';
function Demo() {
return <DatePicker withWeekNumbers />;
}
import { BarChart } from '@mantine/charts';
import { data } from './data';
import classes from './Demo.module.css';
function Demo() {
const bigBarWidth = useMediaQuery('(min-width: 48em)') ? 42 : 26;
const ratio = 0.5;
const smallBarWidth = bigBarWidth * ratio;
const barGap = (bigBarWidth + smallBarWidth) / -2;
return (
<BarChart
h={300}
data={overlayData}
dataKey="index"
barChartProps={{ barGap }}
barProps={(data) => ({ barSize: data.name === 'you' ? bigBarWidth : smallBarWidth })}
classNames={classes}
series={[
{ name: 'you', color: 'var(--you-bar-color)' },
{ name: 'average', color: 'var(--average-bar-color)' },
]}
/>
);
}
Custom variants types augmentation guide was added to the documentation.
Example of adding custom variant type to Button component:
import { ButtonVariant, MantineSize } from '@mantine/core';
type ExtendedButtonVariant = ButtonVariant | 'contrast' | 'radial-gradient';
declare module '@mantine/core' {
export interface ButtonProps {
variant?: ExtendedButtonVariant;
}
}
- How to use Mantine template on GitHub? and How can I submit a template to Mantine documentation? pages were moved from the documentation to Help Center
- How that thing is done on mantine.dev website? question
- Why is it required to have 10 shades per color? question
- Why I see color scheme flickering on page load? question
- How can I test Modal/Drawer/Popover components? question