9.5.2
-
[@mantine/hooks]use-debounced-value: Fixleading: truefiring multiple times per burst and emiting a stale value (#9119) -
[@mantine/schedule]Fix recurring events not working with timzones (#9112) -
[@mantine/dates]FixminDateused for default date in some cases (#9117) -
[@mantine/core]Tooltip: Fix tooltip setting NaN in top/left position style when event position values cannot be read (#9131) -
[@mantine/dates]TimePicker: Fix incorrect focus handling of partially filled hours field (#9128) -
[@mantine/core]RollingNumber: Fix incorrect copy event handling (#9132) -
[@mantine/core]Notification: Fix incorrectcloseButtonPropstype (#9134) -
[@mantine/code-highlight]Add support for lazy languages loading (#9141) -
[@mantine/code-highlight]CodeHighlight: Add prop to keep indentation of the first line of the code block (#9140) -
[@mantine/dates]Add missing formatting functions to MiniCalendarm DateInput and YarsList components -
[@mantine/schedule]WeekView: Improve performance of events positioning algorithm (#9075) -
[@mantine/form]Add new useWatchValue hook -
[@mantine/core]Fix Combobox-based components not working correctly with Chrome autocomplete
9.5.1
-
[@mantine/tiptap]Fix controls being initially disabledbefore element is focused -
[@mantine/tiptap]Fix source code control wrapping content with extra p tag -
[@mantine/hooks]use-scroll-spy: Allow usage with refs (#9025) -
[@mantine/core]ColorInput: Add support for fullWidth prop (#9061) -
[@mantine/core]Checkbox: Fix incottect indeterminate aria attributes handling in Checkbox.Card (#9095) -
[@mantine/core]FloatingIndicator: Fix position and size calculation under scaled ancestors (#9071) -
[@mantine/core]Tooltip: Add interactive prop support (#9072) -
[@mantine/core]Cascader: Add safe area polygon support -
[@mantine/core]PasswordInput: Add option to change whether the visibility toggle is focusable (#9090) -
[@mantine/charts]ScatterChart: Add option to add second y axis -
[@mantine/schedule]YearView: AddrenderDayprop support -
[@mantine/schedule]YearView: Add option to hide weekend days -
[@mantine/core]InputWrapper: Fixcomponent: divtriggering typescript error if passed todescriptionProps -
[@mantine/schedule]ResourcesMonthView: Add option to resize events -
[@mantine/core]FloatingWindow: Add support foronSizeChangeandonResizeStartprops (#9085)
9.5.0 🤖
View changelog with demos on mantine.dev website
You can now sponsor Mantine development with OpenCollective. All funds are used to improve Mantine and create new features and components.
Mantine has migrated its linting and formatting toolchain from ESLint and Prettier to oxc – oxlint is now used as the linter and oxfmt as the formatter. Both tools are written in Rust and are significantly faster than their predecessors, which makes linting and formatting the entire codebase almost instant.
The shared configuration is available as a new oxc-config-mantine package (a replacement for the previous eslint-config-mantine). You can use it in your own projects to follow the same code style and conventions as Mantine.
DatePicker and all other date picker components (DatePickerInput, MonthPicker, YearPicker, DateTimePicker, etc.) now support the withNativeLevelSelect prop. When enabled, it replaces the calendar header level button with native <select> elements, making it easy to quickly navigate to a specific month and year.
import { DatePicker } from '@mantine/dates';
function Demo() {
return <DatePicker withNativeLevelSelect yearsSelectRange={[2020, 2035]} />;
}
Timeline Timeline.Item component now supports the opposite prop that allows rendering content on the opposite side of the timeline. When any item has the opposite prop, the timeline switches to a centered layout with content on both sides of the line.
import { Timeline, Text } from '@mantine/core';
import { GitBranchIcon, GitCommitIcon, GitPullRequestIcon, ChatCircleDotsIcon } from '@phosphor-icons/react';
function Demo() {
return (
<Timeline active={1} bulletSize={24} lineWidth={2}>
<Timeline.Item
bullet={<GitBranchIcon size={12} />}
title="New branch"
opposite={
<Text size="sm" c="dimmed">
2 hours ago
</Text>
}
>
<Text c="dimmed" size="sm">You've created new branch <Text variant="link" component="span" inherit>fix-notifications</Text> from master</Text>
</Timeline.Item>
<Timeline.Item
bullet={<GitCommitIcon size={12} />}
title="Commits"
opposite={
<Text size="sm" c="dimmed">
52 minutes ago
</Text>
}
>
<Text c="dimmed" size="sm">You've pushed 23 commits to <Text variant="link" component="span" inherit>fix-notifications branch</Text></Text>
</Timeline.Item>
<Timeline.Item
title="Pull request"
bullet={<GitPullRequestIcon size={12} />}
lineVariant="dashed"
opposite={
<Text size="sm" c="dimmed">
34 minutes ago
</Text>
}
>
<Text c="dimmed" size="sm">You've submitted a pull request <Text variant="link" component="span" inherit>Fix incorrect notification message (#187)</Text></Text>
</Timeline.Item>
<Timeline.Item title="Code review" bullet={<ChatCircleDotsIcon size={12} />}>
<Text c="dimmed" size="sm"><Text variant="link" component="span" inherit>Robert Gluesticker</Text> left a code review on your pull request</Text>
</Timeline.Item>
</Timeline>
);
}
Set the alternate prop on individual Timeline.Item components to switch the position of content and opposite:
import { Timeline, Text } from '@mantine/core';
import { GitBranchIcon, GitCommitIcon, GitPullRequestIcon, ChatCircleDotsIcon } from '@phosphor-icons/react';
function Demo() {
return (
<Timeline active={2} bulletSize={24} lineWidth={2}>
<Timeline.Item
bullet={<GitBranchIcon size={12} />}
title="New branch"
opposite={
<Text size="sm" c="dimmed">
2 hours ago
</Text>
}
>
<Text c="dimmed" size="sm">You've created new branch <Text variant="link" component="span" inherit>fix-notifications</Text> from master</Text>
</Timeline.Item>
<Timeline.Item
bullet={<GitCommitIcon size={12} />}
title="Commits"
opposite={
<Text size="sm" c="dimmed">
52 minutes ago
</Text>
}
alternate
>
<Text c="dimmed" size="sm">You've pushed 23 commits to <Text variant="link" component="span" inherit>fix-notifications branch</Text></Text>
</Timeline.Item>
<Timeline.Item
title="Pull request"
bullet={<GitPullRequestIcon size={12} />}
lineVariant="dashed"
opposite={
<Text size="sm" c="dimmed">
34 minutes ago
</Text>
}
>
<Text c="dimmed" size="sm">You've submitted a pull request <Text variant="link" component="span" inherit>Fix incorrect notification message (#187)</Text></Text>
</Timeline.Item>
<Timeline.Item
title="Code review"
bullet={<ChatCircleDotsIcon size={12} />}
opposite={
<Text size="sm" c="dimmed">
12 minutes ago
</Text>
}
alternate
>
<Text c="dimmed" size="sm"><Text variant="link" component="span" inherit>Robert Gluesticker</Text> left a code review on your pull request</Text>
</Timeline.Item>
</Timeline>
);
}
FloatingWindow now supports a ResizeHandle compound component that allows users to resize the floating window by dragging a handle element. Set the dimensions prop on FloatingWindow to control resize constraints for both width (initialWidth, minWidth, maxWidth) and height (initialHeight, minHeight, maxHeight).
The resize handle is fully accessible – it supports keyboard interaction with Arrow Left/Arrow Right keys for width, Arrow Up/Arrow Down for height (10px steps), and Home/End keys (jump to min/max size).
import { NotchesIcon } from '@phosphor-icons/react';
import { Button, CloseButton, FloatingWindow, Group, Text } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
function Demo() {
const [visible, handlers] = useDisclosure();
return (
<>
<Button onClick={handlers.toggle} variant="default">
{visible ? 'Hide' : 'Show'} floating window
</Button>
{visible && (
<FloatingWindow
withBorder
constrainOffset={40}
dimensions={{
initialWidth: 260,
maxWidth: 500,
minWidth: 180,
initialHeight: 260,
maxHeight: 400,
minHeight: 220,
}}
dragHandleSelector=".drag-handle"
excludeDragHandleSelector="button"
initialPosition={{ top: 300, left: 60 }}
style={{ overflow: 'hidden' }}
>
<Group
justify="space-between"
px="md"
py="sm"
className="drag-handle"
style={{ cursor: 'move' }}
>
<Text fw={500} fz="sm">
Resize demo
</Text>
<CloseButton onClick={handlers.close} />
</Group>
<Text fz="sm" px="md" pb="sm">
Drag the grip icon in the bottom-right corner to resize.
Use Arrow keys when the handle is focused:
Left/Right for width, Up/Down for height.
</Text>
<FloatingWindow.ResizeHandle
aria-label="Resize floating window"
style={{
position: 'absolute',
right: 0,
bottom: 0,
width: 20,
height: 20,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'nwse-resize',
}}
>
<NotchesIcon size={14} style={{ opacity: 0.5 }} />
</FloatingWindow.ResizeHandle>
</FloatingWindow>
)}
</>
);
}
New Cascader component allows selecting a value from hierarchical data by drilling down through cascading columns. Picking an option in one column reveals its children in a new column to the right, and the value is an ordered path from the root option to the selected node. It supports changeOnSelect, hover expand trigger, search, a flat list layout for mobile, and full keyboard navigation.
import { Cascader, useMatches } from '@mantine/core';
import { data } from './data';
function Demo() {
// Switch to a flat list on small screens
const withColumns = useMatches({ base: false, sm: true });
return (
<Cascader
withColumns={withColumns}
label="Location"
placeholder="Pick location"
data={data}
/>
);
}
New SunburstChart component displays hierarchical data as concentric rings, similar to a treemap plotted in polar coordinates.
// Demo.tsx
import { SunburstChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return <SunburstChart data={data} />;
}
// data.ts
export const data = [
{ name: 'Analytics', value: 100, color: 'pink.6' },
{
name: 'DevOps',
color: 'grape.6',
children: [
{ name: 'Docker', value: 80 },
{ name: 'Kubernetes', value: 50 },
],
},
{
name: 'Backend',
color: 'teal.6',
children: [
{ name: 'Node', value: 150 },
{
name: 'Python',
children: [
{ name: 'Django', value: 110 },
{ name: 'FastAPI', value: 60 },
],
},
{ name: 'Go', value: 50 },
],
},
{
name: 'Frontend',
color: 'blue.6',
children: [
{
name: 'React',
children: [
{
name: 'Frameworks',
children: [
{ name: 'Next.js', value: 150 },
{ name: 'Remix', value: 40 },
],
},
{ name: 'CRA', value: 20 },
],
},
{ name: 'Vue', value: 90 },
{ name: 'Svelte', value: 30 },
],
},
];
New BulletChart component displays a single measure against a qualitative range, useful for comparing a primary value (such as revenue) against a target and qualitative thresholds like poor, average, and good.
// Demo.tsx
import { BulletChart } from '@mantine/charts';
import { ranges } from './data';
function Demo() {
return (
<BulletChart
value={230000}
target={150000}
ranges={ranges}
valueFormatter={(value) => `${(value / 1000).toFixed(0)}k`}
/>
);
}
// data.ts
export const ranges = ${JSON.stringify(ranges, null, 2)};
All @mantine/charts components now expose the accessibilityLayer prop (true by default) that makes charts navigable with the keyboard. The chart surface is focusable and displays the Mantine focus ring; once focused, the arrow keys move the active tooltip point-by-point and Enter toggles the tooltip, so users who do not use a mouse can read the underlying values. The prop is supported by AreaChart, BarChart, LineChart, CompositeChart, ScatterChart, BubbleChart, PieChart, DonutChart, RadarChart, RadialBarChart and FunnelChart components.
AreaChart, BarChart, LineChart and CompositeChart now support the withBrush prop that displays a brush (range selector) under the chart. Drag the brush handles to zoom into a subset of the data. Use the brushProps prop to customize the underlying recharts Brush, or render the new themed ChartBrush component as a child of the chart for full control.
// Demo.tsx
import { AreaChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<AreaChart
h={300}
data={data}
dataKey="date"
withBrush
series={[
{ name: 'Apples', color: 'indigo.6' },
{ name: 'Oranges', color: 'blue.6' },
]}
/>
);
}
// data.ts
export const data = [
{ date: 'Mar 1', Apples: 2200, Oranges: 1400 },
{ date: 'Mar 2', Apples: 2500, Oranges: 1500 },
{ date: 'Mar 3', Apples: 2800, Oranges: 1700 },
{ date: 'Mar 4', Apples: 3100, Oranges: 1600 },
{ date: 'Mar 5', Apples: 3000, Oranges: 1800 },
{ date: 'Mar 6', Apples: 2700, Oranges: 2000 },
{ date: 'Mar 7', Apples: 2400, Oranges: 2100 },
{ date: 'Mar 8', Apples: 2100, Oranges: 1900 },
{ date: 'Mar 9', Apples: 1900, Oranges: 1700 },
{ date: 'Mar 10', Apples: 2200, Oranges: 1500 },
{ date: 'Mar 11', Apples: 2600, Oranges: 1600 },
{ date: 'Mar 12', Apples: 3000, Oranges: 1800 },
{ date: 'Mar 13', Apples: 3300, Oranges: 2000 },
{ date: 'Mar 14', Apples: 3100, Oranges: 2200 },
{ date: 'Mar 15', Apples: 2800, Oranges: 2100 },
{ date: 'Mar 16', Apples: 2500, Oranges: 1900 },
];
Heatmap now supports monthLabelsPosition prop that allows displaying month labels at the bottom of the heatmap instead of the top (default).
// 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
monthLabelsPosition="bottom"
/>
);
}
// data.ts
export const data = ${JSON.stringify(data, null, 2)};
Accordion now supports the disableCollapse prop. When enabled in single mode (multiple={false}), the open item cannot be collapsed by clicking it again, so one item always stays open. Pair it with defaultValue or value to guarantee that a section is open from the start – useful for settings panels, stepper-style flows and FAQ pages.
// Demo.tsx
import { Accordion } from '@mantine/core';
import { data } from './data';
function Demo() {
const items = data.map((item) => (
<Accordion.Item key={item.value} value={item.value}>
<Accordion.Control icon={item.emoji}>{item.value}</Accordion.Control>
<Accordion.Panel>{item.description}</Accordion.Panel>
</Accordion.Item>
));
return (
<Accordion defaultValue="Apples" disableCollapse order={3}>
{items}
</Accordion>
);
}
// data.ts
export const data = [
{
emoji: '🍎',
value: 'Apples',
description:
'Crisp and refreshing fruit. Apples are known for their versatility and nutritional benefits. They come in a variety of flavors and are great for snacking, baking, or adding to salads.',
},
{
emoji: '🍌',
value: 'Bananas',
description:
'Naturally sweet and potassium-rich fruit. Bananas are a popular choice for their energy-boosting properties and can be enjoyed as a quick snack, added to smoothies, or used in baking.',
},
{
emoji: '🥦',
value: 'Broccoli',
description:
'Nutrient-packed green vegetable. Broccoli is packed with vitamins, minerals, and fiber. It has a distinct flavor and can be enjoyed steamed, roasted, or added to stir-fries.',
},
];
ScrollArea now supports the verticalScrollbarPosition prop that pins the vertical scrollbar to a physical side (left or right) regardless of direction. This is useful for RTL applications where users expect the vertical scrollbar to stay on the right, matching the behavior of most desktop software. The prop also realigns the offset padding, the corner and the horizontal scrollbar gap, so it works correctly with offsetScrollbars and scrollbars="xy".
import { ScrollArea } from '@mantine/core';
function Demo() {
return (
<ScrollArea
w={300}
h={200}
type="always"
scrollbars="y"
verticalScrollbarPosition="right" offsetScrollbars={true}
>
{/* ... content */}
</ScrollArea>
);
}
ResourcesDayView and ResourcesWeekView now support intervalMinutes values larger than 60. Previously the interval was capped at one hour – you can now set it to any whole number of hours (for example 120 or 240) to display multi-hour time slots. Values of 60 or less must still divide evenly into an hour; any value that does not keep the grid on hour boundaries falls back to 60.
// Demo.tsx
import dayjs from 'dayjs';
import { useState } from 'react';
import { SegmentedControl, Stack } from '@mantine/core';
import { ResourcesDayView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
const [intervalMinutes, setIntervalMinutes] = useState('120');
return (
<Stack>
<SegmentedControl
value={intervalMinutes}
onChange={setIntervalMinutes}
data={[
{ label: '1 hour', value: '60' },
{ label: '2 hours', value: '120' },
{ label: '4 hours', value: '240' },
]}
/>
<ResourcesDayView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
intervalMinutes={Number(intervalMinutes)}
startScrollTime="08:00:00"
/>
</Stack>
);
}
// 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',
},
];
New Table example demonstrates how to use Table.ScrollContainer with @tanstack/react-virtual to efficiently render large datasets with 5,000 rows.
import { useState } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
import { Table } from '@mantine/core';
import { generateData } from './data';
const data = generateData(5000);
const ROW_HEIGHT = 36;
function Demo() {
const [scrollParent, setScrollParent] = useState<HTMLDivElement | null>(null);
const virtualizer = useVirtualizer({
count: data.length,
getScrollElement: () => scrollParent,
estimateSize: () => ROW_HEIGHT,
overscan: 20,
});
const virtualItems = virtualizer.getVirtualItems();
return (
<Table.ScrollContainer
minWidth={500}
maxHeight={400}
scrollAreaProps={{ viewportRef: setScrollParent }}
>
<Table stickyHeader layout="fixed">
<Table.Thead>
<Table.Tr>
<Table.Th w={50}>#</Table.Th>
<Table.Th>Name</Table.Th>
<Table.Th>Email</Table.Th>
<Table.Th>Company</Table.Th>
<Table.Th>City</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{virtualItems.length > 0 && (
<tr aria-hidden>
<td
aria-hidden
colSpan={5}
style={{ height: virtualItems[0].start, padding: 0, border: 'none' }}
/>
</tr>
)}
{virtualItems.map((virtualItem) => {
const row = data[virtualItem.index];
return (
<Table.Tr key={virtualItem.index}>
<Table.Td>{row.id}</Table.Td>
<Table.Td>{row.name}</Table.Td>
<Table.Td>{row.email}</Table.Td>
<Table.Td>{row.company}</Table.Td>
<Table.Td>{row.city}</Table.Td>
</Table.Tr>
);
})}
{virtualItems.length > 0 && (
<tr aria-hidden>
<td
aria-hidden
colSpan={5}
style={{
height:
virtualizer.getTotalSize() -
virtualItems[virtualItems.length - 1].end,
padding: 0,
border: 'none',
}}
/>
</tr>
)}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
);
}
9.4.2
[@mantine/schedule]Fix 1-3 minutes events not aligning correctly in ResourcesWeekView and ResourcesDayView (#9057)[@mantine/form]Fix async validation with debounce on initial keystroke of empty input (#9068)[@mantine/core]AddkeepMountedModeprop to Modal and Drawer components (#9056)[@mantine/core]Menubar: Fix menu requiring two clicks to dismiss after switching menus with hover (#9050)[@mantine/core]AppShell: Fixbreakpoint={0}not working correctly (#9042)[@mantine/core]Tree: Fix keyboard navigation not working on Safari (#9049)[@mantine/notifications]NotificationContainer: FixautoCloseleak (#9048)[@mantine/core]Collapse: AddkeepMountedModeprop (#9021)[@mantine/dates]TimePicker: Fix incorrectwithSecondshandling when pasting partial values (#9041)[@mantine/schedule]Add option to customize current time in ResourcesDayView and ResourcesWeekView components[@mantine/core]TreeSelect: Addexpandcallback torenderNodepayload[@mantine/core]Fix incorrect empty children arrays handling in Tree and TreeSelect[@mantine/hooks]Fix unstablescrollIntoViewfor some bundlers (#9035)[@mantine/schedule]AddintervalMinutesmore than 60 support to ResourcesDayView and ResourcesWeekView[@mantine/schedule]ResourcesWeekView: Fix incorrect handling of events with small duration[@mantine/schedule]ResourcesDayView: Fix events with small durations not being visible
- @saadpocalypse made their first contribution in https://github.com/mantinedev/mantine/pull/9021
- @Arman-Luthra made their first contribution in https://github.com/mantinedev/mantine/pull/9050
Full Changelog: https://github.com/mantinedev/mantine/compare/9.4.1...9.4.2
9.4.1
[@mantine/form]Fix some functions not working correctly with react compiler (#9007)[@mantine/charts]Heatmap: Fix values outside the provided domain rendering with no fill (#8982)[@mantine/core]RollingNumber: Fix rendering-0for values that round to zero (#8983)[@mantine/core]Slider: Fix incorrect marks labels position in RTL layouts (#8996)[@mantine/hooks]Fix use-set and use-map hooks using stale values when used with React compiler (#9008)[@mantine/form]AddFormProviderPropstype export (#9009)[@mantine/schedule]ResourcesDayView: Fix incorrect multiday events rendering (#9014)[@mantine/dates]TimePicker: Fix duration values greater than 9999 hours not working (#9002)[@mantine/core]Menu: Marknon-menuitemdropdown children as presentational (#9004)[@mantine/core]Collapse: Fixrefandstyleprops not working whentransitionDuration: 0(#9013)[@mantine/core]Textarea: Fix autosize not working correctly withminRowson initial render[@mantine/schedule]ResourcesWeekView: Add events resizing support
- @ayu3456 made their first contribution in https://github.com/mantinedev/mantine/pull/9013
- @Sanjays2402 made their first contribution in https://github.com/mantinedev/mantine/pull/9004
Full Changelog: https://github.com/mantinedev/mantine/compare/9.4.0...9.4.1
9.4.0 🥵
View changelog with demos on mantine.dev website
You can now sponsor Mantine development with OpenCollective. All funds are used to improve Mantine and create new features and components.
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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',
},
];
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',
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: \`\${tomorrow} 09:30:00\`,
end: \`\${tomorrow} 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: \`\${tomorrow} 11:00:00\`,
end: \`\${tomorrow} 11:30:00\`,
color: 'cyan',
resourceId: 'new-york',
},
{
id: 6,
title: 'Workshop',
start: \`\${dayAfter} 14:00:00\`,
end: \`\${dayAfter} 16:00:00\`,
color: 'pink',
resourceId: 'new-york',
},
{
id: 7,
title: 'Architecture Review',
start: \`\${tomorrow} 10:00:00\`,
end: \`\${tomorrow} 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',
},
{
id: 9,
title: 'Product Demo',
start: \`\${dayAfter} 09:00:00\`,
end: \`\${dayAfter} 10:00:00\`,
color: 'teal',
resourceId: 'tokyo',
},
{
id: 10,
title: 'Budget Review',
start: \`\${dayAfter2} 11:00:00\`,
end: \`\${dayAfter2} 12:30:00\`,
color: 'indigo',
resourceId: 'paris',
},
];
New ResourcesMonthView component displays resources as rows and days of the month as columns. Events are shown as colored indicators within each resource-day cell for easy visualization of resource utilization across the month.
// Demo.tsx
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesMonthView } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const [date, setDate] = useState(dayjs().format('YYYY-MM-DD'));
return (
<ResourcesMonthView
date={date}
onDateChange={setDate}
resources={resources}
events={events}
startScrollDate={dayjs().format('YYYY-MM-DD')}
/>
);
}
// 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 nextWeek = dayjs().add(5, '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' },
];
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: 'paris',
},
{
id: 3,
title: 'Design Review',
start: \`\${tomorrow} 13:00:00\`,
end: \`\${tomorrow} 14:00:00\`,
color: 'orange',
resourceId: 'tokyo',
},
{
id: 4,
title: 'Client Call',
start: \`\${tomorrow} 09:30:00\`,
end: \`\${tomorrow} 10:30:00\`,
color: 'violet',
resourceId: 'new-york',
},
{
id: 5,
title: 'Workshop',
start: \`\${nextWeek} 14:00:00\`,
end: \`\${nextWeek} 16:00:00\`,
color: 'pink',
resourceId: 'paris',
},
];
New ResourcesSchedule wrapper component combines ResourcesDayView, ResourcesWeekView and ResourcesMonthView into a single component with view switching, similar to how Schedule combines day, week, month and year views.
// Demo.tsx
import dayjs from 'dayjs';
import { useState } from 'react';
import { ResourcesSchedule } from '@mantine/schedule';
import { events, resources } from './data';
function Demo() {
const today = dayjs().format('YYYY-MM-DD');
const [date, setDate] = useState(today);
return (
<ResourcesSchedule
date={date}
onDateChange={setDate}
resources={resources}
events={events}
dayViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollTime: '08:00:00' }}
weekViewProps={{ startTime: '08:00:00', endTime: '18:00:00', startScrollDateTime: `${today} 08:00:00` }}
monthViewProps={{ startScrollDate: today }}
/>
);
}
// 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 nextWeek = dayjs().add(5, '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', 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: \`\${tomorrow} 13:00:00\`, end: \`\${tomorrow} 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: \`\${nextWeek} 14:00:00\`, end: \`\${nextWeek} 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: \`\${tomorrow} 15:00:00\`, end: \`\${tomorrow} 16:00:00\`, color: 'grape', resourceId: 'london' },
];
New AgendaView component renders a vertical list of events for a specified time period. Events are grouped by date in chronological order.
import dayjs from 'dayjs';
import { AgendaView } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const startOfMonth = dayjs().startOf('month').format('YYYY-MM-DD');
const events = [
{
id: 'team-meeting',
title: 'Team Meeting',
start: `${startOfMonth} 09:00:00`,
end: `${startOfMonth} 10:30:00`,
color: 'blue',
},
{
id: 'client-call',
title: 'Client Call',
start: `${today} 14:00:00`,
end: `${today} 15:00:00`,
color: 'green',
},
{
id: 'daily-sync-series',
title: 'Daily sync',
start: `${startOfMonth} 09:30:00`,
end: `${startOfMonth} 10:00:00`,
color: 'grape',
recurrence: {
rrule: 'FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR;COUNT=30',
},
},
{
id: 'weekly-review-series',
title: 'Weekly review',
start: `${startOfMonth} 16:00:00`,
end: `${startOfMonth} 17:00:00`,
color: 'cyan',
recurrence: {
rrule: 'FREQ=WEEKLY;COUNT=8',
},
},
];
function Demo() {
return (
<AgendaView
rangeStart={dayjs().startOf('month').format('YYYY-MM-DD')}
rangeEnd={dayjs().endOf('month').format('YYYY-MM-DD')}
events={events}
/>
);
}
DayView, WeekView, MonthView and Schedule components now support withAgenda prop. When enabled, an "Agenda" button is displayed in the header. Clicking it opens an AgendaView for the currently visible date range.
import dayjs from 'dayjs';
import { useState } from 'react';
import { MonthView } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const startOfMonth = dayjs().startOf('month').format('YYYY-MM-DD');
const events = [
{
id: 'team-meeting',
title: 'Team Meeting',
start: `${startOfMonth} 09:00:00`,
end: `${startOfMonth} 10:30:00`,
color: 'blue',
},
{
id: 'client-call',
title: 'Client Call',
start: `${today} 14:00:00`,
end: `${today} 15:00:00`,
color: 'green',
},
{
id: 'daily-sync-series',
title: 'Daily sync',
start: `${startOfMonth} 09:30:00`,
end: `${startOfMonth} 10:00:00`,
color: 'grape',
recurrence: {
rrule: 'FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR;COUNT=30',
},
},
{
id: 'weekly-review-series',
title: 'Weekly review',
start: `${startOfMonth} 16:00:00`,
end: `${startOfMonth} 17:00:00`,
color: 'orange',
recurrence: {
rrule: 'FREQ=WEEKLY;COUNT=8',
},
},
];
function Demo() {
const [date, setDate] = useState(today);
return (
<MonthView
date={date}
onDateChange={setDate}
events={events}
withAgenda
/>
);
}
MonthView now supports withWeekendDays prop. Set it to false to hide weekend days: the grid shrinks to the remaining columns and events that span hidden days are clipped to the visible days. The days that are considered weekend are controlled by the weekendDays prop (or DatesProvider, [0, 6] by default).
// Demo.tsx
import { MonthView } from '@mantine/schedule';
import { events } from './data';
function Demo() {
return <MonthView date={new Date()} events={events} withWeekendDays={false} />;
}
// data.ts
import dayjs from 'dayjs';
const today = dayjs().format('YYYY-MM-DD');
const startOfMonth = dayjs().startOf('month').format('YYYY-MM-DD');
const midMonth = dayjs().date(15).format('YYYY-MM-DD');
const endOfMonth = dayjs().endOf('month').format('YYYY-MM-DD');
export const events = [
{
id: 1,
title: 'Team Meeting',
start: \`\${startOfMonth} 09:00:00\`,
end: \`\${startOfMonth} 10:30:00\`,
color: 'blue',
},
{
id: 2,
title: 'Project Deadline',
start: \`\${midMonth} 00:00:00\`,
end: dayjs(midMonth).add(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
color: 'red',
},
{
id: 3,
title: 'Client Call',
start: \`\${today} 14:00:00\`,
end: \`\${today} 15:00:00\`,
color: 'green',
},
{
id: 4,
title: 'Monthly Review',
start: \`\${endOfMonth} 00:00:00\`,
end: dayjs(endOfMonth).add(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
color: 'violet',
},
{
id: 5,
title: 'Workshop',
start: dayjs().add(3, 'day').format('YYYY-MM-DD 10:00:00'),
end: dayjs().add(3, 'day').format('YYYY-MM-DD 12:00:00'),
color: 'orange',
},
{
id: 6,
title: 'Conference',
start: dayjs().add(5, 'day').format('YYYY-MM-DD 00:00:00'),
end: dayjs().add(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
color: 'cyan',
},
];
DayView and WeekView now support withSubHourGridLines prop. When intervalMinutes is smaller than 60, set withSubHourGridLines={false} to display only one grid line per hour while keeping the smaller interval for creating and resizing events. This is useful to achieve a Google Calendar like layout: events snap to 15 or 30 minutes increments, but the grid stays clean with hourly lines.
import { useState } from 'react';
import dayjs from 'dayjs';
import { WeekView, ScheduleEventData } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const tomorrow = dayjs().add(1, 'day').format('YYYY-MM-DD');
const initialEvents: ScheduleEventData[] = [
{
id: 1,
title: 'Morning Standup',
start: `${today} 09:00:00`,
end: `${today} 09:30:00`,
color: 'blue',
},
{
id: 2,
title: 'Team Meeting',
start: `${tomorrow} 11:15:00`,
end: `${tomorrow} 12:00:00`,
color: 'green',
},
{
id: 3,
title: 'Code Review',
start: `${today} 14:00:00`,
end: `${today} 14:45:00`,
color: 'violet',
},
];
function Demo() {
const [events, setEvents] = useState(initialEvents);
const handleEventResize = ({ eventId, newStart, newEnd }: { eventId: string | number; newStart: string; newEnd: string }) => {
setEvents((prev) =>
prev.map((event) =>
event.id === eventId ? { ...event, start: newStart, end: newEnd } : event
)
);
};
// Events snap to 15 minutes increments, but only one grid line per hour is displayed
return (
<WeekView
date={new Date()}
events={events}
startTime="08:00:00"
endTime="18:00:00"
intervalMinutes={15}
withSubHourGridLines
withEventResize
onEventResize={handleEventResize}
/>
);
}
All inputs based on Input and Input.Wrapper now support a success prop. When set, it changes the input border color to green (--mantine-color-success). You can also pass a React node to display a success message below the input. If both error and success props are set, error takes precedence.
New --mantine-color-success CSS variable has been added (resolves to teal-6 in light mode and teal-8 in dark mode).
import { TextInput } from '@mantine/core';
function Demo() {
return (
<>
<TextInput placeholder="Success as boolean" label="Success as boolean" success />
<TextInput
mt="md"
placeholder="Success as react node"
label="Success as react node"
success="Username is available"
/>
</>
);
}
Splitter.Pane defaultSize, min and max props now accept CSS units in addition to plain numbers. A plain number or % string is a flexible size that shares the leftover space, while a px or rem string is a fixed size that keeps its pixel size when the container is resized. This makes it possible to mix a fixed-width sidebar with a fluid content pane:
import { Splitter } from '@mantine/core';
function Demo() {
return (
<Splitter h={200}>
<Splitter.Pane defaultSize="240px" min="160px" max="50%" bg="blue">
Fixed 240px sidebar
</Splitter.Pane>
<Splitter.Pane defaultSize={100} bg="teal">
Flexible content
</Splitter.Pane>
</Splitter>
);
}
Notifications now support a priority property. When the number of active notifications exceeds the limit, notifications with a higher priority take the visible slots and lower priority ones are pushed into the queue. Notifications with equal priority keep insertion order (FIFO), so the default behavior is unchanged (priority defaults to 0).
import { Button, Group } from '@mantine/core';
import { createNotificationsStore, notifications, Notifications } from '@mantine/notifications';
// Dedicated store with limit={1} so the priority behavior is easy to see
const store = createNotificationsStore();
function Demo() {
return (
<>
<Notifications store={store} limit={1} position="top-center" />
<Group justify="center">
<Button
color="gray"
onClick={() =>
notifications.show(
{
title: 'Low priority',
message: 'I am pushed to the queue when an urgent notification arrives',
autoClose: false,
priority: 0,
},
store
)
}
>
Show low priority
</Button>
<Button
color="red"
onClick={() =>
notifications.show(
{
title: 'High priority',
message: 'I take the visible slot even when the limit is reached',
color: 'red',
autoClose: false,
priority: 10,
},
store
)
}
>
Show high priority
</Button>
</Group>
</>
);
}
PieChart, DonutChart and FunnelChart now support the withLegend prop. When enabled, a legend with the name and color of each segment is displayed. Hovering over a legend item highlights the corresponding segment. Use the legendProps prop to pass props down to the underlying recharts Legend component.
// Demo.tsx
import { PieChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return <PieChart data={data} withLegend />;
}
// data.ts
export const data = [
{ name: 'USA', value: 400, color: 'indigo.6' },
{ name: 'India', value: 300, color: 'yellow.6' },
{ name: 'Japan', value: 300, color: 'teal.6' },
{ name: 'Other', value: 200, color: 'gray.6' },
];
Virtual colors now support autoContrast. Previously, filled components with a virtual color always used white text because the underlying color could not be resolved on the JavaScript side. The contrast color is now calculated separately for each color scheme based on the resolved background color, so text stays readable when the virtual color resolves to a light color in one scheme and a dark color in another. Switch between light and dark color schemes (Ctrl + J) to see the text color adjust:
// App.tsx
import { Button, colorsTuple, createTheme, MantineProvider, virtualColor } from '@mantine/core';
import { Demo } from './Demo';
const theme = createTheme({
colors: {
white: colorsTuple('#FFFFFF'),
black: colorsTuple('#000000'),
adaptive: virtualColor({
name: 'adaptive',
dark: 'white',
light: 'black',
}),
},
});
function App() {
return (
<MantineProvider theme={theme}>
<Demo />
</MantineProvider>
);
}
// Demo.tsx
import { Button } from '@mantine/core';
export function Demo() {
return (
<Button color="adaptive" autoContrast>
Button
</Button>
);
}
- Splitter now supports
resetOnDoubleClickprop. When enabled, double-clicking a resize handle restores the adjacent panes to their default ratio. - TimePicker now supports
closeDropdownOnPresetSelectprop. When set, the dropdown is closed once a value is selected from the presets list. - useLongPress hook now handles
onTouchCancelevents: the returned handlers include anonTouchCancelcallback so that a long press is correctly canceled when the touch is interrupted by the system (incoming call, browser gesture, etc.).
9.3.2
[@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)
- @SyntaxHQDEV made their first contribution in https://github.com/mantinedev/mantine/pull/8966
- @bensaufley made their first contribution in https://github.com/mantinedev/mantine/pull/8978
Full Changelog: https://github.com/mantinedev/mantine/compare/9.3.1...9.3.2
9.3.1
[@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)
- @spokodev made their first contribution in https://github.com/mantinedev/mantine/pull/8925
- @cyphercodes made their first contribution in https://github.com/mantinedev/mantine/pull/8947
- @KasperiP made their first contribution in https://github.com/mantinedev/mantine/pull/8939
Full Changelog: https://github.com/mantinedev/mantine/compare/9.3.0...9.3.1
9.3.0 🥵
View changelog with demos on mantine.dev website
You can now sponsor Mantine development with OpenCollective. All funds are used to improve Mantine and create new features and components.
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 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>
);
}
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>
);
}
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 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 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 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, 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(--combobox-floating-options-max-height)" type="scroll">
{options}
</ScrollArea.Autosize>
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}
Menu submenus now use a safe polygon when moving the cursor from the parent item to the dropdown. This allows you to move the cursor diagonally across other menu items without accidentally closing the submenu.
import { Button, Menu } from '@mantine/core';
function Demo() {
return (
<Menu width={200} position="bottom-start">
<Menu.Target>
<Button>Toggle Menu</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item>Dashboard</Menu.Item>
<Menu.Sub openDelay={120} closeDelay={150}>
<Menu.Sub.Target>
<Menu.Sub.Item>Products</Menu.Sub.Item>
</Menu.Sub.Target>
<Menu.Sub.Dropdown>
<Menu.Item>All products</Menu.Item>
<Menu.Item>Categories</Menu.Item>
<Menu.Item>Tags</Menu.Item>
<Menu.Item>Attributes</Menu.Item>
<Menu.Item>Shipping classes</Menu.Item>
</Menu.Sub.Dropdown>
</Menu.Sub>
<Menu.Item>Customers</Menu.Item>
<Menu.Item>Reports</Menu.Item>
<Menu.Sub>
<Menu.Sub.Target>
<Menu.Sub.Item>Orders</Menu.Sub.Item>
</Menu.Sub.Target>
<Menu.Sub.Dropdown>
<Menu.Item>Open</Menu.Item>
<Menu.Item>Completed</Menu.Item>
<Menu.Item>Cancelled</Menu.Item>
</Menu.Sub.Dropdown>
</Menu.Sub>
<Menu.Sub>
<Menu.Sub.Target>
<Menu.Sub.Item>Settings</Menu.Sub.Item>
</Menu.Sub.Target>
<Menu.Sub.Dropdown>
<Menu.Item>Profile</Menu.Item>
<Menu.Item>Security</Menu.Item>
<Menu.Item>Notifications</Menu.Item>
</Menu.Sub.Dropdown>
</Menu.Sub>
</Menu.Dropdown>
</Menu>
);
}
Menu now supports Menu.Search – a search input that filters items without taking focus away from the input. Use ArrowUp/ArrowDown to move the highlight, Enter to trigger the highlighted item. Filtering logic is controlled by the user: pass value/onChange and filter Menu.Item children based on the query. The search value is cleared automatically after the menu close transition completes; set clearSearchOnClose={false} to keep the query between openings.
import { useState } from 'react';
import { Button, Menu, Text } from '@mantine/core';
const data = [
'Dashboard',
'Customers',
'Products',
'Orders',
'Reports',
'Settings',
'Integrations',
'Billing',
'Team members',
'Help center',
];
function Demo() {
const [query, setQuery] = useState('');
const items = data.filter((item) => item.toLowerCase().includes(query.toLowerCase().trim()));
return (
<Menu shadow="md" width={240}>
<Menu.Target>
<Button>Toggle menu</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Search
value={query}
onChange={(event) => setQuery(event.currentTarget.value)}
placeholder="Search items"
/>
{items.length > 0 ? (
items.map((item) => <Menu.Item key={item}>{item}</Menu.Item>)
) : (
<Text c="dimmed" size="sm" ta="center" py="xs">
Nothing found
</Text>
)}
</Menu.Dropdown>
</Menu>
);
}
Menu now supports Menu.CheckboxItem, Menu.RadioItem, and Menu.RadioGroup for building option menus. Checkbox and radio items render an indicator slot at the start and do not close the menu on click by default. The new alignItemsLabels prop on Menu controls how indicator slot space is reserved so labels stay aligned when mixing plain and indicator items.
import { useState } from 'react';
import { Button, Menu } from '@mantine/core';
function Demo() {
const [columns, setColumns] = useState({
name: true,
email: true,
role: false,
lastSeen: false,
});
const setColumn = (key: keyof typeof columns) => (checked: boolean) =>
setColumns((current) => ({ ...current, [key]: checked }));
return (
<Menu shadow="md" width={220} closeOnItemClick={false}>
<Menu.Target>
<Button>Columns</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Visible columns</Menu.Label>
<Menu.CheckboxItem checked={columns.name} onChange={setColumn('name')}>
Name
</Menu.CheckboxItem>
<Menu.CheckboxItem checked={columns.email} onChange={setColumn('email')}>
Email
</Menu.CheckboxItem>
<Menu.CheckboxItem checked={columns.role} onChange={setColumn('role')}>
Role
</Menu.CheckboxItem>
<Menu.CheckboxItem checked={columns.lastSeen} onChange={setColumn('lastSeen')}>
Last seen
</Menu.CheckboxItem>
</Menu.Dropdown>
</Menu>
);
}
import { useState } from 'react';
import { Button, Menu } from '@mantine/core';
function Demo() {
const [sort, setSort] = useState('newest');
return (
<Menu shadow="md" width={220} closeOnItemClick={false}>
<Menu.Target>
<Button>Sort by</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Order</Menu.Label>
<Menu.RadioGroup value={sort} onChange={setSort}>
<Menu.RadioItem value="newest">Newest first</Menu.RadioItem>
<Menu.RadioItem value="oldest">Oldest first</Menu.RadioItem>
<Menu.RadioItem value="popular">Most popular</Menu.RadioItem>
<Menu.RadioItem value="commented">Most commented</Menu.RadioItem>
</Menu.RadioGroup>
</Menu.Dropdown>
</Menu>
);
}
Menu now supports Menu.ContextMenu – a target replacement that opens the dropdown at the cursor position when the wrapped element is right-clicked. The browser's default context menu is suppressed, and right-clicking again repositions the dropdown to the new coordinates.
import { Menu, Paper, Text } from '@mantine/core';
function Demo() {
return (
<Menu shadow="md" width={200}>
<Menu.ContextMenu>
<Paper withBorder p="xl" radius="md" style={{ userSelect: 'none', textAlign: 'center' }}>
<Text fw={500}>Right-click anywhere inside this area</Text>
<Text c="dimmed" size="sm" mt={4}>
The menu will open at the cursor position
</Text>
</Paper>
</Menu.ContextMenu>
<Menu.Dropdown>
<Menu.Label>Actions</Menu.Label>
<Menu.Item>Open</Menu.Item>
<Menu.Item>Rename</Menu.Item>
<Menu.Item>Duplicate</Menu.Item>
<Menu.Divider />
<Menu.Item color="red">Delete</Menu.Item>
</Menu.Dropdown>
</Menu>
);
}
Popover now supports Popover.ContextMenu – a target replacement that opens the dropdown at the cursor position on right-click. Unlike Menu.ContextMenu, Popover.Dropdown can contain any content.
import { Avatar, Button, Group, Paper, Popover, Stack, Text } from '@mantine/core';
function Demo() {
return (
<Popover width={260} shadow="md" position="bottom-start" offset={0}>
<Popover.ContextMenu>
<Paper withBorder p="xl" radius="md" style={{ userSelect: 'none', textAlign: 'center' }}>
<Text fw={500}>Right-click anywhere inside this area</Text>
<Text c="dimmed" size="sm" mt={4}>
A popover will open at the cursor position
</Text>
</Paper>
</Popover.ContextMenu>
<Popover.Dropdown>
<Stack gap="xs">
<Group gap="sm" wrap="nowrap">
<Avatar radius="xl" color="blue">JD</Avatar>
<div>
<Text size="sm" fw={500}>Jane Doe</Text>
<Text size="xs" c="dimmed">jane@example.com</Text>
</div>
</Group>
<Group grow gap="xs">
<Button size="xs" variant="default">Message</Button>
<Button size="xs">Follow</Button>
</Group>
</Stack>
</Popover.Dropdown>
</Popover>
);
}
When focus is inside Menu dropdown (and Menu.Search is not used), pressing a printable character key now moves focus to the next item whose label starts with the typed character. Pressing the same character cycles through matches, and multiple characters typed within 500ms match items by full prefix.
import { Menu, Button, Text } from '@mantine/core';
import { GearSixIcon, MagnifyingGlassIcon, ImageIcon, ChatCircleIcon, TrashIcon, IconArrowsLeftRight } from '@phosphor-icons/react';
function Demo() {
return (
<Menu shadow="md" width={200}>
<Menu.Target>
<Button>Toggle menu</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Application</Menu.Label>
<Menu.Item leftSection={<GearSixIcon size={14} />}>
Settings
</Menu.Item>
<Menu.Item leftSection={<ChatCircleIcon size={14} />}>
Messages
</Menu.Item>
<Menu.Item leftSection={<ImageIcon size={14} />}>
Gallery
</Menu.Item>
<Menu.Item
leftSection={<MagnifyingGlassIcon size={14} />}
rightSection={
<Text size="xs" c="dimmed">
⌘K
</Text>
}
>
Search
</Menu.Item>
<Menu.Divider />
<Menu.Label>Danger zone</Menu.Label>
<Menu.Item
leftSection={<IconArrowsLeftRight size={14} />}
>
Transfer my data
</Menu.Item>
<Menu.Item
color="red"
leftSection={<TrashIcon size={14} />}
>
Delete my account
</Menu.Item>
</Menu.Dropdown>
</Menu>
);
}
Highlight component now supports caseInsensitive and accentInsensitive props. Both are enabled by default – matching is case-insensitive and accent-insensitive, so cafe matches café, CAFÉ, etc. Set either prop to false to opt out:
import { Highlight, Stack, Text } from '@mantine/core';
function Demo() {
return (
<Stack gap="md">
<div>
<Text size="sm" fw={500} mb={5}>
With accent-insensitive matching (default)
</Text>
<Highlight highlight="cafe">We visited café and cafe.</Highlight>
</div>
<div>
<Text size="sm" fw={500} mb={5}>
{'With accent-sensitive matching (accentInsensitive={false})'}
</Text>
<Highlight highlight="cafe" accentInsensitive={false}>
We visited café and cafe.
</Highlight>
</div>
</Stack>
);
}
PieChart and DonutChart components now support labelsType="name" to display segment names as labels instead of values or percentages:
// Demo.tsx
import { PieChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return <PieChart data={data} withLabelsLine labelsPosition="outside" labelsType="name" withLabels />;
}
// data.ts
export const data = [
{ name: 'USA', value: 400, color: 'indigo.6' },
{ name: 'India', value: 300, color: 'yellow.6' },
{ name: 'Japan', value: 300, color: 'teal.6' },
{ name: 'Other', value: 200, color: 'gray.6' },
];
Tooltip, Popover and other components based on Popover now support arrowPosition="merge". When set, the arrow forms a right triangle merged with the corresponding corner of the dropdown, and the border radius of that corner is removed.
import { Tooltip, Button } from '@mantine/core';
function Demo() {
return (
<Tooltip arrowPosition="center" arrowOffset={10} arrowSize={4} arrowRadius={0} label="Tooltip" withArrow opened position="top-start">
<Button>Button with tooltip</Button>
</Tooltip>
);
}
Popover and all components built on top of it (Combobox, Select, MultiSelect, Autocomplete, TagsInput, Menu, HoverCard, Tooltip, ColorInput, date pickers and others) now treat preventPositionChangeWhenVisible as true by default.
With this behavior, the dropdown picks its side once on open – respecting the position prop when there is enough room – and then stays on that side until the dropdown is closed. Scrolling, resizing, or changes to the dropdown content (for example narrowing a searchable Select) no longer cause the dropdown to flip between top and bottom while it is open. The next open recalculates the side from scratch.
The flip is also more predictable: when neither side fits, the dropdown falls back to the preferred position prop instead of the side with marginally more space.
To restore the previous behavior – where the dropdown could re-flip while open whenever available space changed – pass preventPositionChangeWhenVisible={false}:
<Select
comboboxProps={{ preventPositionChangeWhenVisible: false }}
data={['React', 'Angular', 'Svelte']}
/>
DayView and WeekView components now support getCurrentTime prop – a function that returns the current time used by the current time indicator. It is called on every tick, so the indicator keeps updating automatically. Combined with the timezone-agnostic event strings, this allows displaying the indicator in any timezone without re-implementing the update loop:
// Demo.tsx
import { useState } from 'react';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import { Select, Stack } from '@mantine/core';
import { DayView } from '@mantine/schedule';
import { getEvents } from './data';
dayjs.extend(utc);
dayjs.extend(timezone);
const timezones = ['UTC', 'America/New_York', 'Europe/Berlin', 'Asia/Kolkata', 'Asia/Tokyo'];
function Demo() {
const [tz, setTz] = useState('UTC');
// getCurrentTime is called on every tick, so the indicator keeps updating
const getCurrentTime = () => dayjs().tz(tz).format('YYYY-MM-DD HH:mm:ss');
const currentDate = getCurrentTime().split(' ')[0];
return (
<Stack>
<Select
label="Display timezone"
data={timezones}
value={tz}
onChange={(value) => setTz(value!)}
allowDeselect={false}
/>
<DayView
date={currentDate}
events={getEvents(currentDate)}
getCurrentTime={getCurrentTime}
startScrollTime={dayjs(getCurrentTime()).subtract(2, 'hour').format('HH:mm:ss')}
withCurrentTimeIndicator
withCurrentTimeBubble
/>
</Stack>
);
}
// data.ts
import dayjs from 'dayjs';
import { ScheduleEventData } from '@mantine/schedule';
export function getEvents(date: string): ScheduleEventData[] {
return [
{ id: 1, title: 'Morning standup', start: `${date} 09:00:00`, end: `${date} 09:30:00`, color: 'blue' },
{ id: 2, title: 'Team meeting', start: `${date} 12:00:00`, end: `${date} 13:00:00`, color: 'teal' },
{ id: 3, title: 'Code review', start: `${date} 16:00:00`, end: `${date} 17:00:00`, color: 'grape' },
];
}
9.2.2
[@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)
- @chbaefront made their first contribution in https://github.com/mantinedev/mantine/pull/8908
- @krusche made their first contribution in https://github.com/mantinedev/mantine/pull/8900
- @hyeongjun6364 made their first contribution in https://github.com/mantinedev/mantine/pull/8901
- @Israadaassi1 made their first contribution in https://github.com/mantinedev/mantine/pull/8921
Full Changelog: https://github.com/mantinedev/mantine/compare/9.2.1...9.2.2