Releases: mantinedev/mantine
9.4.0 🥵
View changelog with demos on mantine.dev website
Support Mantine development
You can now sponsor Mantine development with OpenCollective.
All funds are used to improve Mantine and create new features and components.
ComboboxPopover component
New ComboboxPopover component allows adding a combobox dropdown
with selectable options to any button element. Unlike Select and MultiSelect, it does not
render an input – you provide your own target element via ComboboxPopover.Target. Supports
single and multiple selection modes with the same data format as Select.
import { useState } from 'react';
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
const [value, setValue] = useState<string | null>(null);
return (
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
value={value}
onChange={setValue}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>{value || 'Select framework'}</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}DataList component
New DataList component displays label-value pairs as a semantic description
list using dl, dt, and dd HTML elements. Supports vertical and horizontal orientations,
dividers between items, and all standard Mantine features like Styles API and size prop.
import { DataList } from '@mantine/core';
const data = [
{ label: 'Name', value: 'John Doe' },
{ label: 'Email', value: 'john@example.com' },
{ label: 'Role', value: 'Software Engineer' },
{ label: 'Location', value: 'San Francisco, CA' },
];
function Demo() {
return (
<DataList size="md" orientation="vertical" withDivider={false}>
{data.map((item) => (
<DataList.Item key={item.label}>
<DataList.ItemLabel>{item.label}</DataList.ItemLabel>
<DataList.ItemValue>{item.value}</DataList.ItemValue>
</DataList.Item>
))}
</DataList>
);
}EmptyState component
New EmptyState component displays a placeholder for "no data" situations:
empty search results, empty tables and lists, first-run states or error illustrations with an
optional call to action. It can be used with icon, title and description shorthand props
or with EmptyState.Indicator, EmptyState.Title, EmptyState.Description and
EmptyState.Actions compound components for full control.
import { Button, EmptyState } from '@mantine/core';
import { MagnifyingGlassIcon } from '@phosphor-icons/react';
function Demo() {
return (
<EmptyState>
<EmptyState.Indicator>
<MagnifyingGlassIcon />
</EmptyState.Indicator>
<EmptyState.Title>No results found</EmptyState.Title>
<EmptyState.Description>
We couldn't find anything matching your search. Try adjusting your filters or searching with
different keywords to see more results.
</EmptyState.Description>
<EmptyState.Actions>
<Button variant="default">Reset filters</Button>
<Button variant="default">Create new</Button>
</EmptyState.Actions>
</EmptyState>
);
}Menubar component
New Menubar component adds a desktop-application style menu bar: a horizontal row
of top-level menu triggers (File, Edit, View, …) where each trigger opens a dropdown. Arrow keys
move between the top-level menus, and once one menu is opened, moving to a sibling opens it
immediately. Menubar is built on top of Menu and follows the WAI-ARIA menubar pattern.
import { Menu, Menubar, Text } from '@mantine/core';
function Demo() {
return (
<Menubar>
<Menubar.Menu width={220}>
<Menubar.Target>File</Menubar.Target>
<Menubar.Dropdown>
<Menu.Item rightSection={<Text size="xs" c="dimmed">⌘N</Text>}>New file</Menu.Item>
<Menu.Item rightSection={<Text size="xs" c="dimmed">⌘⇧N</Text>}>New window</Menu.Item>
<Menu.Sub>
<Menu.Sub.Target>
<Menu.Sub.Item>Open recent</Menu.Sub.Item>
</Menu.Sub.Target>
<Menu.Sub.Dropdown>
<Menu.Item>project-alpha</Menu.Item>
<Menu.Item>project-beta</Menu.Item>
<Menu.Item>project-gamma</Menu.Item>
</Menu.Sub.Dropdown>
</Menu.Sub>
<Menu.Divider />
<Menu.Item rightSection={<Text size="xs" c="dimmed">⌘S</Text>}>Save</Menu.Item>
<Menu.Item>Save as…</Menu.Item>
</Menubar.Dropdown>
</Menubar.Menu>
<Menubar.Menu width={220}>
<Menubar.Target>Edit</Menubar.Target>
<Menubar.Dropdown>
<Menu.Item rightSection={<Text size="xs" c="dimmed">⌘Z</Text>}>Undo</Menu.Item>
<Menu.Item rightSection={<Text size="xs" c="dimmed">⌘⇧Z</Text>}>Redo</Menu.Item>
<Menu.Divider />
<Menu.Item>Cut</Menu.Item>
<Menu.Item>Copy</Menu.Item>
<Menu.Item>Paste</Menu.Item>
</Menubar.Dropdown>
</Menubar.Menu>
<Menubar.Menu width={220}>
<Menubar.Target>Help</Menubar.Target>
<Menubar.Dropdown>
<Menu.Item>Documentation</Menu.Item>
<Menu.Item>Keyboard shortcuts</Menu.Item>
<Menu.Item>About</Menu.Item>
</Menubar.Dropdown>
</Menubar.Menu>
</Menubar>
);
}ResourcesDayView component
New ResourcesDayView component displays resources as rows and
time slots as columns. Each row represents a resource (conference room, person, equipment) and
shows events assigned to that resource. Supports drag and drop across resources, business hours
highlighting, and slot drag select.
// Demo.tsx
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesDayView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesDayView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
startTime="08:00:00"
endTime="18:00:00"
/>
);
}
// data.ts
import dayjs from 'dayjs';
import { ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
{ id: 'new-york', label: 'Meeting room: New York' },
{ id: 'london', label: 'Meeting room: London' },
];
const events = [
{
id: 1,
title: 'Team Standup',
start: \`\${today} 09:00:00\`,
end: \`\${today} 09:30:00\`,
color: 'blue',
resourceId: 'tokyo',
},
{
id: 2,
title: 'Sprint Planning',
start: \`\${today} 10:00:00\`,
end: \`\${today} 11:30:00\`,
color: 'green',
resourceId: 'tokyo',
},
{
id: 3,
title: 'Client Call',
start: \`\${today} 09:30:00\`,
end: \`\${today} 10:30:00\`,
color: 'violet',
resourceId: 'paris',
},
{
id: 4,
title: 'Design Review',
start: \`\${today} 13:00:00\`,
end: \`\${today} 14:00:00\`,
color: 'orange',
resourceId: 'paris',
},
{
id: 5,
title: '1:1 Meeting',
start: \`\${today} 11:00:00\`,
end: \`\${today} 11:30:00\`,
color: 'cyan',
resourceId: 'new-york',
},
{
id: 6,
title: 'Workshop',
start: \`\${today} 14:00:00\`,
end: \`\${today} 16:00:00\`,
color: 'pink',
resourceId: 'new-york',
},
{
id: 7,
title: 'Architecture Review',
start: \`\${today} 10:00:00\`,
end: \`\${today} 11:00:00\`,
color: 'red',
resourceId: 'london',
},
{
id: 8,
title: 'Retrospective',
start: \`\${today} 15:00:00\`,
end: \`\${today} 16:00:00\`,
color: 'grape',
resourceId: 'london',
},
];ResourcesWeekView component
New ResourcesWeekView component displays resources as rows
and a full week of time slots as columns with a two-level header showing day names and time
labels. Supports drag and drop, slot selection, business hours, and current time indicator.
// Demo.tsx
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesWeekView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesWeekView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
startTime="08:00:00"
endTime="18:00:00"
startScrollDateTime={`${today} 08:00:00`}
/>
);
}
// data.ts
import dayjs from 'dayjs';
import { ScheduleResourceData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const tomorrow = dayjs().add(1, 'day').format('YYYY-MM-DD');
const dayAfter = dayjs().add(2, 'day').format('YYYY-MM-DD');
const dayAfter2 = dayjs().add(3, 'day').format('YYYY-MM-DD');
const resources: ScheduleResourceData[] = [
{ id: 'tokyo', label: 'Meeting room: Tokyo' },
{ id: 'paris', label: 'Meeting room: Paris' },
{ id: 'new-york', label: 'Meeting room: New York' },
{ id: 'london', label: 'Meeting room: London' },
];
const events = [
{
id: 1,
title: 'Team Standup',
start: \`\${today} 09:00:00\`,
end: \`\${today} 09:30:00\`,
color: 'blue',
res...9.3.2
What's Changed
[@mantine/core]Allow undefined className with TypeScriptexactOptionalPropertyTypes(#8978)[@mantine/dates]TimePicker: Allow entering 01-09 hours over a selected 00 value (#8970)[@mantine/mcp-server]Fix cli flags not being handled correctly (#8966)[@mantine/core]Combobox: Fix default options rendering not fully working with some components combination (#8979)[@mantine/charts]SankeyChart: Fix incorrect color resolving (#8973)[@mantine/dates]DatePickerPicker: Fix range presets ignoring specified time (#8980)[@mantine/schedule]DayView: Fix double top border when events are present[@mantine/dates]TimePicker: AddcloseDropdownOnPresetSelectprop support[@mantine/dates]TimePicker: Fix input getting stuck at maximum value when typing at the last spin input[@mantine/core]PasswordInput: Fix sections misplaced whendiroverrides parent direction (#8936)[@mantine/core]Splitter: Add option to reset on double click (#8957)[@mantine/core]Popover: Fix dropdown position not updating when page is zommed[@mantine/core]Text: Fix incorrect defaulttextWrapvalue (#8961)[@mantine/core]Checkbox: FixreadOnlyprop not working correctly for controlled checkbox (#8960)[@mantine/core]Textarea: Fix autosize textarea not growing when resized within Splitter (#8956)
New Contributors
- @SyntaxHQDEV made their first contribution in #8966
- @bensaufley made their first contribution in #8978
Full Changelog: 9.3.1...9.3.2
9.3.1
What's Changed
[@mantine/notifications]Fix stale DOM nodes references not being cleaned up when notifications is closed (#8955)[@mantine/dates]DateInput: Addpresetssupport (#8954)[@mantine/core]Collapse: FixkeepMountedprop not being set correctly (#8949)[@mantine/core]Menu: Add controlled state support for Menu.Sub opened state[@mantine/schedule]Fix incorrect current time indicator position when time does not divide evenly with interval minutes in DayView and WeekView (#8945)[@mantine/core]Popover: Fix context menu not working on iOS touch devices (#8942)[@mantine/core]SegemntedControl: Fix incorrect indicator border-radius calculation (#8904)[@mantine/core]PinInput: Fix incorrect placeholder text centering (#8943)[@mantine/core]Tree: Fix arrow key navigation focusing hidden nodes when keepMounted is set (#8939)[@mantine/core]MaskInput: Fix compatibility issues with uncontrolled use-form (#8947)[@mantine/hooks]use-id: Fix id changing to new value with Activity (#8925)
New Contributors
- @spokodev made their first contribution in #8925
- @cyphercodes made their first contribution in #8947
- @KasperiP made their first contribution in #8939
Full Changelog: 9.3.0...9.3.1
9.3.0 🥵
View changelog with demos on mantine.dev website
Support Mantine development
You can now sponsor Mantine development with OpenCollective.
All funds are used to improve Mantine and create new features and components.
Pagination responsive layout
Pagination component now supports layout="responsive" prop that uses CSS container
queries to switch between page number buttons and a compact "Page X of Y" label based on the available width.
import { Box, Pagination } from '@mantine/core';
function Demo() {
return (
<Box style={{ resize: 'horizontal', overflow: 'auto', minWidth: 200, maxWidth: '100%' }}>
<Pagination total={20} layout="responsive" />
</Box>
);
}Text textWrap prop
Text and Blockquote components now support
textWrap prop that controls the text-wrap CSS property. You can use it to balance line lengths
or prevent orphaned words in paragraphs.
import { Text } from '@mantine/core';
function Demo() {
return (
<Text textWrap="wrap">
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Quasi voluptatibus inventore iusto
cum dolore molestiae perspiciatis! Totam repudiandae impedit maxime!
</Text>
);
}use-splitter hook
New use-splitter hook provides resizable split-pane functionality
with pointer drag, keyboard navigation (WAI-ARIA Window Splitter pattern), collapsible panels
and min/max constraints:
import React from 'react';
import { DotsSixVerticalIcon } from '@phosphor-icons/react';
import { useSplitter } from '@mantine/hooks';
const colors = ['var(--mantine-color-blue-filled)', 'var(--mantine-color-teal-filled)'];
const labels = ['Panel A', 'Panel B'];
function Demo() {
const splitter = useSplitter({
panels: [
{ defaultSize: 50, min: 20 },
{ defaultSize: 50, min: 20 },
],
});
return (
<div
ref={splitter.ref}
style={{
display: 'flex',
height: 200,
borderRadius: 'var(--mantine-radius-md)',
overflow: 'hidden',
}}
>
{splitter.sizes.map((size, i) => (
<React.Fragment key={i}>
{i > 0 && (
<div
{...splitter.getHandleProps({ index: i - 1 })}
style={{
width: 4,
flexShrink: 0,
cursor: 'col-resize',
touchAction: 'none',
backgroundColor: 'var(--mantine-color-default-border)',
position: 'relative',
}}
>
<div
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 8,
height: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 'var(--mantine-radius-xs)',
backgroundColor: 'var(--mantine-color-default)',
border: '1px solid var(--mantine-color-default-border)',
color: 'var(--mantine-color-dimmed)',
}}
>
<DotsSixVerticalIcon />
</div>
</div>
)}
<div
style={{
width: `${size}%`,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors[i],
color: 'var(--mantine-color-white)',
fontWeight: 500,
whiteSpace: 'nowrap',
gap: 2,
}}
>
{labels[i]} ({Math.round(size)}%)
</div>
</React.Fragment>
))}
</div>
);
}Splitter component
New Splitter component provides declarative resizable split pane layout
built on top of the use-splitter hook:
import { Splitter } from '@mantine/core';
function Demo() {
return (
<Splitter orientation="horizontal" h={200}>
<Splitter.Pane defaultSize={50} min={20} bg="blue">
First pane
</Splitter.Pane>
<Splitter.Pane defaultSize={50} min={20} bg="teal">
Second pane
</Splitter.Pane>
</Splitter>
);
}CodeHighlight line numbers
CodeHighlight component now supports withLineNumbers prop
to display line numbers alongside the code:
import { CodeHighlight } from '@mantine/code-highlight';
const exampleCode = `...`;
function Demo() {
return <CodeHighlight code={exampleCode} language="tsx" withLineNumbers />;
}OverflowList collapseFrom
OverflowList component now supports collapseFrom prop that controls
from which direction items are collapsed when they overflow. Set collapseFrom="start" to
collapse items from the beginning – this is useful for breadcrumb-like patterns where
the last items should remain visible.
// OverflowListDemo.tsx
import { Badge, OverflowList } from '@mantine/core';
import { data } from './data';
function Demo() {
return (
<div style={{ resize: 'horizontal', overflow: 'auto', maxWidth: '100%' }}>
<OverflowList
data={data}
gap={4}
collapseFrom="start"
renderOverflow={(items) => <Badge>+{items.length} more</Badge>}
renderItem={(item, index) => <Badge key={index}>{item}</Badge>}
/>
</div>
);
}
// data.ts
export const data = [
'Apple',
'Banana',
'Cherry',
'Date',
'Elderberry',
'Fig',
'Grape',
'Honeydew',
'Indian Fig',
'Jackfruit',
'Kiwi',
'Lemon',
'Mango',
'Nectarine',
'Orange',
'Papaya',
];Textarea bottomSection
Textarea component now supports bottomSection prop that renders content
inside the input border at the bottom. This is useful for displaying character counters
or other supplementary information:
import { useState } from 'react';
import { Text, Textarea } from '@mantine/core';
function Demo() {
const maxLength = 500;
const [value, setValue] = useState('');
return (
<Textarea
label="Your message"
placeholder="Type your message..."
autosize
minRows={4}
value={value}
onChange={(event) => setValue(event.currentTarget.value.slice(0, maxLength))}
bottomSection={
<Text size="xs" c="dimmed">
{value.length}/{maxLength} characters
</Text>
}
/>
);
}Combobox floatingHeight
Combobox, Select, MultiSelect,
Autocomplete and TagsInput now support
floatingHeight="viewport". When set, the dropdown grows to fill the available vertical
space in the viewport and the flip middleware is disabled – useful when working with
large option lists:
import { useState } from 'react';
import { Combobox, Input, InputBase, ScrollArea, useCombobox } from '@mantine/core';
const countries = [
'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Argentina', 'Armenia', 'Australia',
'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium',
'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso',
'Burundi', 'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Chad', 'Chile', 'China', 'Colombia',
'Comoros', 'Costa Rica', 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti',
'Dominica', 'Ecuador', 'Egypt', 'El Salvador', 'Estonia', 'Eswatini', 'Ethiopia', 'Fiji',
'Finland', 'France', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada',
'Guatemala', 'Guinea', 'Guyana', 'Haiti', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia',
'Iran', 'Iraq', 'Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya',
'Kiribati', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya',
'Liechtenstein', 'Lithuania', 'Luxembourg', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives',
'Mali', 'Malta', 'Mauritania', 'Mauritius', 'Mexico', 'Moldova', 'Monaco', 'Mongolia',
];
function Demo() {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const [value, setValue] = useState<string | null>(null);
const options = countries.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));
return (
<Combobox
store={combobox}
floatingHeight="viewport"
onOptionSubmit={(val) => {
setValue(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={() => combobox.toggleDropdown()}
>
{value || <Input.Placeholder>Pick a country</Input.Placeholder>}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
<ScrollArea.Autosize mah="var(--comb...9.2.2
What's Changed
[@mantine/core]Pill: Fix incorrect overflow handling (#8929)[@mantine/dates]TimePicker: Fix incorrect am/pm switching in some cases in production builds (#8911)[@mantine/hooks]use-mask: Fix undo keyboard shortcut not working (#8927)[@mantine/hooks]use-mask: Fix cursor jumping on paste/cut (#8926)[@mantine/core]Input: Fix sections misplaced whendiroverrides parent direction (#8905)[@mantine/core]Select: Fix clear button not showing for falsy primitive values (#8901)[@mantine/core]Fix incorrect attributes type in Modal, Drawer and Spotlight[@mantine/tiptap]Fix controls throwing errors when editor is destroyed/not initialized (#8900)[@mantine/core]Menu: Add option to pass safe area polygon options down to Menu.Sub (#8908)
New Contributors
- @chbaefront made their first contribution in #8908
- @krusche made their first contribution in #8900
- @hyeongjun6364 made their first contribution in #8901
- @Israadaassi1 made their first contribution in #8921
Full Changelog: 9.2.1...9.2.2
9.2.1
What's Changed
[@mantine/tiptap]Fix controls having stale state when built with react compiler (#8725)[@mantine/charts]Fix highlighted are being stuck at the previously hovered chart legend section if mouse is moved quickly (#8768)[@mantine/modals]Fix incorrect duplicate modals ids handling (#8736)[@mantine/core]Table: Fix th borders being rendered transparent ifstickyprop set (#8778)[@mantine/core]Fix error id not being passed toaria-describedbyin Checkbox, Radio and Switch components (#8820)[@mantine/core]Addaria-valuetextsupport to Slider and RangeSlider (#8871)[@mantine/schedule]MonthView: Improve multi-day events overlap rendering for maxed-out days (#8874)[@mantine/core]FixmergeMantineThememutatedDEFAULT_THEME.headings(#8875)[@mantine/hooks]use-debounced-value: Fixleadingcallback not being reset on timeout (#8833)[@mantine/core]Highlight: Add accent insensitive option support (#8890)[@mantine/form]Fix some handlers not being stable reference (#8891)[@mantine/dropzone]ChangeuseFsAccessApito false by default to make Dropzone compatible with all current browsers (#8876)[@mantine/schedule]Fix incorrect events positioning withintervalMinutes={60}(#8887)[@mantine/core]PinInput: Fix keyboard shorcuts being blocked on numeric input type (#8889)[@mantine/form]Fix default validators making form.validate return value async (#8880)[@mantine/core]Menu: Add safe polygon support for sub menus (#8888)[@mantine/core]ScrollArea: Fix Maximum update depth exceeded error[@mantine/core]TreeSelect: Fix focus to moving to input after clear button click
New Contributors
- @oab24413gmai made their first contribution in #8892
- @sarioglu made their first contribution in #8890
- @noahsilas made their first contribution in #8833
- @chaitanya-bhagavan made their first contribution in #8875
- @liamdon made their first contribution in #8874
- @oozan made their first contribution in #8820
- @Pirulax made their first contribution in #8736
Full Changelog: 9.2.0...9.2.1
9.2.0 🔥
View changelog with demos on mantine.dev website
Support Mantine development
You can now sponsor Mantine development with OpenCollective.
All funds are used to improve Mantine and create new features and components.
TreeSelect component
New TreeSelect component allows picking one or more values from hierarchical tree data.
It supports three selection modes: single, multiple, and checkbox (with parent-child cascade):
import { TreeSelect } from '@mantine/core';
import { data } from './data';
function Demo() {
return (
<TreeSelect
label="Your favorite item"
placeholder="Pick value"
data={data}
/>
);
}Tree select Combobox examples
New Combobox examples showing how to build tree select components
from Combobox primitives with connecting lines, expand/collapse chevrons, and proper indentation:
- Tree select – basic single-value tree select
- Tree multi select – multi select with checkbox cascade
- Searchable tree select – tree select with search filtering
- Tree select with checkboxes – single select with expand-on-click
- Virtualized tree select – large tree (~500 nodes) with @tanstack/react-virtual
Notifications swipe dismissal
@mantine/notifications now supports dismissing notifications by dragging them
left or right, and with horizontal scroll swipe while hovered. Both interactions can be disabled
on Notifications, and individual items can opt out with allowClose: false.
import { Button } from '@mantine/core';
import { notifications } from '@mantine/notifications';
function Demo() {
return (
<Button
onClick={() =>
notifications.show({
title: 'Default notification',
message: 'Do not forget to star Mantine on GitHub! 🌟',
})
}
>
Show notification
</Button>
);
}use-drag hook
New use-drag hook handles pointer drag gestures with movement tracking,
velocity, direction and axis constraints. It uses the Pointer Events API and works with
both mouse and touch input:
import { useState } from 'react';
import { Button, Group, Paper, Text } from '@mantine/core';
import { useDrag } from '@mantine/hooks';
interface NotificationItem {
id: number;
text: string;
}
function SwipeNotification({
notification,
onDismiss,
}: {
notification: NotificationItem;
onDismiss: (id: number) => void;
}) {
const [offset, setOffset] = useState(0);
const [dismissed, setDismissed] = useState(false);
const { ref, active } = useDrag(
(state) => {
if (state.last) {
const shouldDismiss =
Math.abs(state.movement[0]) > 120 || state.velocity[0] > 0.5;
if (shouldDismiss) {
setDismissed(true);
setTimeout(() => onDismiss(notification.id), 300);
} else {
setOffset(0);
}
} else {
setOffset(state.movement[0]);
}
},
{ axis: 'x', threshold: 5, filterTaps: true }
);
return (
<Paper
ref={ref}
p="sm"
mb="xs"
withBorder
radius="md"
style={{
transform: dismissed
? `translateX(${offset > 0 ? 400 : -400}px)`
: `translateX(${offset}px)`,
opacity: dismissed ? 0 : 1 - Math.min(Math.abs(offset) / 200, 1) * 0.6,
transition: active ? 'none' : 'transform 300ms ease, opacity 300ms ease',
cursor: active ? 'grabbing' : 'grab',
touchAction: 'pan-y',
userSelect: 'none',
}}
>
{notification.text}
</Paper>
);
}
const initialItems: NotificationItem[] = [
{ id: 1, text: 'New message from Alice' },
{ id: 2, text: 'Build succeeded' },
{ id: 3, text: 'Deployment complete' },
{ id: 4, text: 'Review requested' },
];
function Demo() {
const [notifications, setNotifications] = useState(initialItems);
return (
<div style={{ height: 300 }}>
{notifications.map((n) => (
<SwipeNotification
key={n.id}
notification={n}
onDismiss={(id) =>
setNotifications((items) => items.filter((item) => item.id !== id))
}
/>
))}
{notifications.length === 0 && (
<Text ta="center" c="dimmed" py="md">All cleared!</Text>
)}
<Group justify="center" mt="md">
<Button onClick={() => setNotifications(initialItems)}>
Reset
</Button>
</Group>
</div>
);
}InlineDateTimePicker component
New InlineDateTimePicker component renders a calendar
with a time picker inline, without a dropdown. It supports both default and range modes:
import { InlineDateTimePicker } from '@mantine/dates';
function Demo() {
return <InlineDateTimePicker />;
}Set type="range" to select a date and time range with two time inputs:
import { InlineDateTimePicker } from '@mantine/dates';
function Demo() {
return <InlineDateTimePicker type="range" />;
}DateTimePicker range support
DateTimePicker now supports type="range" to select
a date and time range. In range mode, two time inputs are displayed in the dropdown
for start and end times:
import { DateTimePicker } from '@mantine/dates';
function Demo() {
return (
<DateTimePicker
type="range"
label="Pick dates and times range"
placeholder="Pick dates and times range"
/>
);
}DateTimePicker valueFormat function
DateTimePicker valueFormat prop now accepts a function in addition
to a dayjs format string. The callback receives the value as a YYYY-MM-DD HH:mm:ss string and
returns the formatted value, which is useful for cases that cannot be expressed with a dayjs
format string:
import dayjs from 'dayjs';
import { DateTimePicker } from '@mantine/dates';
function Demo() {
return (
<DateTimePicker
valueFormat={(date) => dayjs(date).format('dddd, MMMM D [at] h:mm A')}
defaultValue="2024-04-11 14:45:00"
label="Pick date and time"
placeholder="Pick date and time"
/>
);
}RollingNumber component
New RollingNumber component animates value changes with rolling digit
transitions. Each digit independently rolls to its new position when the value changes:
import { useState } from 'react';
import { Button, Group, RollingNumber } from '@mantine/core';
function Demo() {
const [value, setValue] = useState(1234);
return (
<>
<RollingNumber value={value} fz="36px" />
<Group mt="md">
<Button onClick={() => setValue((v) => v + 1)}>Increment</Button>
<Button onClick={() => setValue((v) => v - 1)}>Decrement</Button>
<Button onClick={() => setValue(Math.floor(Math.random() * 10000))}>Random</Button>
</Group>
</>
);
}MaskInput improvements
MaskInput now supports a resetRef prop that assigns a function that
clears the input value imperatively. This is useful because MaskInput is uncontrolled
internally, so setting value from a parent does not clear it:
import { useRef } from 'react';
import { MaskInput, Button, Group } from '@mantine/core';
function Demo() {
const resetRef = useRef<() => void>(null);
return (
<>
<MaskInput
label="Phone number"
placeholder="(___) ___-____"
mask="(999) 999-9999"
resetRef={resetRef}
/>
<Group mt="md">
<Button onClick={() => resetRef.current?.()}>Reset</Button>
</Group>
</>
);
}MaskInput integration with use-form is now documented. Use defaultValue
to seed the initial value and onChangeRaw to write the raw value to form state:
import { Button, MaskInput } from '@mantine/core';
import { useForm } from '@mantine/form';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: { phone: '' },
});
return (
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<MaskInput
mask="(999) 999-9999"
placeholder="(___) ___-____"
label="Phone"
onChangeRaw={(raw) => form.setFieldValue('phone', raw)}
/>
<Button type="submit" mt="md">
Submit
</Button>
</form>
);
}SankeyChart component
New SankeyChart component visualizes flow between nodes as a Sankey diagram
where the width of each link is proportional to the flow value:
// Demo.tsx
import { SankeyChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return <SankeyChart data={data} />;
}
// data.ts
export const data = {
nodes: [
{ name: 'Visit' },
{ name: 'Direct-Favourite' },
{ name: 'Page-Click' },
{ name: 'Detail-Favourite' },
{ name: 'Lost' },
],
links: [
{ source: 0, target: 1, value: 3728.3 },
{ source: 0, target: 2, value: 354170 },
{ source: 2, target: 3, value: 62429 },
{ source:...9.1.1
What's Changed
[@mantine/spotlight]Fix error thrown when listId is empty (#8863)[@mantine/schedule]FixonEventClicknot being passed down to MoreEvents in DayView and MonthView components (#8862)[@mantine/core]Tree: Add dnd handle and dnd lock support[@mantine/schedule]Fixlabelsnot propagating to custom schedule header and more events lists[@mantine/modals]Add ModalsSettings type export[@mantine/schedule]FixrenderEventnot working in MoreEvents[@mantine/mcp-server]Update displayed version[@mantine/core]Combobox: Fix keyboard events not triggering in Safari after click on the inout (#7386)[@mantine/hooks]use-focus-return: Fix incorrect logic when used with nested focus traps (#8857)[@mantine/core]ScrollArea: Fix scrollbar never visible withoffsetScrollbars="present"(#8844)[@mantine/core]Fix incorrectrenderOptiontype in Combobox-based components (#8858)[@mantine/hooks]use-mask: Fix stale mask partial remaining as input value on blur after input field was cleared[@mantine/hooks]use-mask: Fix incorrect cursor position handling[@mantine/hooks]use-mask: Fix part of the mask remaining as input value on blur[@mantine/core]Radio: Fix icon not being centered on some low-density screens (#8845)[@mantine/core]Highlight: Fix wholeWord matching for non-ASCII characters[@mantine/core]Card: Fix Card.Section not being handled correctly during server-side rendering of server components (#8846))[@mantine/core]FixclearButtonProps={{ size: lg }}not working when passed to Select and other similar components (#8855)[@mantine/core]Fix Styles API defined for Input not being applied to Select and MultiSelect components (#8851)
New Contributors
- @malekmimouna made their first contribution in #8856
- @dfedoryshchev made their first contribution in #8853
- @stasyzon made their first contribution in #8845
- @atinary-ybouzonie made their first contribution in #8863
Full Changelog: 9.1.0...9.1.1
9.1.0
View changelog with demos on mantine.dev website
Support Mantine development
You can now sponsor Mantine development with OpenCollective.
All funds are used to improve Mantine and create new features and components.
deduplicateInlineStyles
New deduplicateInlineStyles prop on MantineProvider enables
React 19 style tag deduplication for responsive style props.
When many components share the same responsive style prop values, only a single <style />
tag is generated and hoisted to <head /> instead of each component injecting its own:
import { MantineProvider } from '@mantine/core';
function Demo() {
return (
<MantineProvider deduplicateInlineStyles>
{/* Your app here */}
</MantineProvider>
);
}This can significantly improve performance when rendering large lists of components
with identical responsive style props. See the
styles performance guide for more details.
use-mask hook
New use-mask hook attaches real-time input masking to any <input> element via
a ref callback. It formats user input against a defined pattern and exposes both the masked display
value and the raw unmasked value. The hook supports built-in and custom tokens, dynamic masks,
character transforms, optional segments, and regex array format:
import { TextInput, Text } from '@mantine/core';
import { useMask } from '@mantine/hooks';
function Demo() {
const { ref, value, rawValue } = useMask({ mask: '(999) 999-9999' });
return (
<>
<TextInput ref={ref} label="Phone number" placeholder="(___) ___-____" />
<Text size="sm" mt="sm">Masked value: {value}</Text>
<Text size="sm">Raw value: {rawValue}</Text>
</>
);
}MaskInput component
New MaskInput component is a wrapper around use-mask hook
that provides all standard input props (label, description, error, etc.) and supports all mask options:
import { MaskInput } from '@mantine/core';
function Demo() {
return (
<MaskInput
variant="default" size="sm" radius="md" label="Input label" withAsterisk={false} description="Input description" error=""
mask="(999) 999-9999"
placeholder="(___) ___-____"
/>
);
}Treemap component
New Treemap component displays hierarchical data as a set of nested
rectangles. It is based on the Treemap recharts component:
// Demo.tsx
import { Treemap } from '@mantine/charts';
import { data } from './data';
function Demo() {
return <Treemap data={data} />;
}
// data.ts
export const data = [
{
name: 'Frontend',
color: 'blue.8',
children: [
{ name: 'React', value: 400 },
{ name: 'Vue', value: 200 },
{ name: 'Angular', value: 150 },
],
},
{
name: 'Backend',
color: 'teal.8',
children: [
{ name: 'Node', value: 300 },
{ name: 'Python', value: 250 },
{ name: 'Go', value: 100 },
],
},
{
name: 'Mobile',
color: 'red.8',
children: [
{ name: 'React Native', value: 200 },
{ name: 'Flutter', value: 180 },
],
},
];TimePicker duration type
TimePicker component now supports type="duration" prop that allows
entering durations that exceed 24 hours. In this mode, the hours field has no upper limit
and the input width adjusts dynamically based on the entered value:
import { TimePicker } from '@mantine/dates';
function Demo() {
return <TimePicker label="Enter duration" type="duration" withSeconds />;
}Heatmap legend
Heatmap component now supports withLegend prop that displays
a color legend below the chart. Use legendLabels prop to customize labels
(default: ['Less', 'More']):
// Demo.tsx
import { Heatmap } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<Heatmap
data={data}
startDate="2024-02-16"
endDate="2025-02-16"
withMonthLabels
withWeekdayLabels
withLegend
/>
);
}
// data.ts
export const data = ${JSON.stringify(data, null, 2)};MonthPicker and YearPicker presets
MonthPicker and YearPicker components now support
presets prop that allows adding predefined values to pick from. Presets are also available
in MonthPickerInput and YearPickerInput
components:
import dayjs from 'dayjs';
import { MonthPicker } from '@mantine/dates';
function Demo() {
return (
<MonthPicker
presets={[
{ value: dayjs().startOf('month').format('YYYY-MM-DD'), label: 'This month' },
{ value: dayjs().add(1, 'month').startOf('month').format('YYYY-MM-DD'), label: 'Next month' },
{ value: dayjs().subtract(1, 'month').startOf('month').format('YYYY-MM-DD'), label: 'Last month' },
{ value: dayjs().add(6, 'month').startOf('month').format('YYYY-MM-DD'), label: 'In 6 months' },
{ value: dayjs().add(1, 'year').startOf('month').format('YYYY-MM-DD'), label: 'Next year' },
{ value: dayjs().subtract(1, 'year').startOf('month').format('YYYY-MM-DD'), label: 'Last year' },
]}
/>
);
}use-roving-index hook
New use-roving-index hook implements the
roving tabindex
keyboard navigation pattern. It manages tabIndex state for a group of focusable elements,
handles arrow key navigation with disabled item skipping, and supports both 1D lists and 2D grids:
import { Button, Group } from '@mantine/core';
import { useRovingIndex } from '@mantine/hooks';
const items = ['Bold', 'Italic', 'Underline', 'Strikethrough', 'Code'];
function Demo() {
const { getItemProps } = useRovingIndex({
total: items.length,
orientation: 'horizontal',
loop: true,
});
return (
<Group gap="xs">
{items.map((item, index) => (
<Button key={item} variant="default" {...getItemProps({ index })}>
{item}
</Button>
))}
</Group>
);
}Tree drag and drop
Tree component now supports drag-and-drop reordering of nodes.
Provide onDragDrop callback to enable it, and use the moveTreeNode utility
to update data based on the result:
import { useState } from 'react';
import { CaretDownIcon } from '@phosphor-icons/react';
import { Group, moveTreeNode, RenderTreeNodePayload, Tree, TreeNodeData } from '@mantine/core';
const data: TreeNodeData[] = [
{
label: 'Pages',
value: 'pages',
children: [
{ label: 'index.tsx', value: 'pages/index.tsx' },
{ label: 'about.tsx', value: 'pages/about.tsx' },
{ label: 'contact.tsx', value: 'pages/contact.tsx' },
],
},
{
label: 'Components',
value: 'components',
children: [
{ label: 'Header.tsx', value: 'components/Header.tsx' },
{ label: 'Footer.tsx', value: 'components/Footer.tsx' },
{ label: 'Sidebar.tsx', value: 'components/Sidebar.tsx' },
],
},
{ label: 'package.json', value: 'package.json' },
{ label: 'tsconfig.json', value: 'tsconfig.json' },
];
function Leaf({ node, expanded, hasChildren, elementProps }: RenderTreeNodePayload) {
return (
<Group gap={5} {...elementProps}>
{hasChildren && (
<CaretDownIcon
size={18}
style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)' }}
/>
)}
<span>{node.label}</span>
</Group>
);
}
function Demo() {
const [treeData, setTreeData] = useState(data);
return (
<Tree
data={treeData}
onDragDrop={(payload) =>
setTreeData((current) => moveTreeNode(current, payload))
}
renderNode={(payload) => <Leaf {...payload} />}
/>
);
}Tree async loading
Tree now supports lazy loading of children. Set hasChildren: true
on a node without providing children – when the node is expanded for the first time,
onLoadChildren callback passed to useTree is called. Use mergeAsyncChildren
utility to splice loaded children into your data:
import { useState } from 'react';
import { CaretDownIcon, SpinnerIcon } from '@phosphor-icons/react';
import {
Group,
mergeAsyncChildren,
RenderTreeNodePayload,
Tree,
TreeNodeData,
useTree,
} from '@mantine/core';
const initialData: TreeNodeData[] = [
{ label: 'Documents', value: 'documents', hasChildren: true },
{ label: 'Photos', value: 'photos', hasChildren: true },
{ label: 'README.md', value: 'readme' },
];
// Simulates an API call to load children
async function fetchChildren(parentValue: string): Promise<TreeNodeData[]> {
await new Promise((resolve) => setTimeout(resolve, 1000));
return [
{ label: `${parentValue}/file-1.txt`, value: `${parentValue}/file-1.txt` },
{ label: `${parentValue}/file-2.txt`, value: `${parentValue}/file-2.txt` },
{
label: `${parentValue}/subfolder`,
value: `${parentValue}/subfolder`,
hasChildren: true,
},
];
}
function Leaf({ node, expanded, hasChildre...9.0.2
What's Changed
[@mantine/schedule]Change default events border-radius to sm[@mantine/dates]DateTimePicker: Fix formatting not working withwithSecondsset ontimePickerPropsonly[@mantine/core]Textarea: Fix error thrown on resize in some cases[@mantine/modals]Fixmodals.closeAll()called from comtext modal causing infinite rerendering[@mantine/tiptap]RichTextEditor: Fix invisible caret in empty task list items[@mantine/schedule]Fix rrule package imports bot being compatible with esm only bundlers[@mantine/schedule]FixonEventClickcalled when event is resizing[@mantine/core]Fix incorrect default colors resolver for custom colors in light variant
New Contributors
- @dkpark10 made their first contribution in #8807
- @sdelpercio made their first contribution in #8826
Full Changelog: 9.0.1...9.0.2