A fully featured React components library
MIT License
Bot releases are visible (Hide)
[@mantine/hooks]
use-idle: Fix idle countdown not starting if the user did non interact with the page (#6683)[@mantine/core]
ScrollArea: Fix onBottomReached
prop not being available in ScrollArea.Autosize
component[@mantine/core]
Remove children
from Checkbox, Radio and Switch types to avoid accidental errors[@mantine/core]
TypographyStylesProvider: Fix incorrect table styles in dark color scheme[@mantine/form]
Fix error thrown for nullable values dirty status check (#6672)[@mantine/core]
Badge: Fix unexpected change to block layout, fix incorrect alignment when fixed width is set (#6698, #6680)[@mantine/core]
ScrollArea: Fix pointer-events being left as none
after interaction with scrollbar (#6681)[@mantine/core]
Tabs: Fix keepMounted
prop being added as attribute to Tabs.Panel
DOM element (#6711)[@mantine/core]
Tree: Add initialCheckedState
support (#6697)[@mantine/spotlight]
Fix SpotlightRoot
component not being exported (#6710)[@mantine/dropzone]
Add 7z
and rar
mime types exports (#6702)[@mantine/dates]
DatePickerInput: Fix incorrect hovered date logic when the component receives value update with partial selected date range (#6718)[@mantine/dates]
Fix valueFormatter
prop being added to DateTimePicker types[@mantine/core]
Badge: Fix right/left sections height affecting the alignment of the label[@mantine/core]
Menu: Fix accessibility warning in devtools when the Menu is opened (#6644)Full Changelog: https://github.com/mantinedev/mantine/compare/7.12.1...7.12.2
Published by rtivital 2 months ago
[@mantine/dates]
DateInput: Fix default date being set to the current date when minDate
is set to the future (#6646)[@mantine/core]
ScrollArea: Fix incorrect thumb::before styles[@mantine/core]
Fix incorrect active styles of buttons used inside disabled fieldset[@mantine/form]
Fix form.watch
callbacks not being fired when form.initialize
is called (#6639)[@mantine/core]
Switch: Fix Switch shrinking when large label or description is used (#6531)[@mantine/core]
Combobox: Fix Combobox.Search
overflow when ScrollArea
is used in the dropdown (#6562)[@mantine/core]
Accordion: Add missing withProps
function (#6564)[@mantine/core]
Pill: Fix remove icon overflowing pill container if its background color was changed with Styles API (#6565)[@mantine/core]
PinInput: Allow passing props to individual input elements depending on index with getInputProps
(#6588)[@mantine/charts]
: Fix LineChart Legend and Tooltip to support nested names (#6536)[@mantine/core]
Tooltip: Add missing Tooltip.Group.extend
function (#6576)[@mantine/spotlight]
Fix limit
prop not working correctly with actions groups (#6632)[@mantine/core]
Badge: Fix text overflow not being handled correctly (#6629)[@mantine/core]
SegmentedControl: Add data-disabled
attribute to the root element to simplify styling with Styles API (#6625)[@mantine/core]
SegmentedControl: Fix initial position of indicator being broken when the component is used inside other element that has transitions on mount (#6622)[@mantine/core]
TagsInput: Fix onKeyDown
prop not working (#6569)[@mantine/charts]
PieChart: Fix valueFormatter
not working on outside labels (#6616)[@mantine/core]
Popover: Fix apply
function of size
middleware not being handled correctly (#6598)[@mantine/core]
Chip: Fix incorrect checked padding for size="xl"
(#6586)[@mantine/dates]
TimeInput: Fix incorrect focus styles of am/pm input (#6579)[@mantine/hook]
use-os: Fix incorrect iPadOS detection (#6535)[@mantine/core]
DatePickerInput: Fix incorrect aria-label
being set on the input element (#6530)[@mantine/core]
Menu: Fix incorrect Escape key handling inside Modal (#6580)Full Changelog: https://github.com/mantinedev/mantine/compare/7.12.0...7.12.1
Published by rtivital 3 months ago
View changelog with demos on mantine.dev website
It is now possible to display notifications at any position on the screen
with @mantine/notifications package:
import { Button } from '@mantine/core';
import { notifications } from '@mantine/notifications';
const positions = [
'top-left',
'top-right',
'bottom-left',
'bottom-right',
'top-center',
'bottom-center',
] as const;
function Demo() {
const buttons = positions.map((position) => (
<Button
key={position}
onClick={() =>
notifications.show({
title: `Notification at ${position}`,
message: `Notification at ${position} message`,
position,
})
}
>
{position}
</Button>
));
return <Group>{buttons}</Group>;
}
You can now subscribe to notifications state changes with useNotifications
hook:
function Demo() {
const [counter, { increment }] = useCounter();
const notificationsStore = useNotifications();
const showNotification = () => {
notifications.show({
title: `Notification ${counter}`,
message: 'Most notifications are added to queue',
});
increment();
};
return (
<>
<Button onClick={showNotification} mb="md">
Show notification
</Button>
<Text>Notifications state</Text>
<Code block>{JSON.stringify(notificationsStore.notifications, null, 2)}</Code>
<Text mt="md">Notifications queue</Text>
<Code block>{JSON.stringify(notificationsStore.queue, null, 2)}</Code>
</>
);
}
New SemiCircleProgress component:
import { SemiCircleProgress } from '@mantine/core';
function Demo() {
return (
<SemiCircleProgress
fillDirection="left-to-right"
orientation="up"
filledSegmentColor="blue"
size={200}
thickness={12}
value={40}
label="Label"
/>
);
}
Tree component now supports checked state:
import { IconChevronDown } from '@tabler/icons-react';
import { Checkbox, Group, RenderTreeNodePayload, Tree } from '@mantine/core';
import { data } from './data';
const renderTreeNode = ({
node,
expanded,
hasChildren,
elementProps,
tree,
}: RenderTreeNodePayload) => {
const checked = tree.isNodeChecked(node.value);
const indeterminate = tree.isNodeIndeterminate(node.value);
return (
<Group gap="xs" {...elementProps}>
<Checkbox.Indicator
checked={checked}
indeterminate={indeterminate}
onClick={() => (!checked ? tree.checkNode(node.value) : tree.uncheckNode(node.value))}
/>
<Group gap={5} onClick={() => tree.toggleExpanded(node.value)}>
<span>{node.label}</span>
{hasChildren && (
<IconChevronDown
size={14}
style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)' }}
/>
)}
</Group>
</Group>
);
};
function Demo() {
return <Tree data={data} levelOffset={23} expandOnClick={false} renderNode={renderTreeNode} />;
}
You can now disable specific features of the postcss-preset-mantine
by setting them to false
in the configuration object. This feature is available starting from
[email protected]
.
module.exports = {
'postcss-preset-mantine': {
features: {
// Turn off `light-dark` function
lightDarkFunction: false,
// Turn off `postcss-nested` plugin
nested: false,
// Turn off `lighten`, `darken` and `alpha` functions
colorMixAlpha: false,
// Turn off `rem` and `em` functions
remEmFunctions: false,
// Turn off `postcss-mixins` plugin
mixins: false,
},
},
};
Component.extend
usage in server components.input
selector is not used for actual input element.Text
component.autoInvoke
option to start the interval automatically when the component mounts.mode="uncontrolled"
now triggers additional rerender when dirty state changes to allow subscribing to form state changes.onTopReached
and onBottomReached
props. The functions are called when the user scrolls to the top or bottom of the scroll area.onTransitionEnd
prop that is called when the panel animation completes.Published by rtivital 3 months ago
[@mantine/core]
Combobox: Fix inconsistent horizontal dropdown padding[@mantine/core]
Drawer: Fix content overflowing horizontally on mobile when offset
is set[@mantine/core]
Drawer: Fix double scrollbar appearing when offset
and scrollAreaComponent
props are set[@mantine/carousel]
Fix responsive slideSize
values working differently from other style props[@mantine/hooks]
use-interval: Add autoInvoke
option support[@mantine/hooks]
use-interval: Fix updates to the function and interval timeout being ignored[@mantine/core]
Anchor: Fix lineClamp
prop not working[@mantine/core]
Anchor: Fix text-decoration styles being inconsistent with variant="gradient"
[@mantine/dates]
DateInput: Fix value flickering with custom timezone (#6517)[@mantine/core]
Burger: Fix lineSize
being passed to the DOM node (#6520)[@mantine/charts]
Add support for nested properties in dataKey
(#5886)[@mantine/core]
Fix Modal/Drawer headers overlaying custom scrollbar (#6175)[@mantine/charts]
Sparkline: Fix incorrect data
prop type (#6352)[@mantine/charts]
Fix strokeColor
prop being passed to the DOM element (#6507)[@mantine/core]
FocusTrap: Improve compatibility with React 19 (#6492)[@mantine/hooks]
use-os: Fix iOS being reported as MacOS in several cases (#6511)[@mantine/emotion]
Fix incorrect types of createStyles
classes (#6490)[@mantine/core]
Tooltip: Fix floatingStrategy="fixed"
not working (#6502)Full Changelog: https://github.com/mantinedev/mantine/compare/7.11.1...7.11.2
Published by rtivital 3 months ago
This is a patch for the previous major version, it does not impact 7.x releases.
[@mantine/core]
Popover: Add size popover middleware to fix overflow issues in Popover.Dropdown
(#5214)[@mantine/core]
Menu: Fix broken focus logic when keepMounted
is set (#5565)[@mantine/core]
Switch: fix accessibility issues (#5755)[@mantine/core]
Fix Typescript 5.5 compatibilityFull Changelog: https://github.com/mantinedev/mantine/compare/6.0.21...6.0.22
Published by rtivital 4 months ago
[@mantine/core]
Add option to display nothingFoundMessage
when data is empty in Select and MultiSelect components (#6477)[@mantine/core]
Tooltip: Add defaultOpened
prop support (#6466)[@mantine/core]
PinInput: Fix incorrect rtl logic (#6382)[@mantine/core]
Popover: Fix floatingStrategy="fixed"
not having position:fixed
styles (#6419)[@mantine/spotlight]
Fix spotlight not working correctly with shadow DOM (#6400)[@mantine/form]
Fix onValuesChange
using stale values (#6392)[@mantine/carousel]
Fix onSlideChange
using stale props values (#6393)[@mantine/charts]
Fix unexpected padding on the right side of the chart in BarChart, AreaChart and LineChart components (#6467)[@mantine/core]
Select: Fix onChange
being called with the already selected if it has been picked from the dropdown (#6468)[@mantine/dates]
DatePickerInput: Fix highlightToday
not working (#6471)[@mantine/core]
NumberInput: Fix incorrect handling of numbers larger than max safe integer on blur (#6407)[@mantine/core]
Tooltip: Fix tooltip arrow being incompatible with headless mode (#6458)[@mantine/core]
ActionIcon: Fix loading styles inconsistency with Button component (#6460)[@mantine/charts]
PieChart: Fix key error for duplicated name
data (#6067)[@mantine/core]
Modal: Fix removeScrollProps.ref
not being compatible with React 19 (#6446)[@mantine/core]
TagsInput: Fix selectFirstOptionOnChange
prop not working (#6337)[@mantine/hooks]
use-eye-dropper: Fix Opera being incorrectly detected as a supported browser (#6307)[@mantine/core]
Fix :host
selector now working correctly in cssVariablesSelector
of MantineProvider (#6404)[@mantine/core]
TagsInput: Fix onChange
being called twice when Enter key is pressed in some cases (#6416)[@mantine/modals]
Fix Modal overrides type augmentation not working with TypeScript 5.5 (#6443)[@mantine/core]
Tree: Fix levelOffset
prop being added to the root DOM element (#6461)Full Changelog: https://github.com/mantinedev/mantine/compare/7.11.0...7.11.1
Published by rtivital 4 months ago
View changelog with demos on mantine.dev website
All Mantine components now have withProps
static function that can be used to
add default props to the component:
import { IMaskInput } from 'react-imask';
import { Button, InputBase } from '@mantine/core';
const LinkButton = Button.withProps({
component: 'a',
target: '_blank',
rel: 'noreferrer',
variant: 'subtle',
});
const PhoneInput = InputBase.withProps({
mask: '+7 (000) 000-0000',
component: IMaskInput,
label: 'Your phone number',
placeholder: 'Your phone number',
});
function Demo() {
return (
<>
{/* You can pass additional props to components created with `withProps` */}
<LinkButton href="https://mantine.dev">Mantine website</LinkButton>
{/* Component props override default props defined in `withProps` */}
<PhoneInput placeholder="Personal phone" />
</>
);
}
Avatar component now supports displaying initials with auto generated color based on the given name
value.
To display initials instead of the default placeholder, set name
prop
to the name of the person, for example, name="John Doe"
. If the name
is set, you can use color="initials"
to generate color based on the name:
import { Avatar, Group } from '@mantine/core';
const names = [
'John Doe',
'Jane Mol',
'Alex Lump',
'Sarah Condor',
'Mike Johnson',
'Kate Kok',
'Tom Smith',
];
function Demo() {
const avatars = names.map((name) => <Avatar key={name} name={name} color="initials" />);
return <Group>{avatars}</Group>;
}
New BubbleChart component:
import { BubbleChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<BubbleChart
h={60}
data={data}
range={[16, 225]}
label="Sales/hour"
color="lime.6"
dataKey={{ x: 'hour', y: 'index', z: 'value' }}
/>
);
}
BarChart component now supports waterfall
type
which is useful for visualizing changes in values over time:
import { BarChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<BarChart
h={300}
data={data}
dataKey="item"
type="waterfall"
series={[{ name: 'Effective tax rate in %', color: 'blue' }]}
withLegend
/>
);
}
LineChart component now supports gradient
type
which renders line chart with gradient fill:
import { LineChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<LineChart
h={300}
data={data}
series={[{ name: 'temperature', label: 'Avg. Temperature' }]}
dataKey="date"
type="gradient"
gradientStops={[
{ offset: 0, color: 'red.6' },
{ offset: 20, color: 'orange.6' },
{ offset: 40, color: 'yellow.5' },
{ offset: 70, color: 'lime.5' },
{ offset: 80, color: 'cyan.5' },
{ offset: 100, color: 'blue.5' },
]}
strokeWidth={5}
curveType="natural"
yAxisProps={{ domain: [-25, 40] }}
valueFormatter={(value) => `${value}°C`}
/>
);
}
LineChart, BarChart and AreaChart components
now support rightYAxis
prop which renders additional Y axis on the right side of the chart:
import { LineChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<LineChart
h={300}
data={data}
dataKey="name"
withRightYAxis
yAxisLabel="uv"
rightYAxisLabel="pv"
series={[
{ name: 'uv', color: 'pink.6' },
{ name: 'pv', color: 'cyan.6', yAxisId: 'right' },
]}
/>
);
}
RadarChart component now supports legend:
import { RadarChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<RadarChart
h={300}
data={data}
dataKey="product"
withPolarRadiusAxis
withLegend
series={[
{ name: 'Sales January', color: 'blue.6', opacity: 0.2 },
{ name: 'Sales February', color: 'orange.6', opacity: 0.2 },
]}
/>
);
}
TagsInput component behavior has been changed. Now By default,
if the user types in a value and blurs the input, the value is added to the list.
You can change this behavior by setting acceptValueOnBlur
to false
. In this case, the value is added
only when the user presses Enter
or clicks on a suggestion.
import { TagsInput } from '@mantine/core';
function Demo() {
return (
<>
<TagsInput
label="Value IS accepted on blur"
placeholder="Enter text, then blur the field"
data={['React', 'Angular', 'Svelte']}
acceptValueOnBlur
/>
<TagsInput
label="Value IS NOT accepted on blur"
placeholder="Enter text, then blur the field"
data={['React', 'Angular', 'Svelte']}
acceptValueOnBlur={false}
mt="md"
/>
</>
);
}
Transition component now supports enterDelay
and exitDelay
props to delay transition start:
import { useState } from 'react';
import { Button, Flex, Paper, Transition } from '@mantine/core';
export function Demo() {
const [opened, setOpened] = useState(false);
return (
<Flex maw={200} pos="relative" justify="center" m="auto">
<Button onClick={() => setOpened(true)}>Open dropdown</Button>
<Transition mounted={opened} transition="pop" enterDelay={500} exitDelay={300}>
{(transitionStyle) => (
<Paper
shadow="md"
p="xl"
h={120}
pos="absolute"
inset={0}
bottom="auto"
onClick={() => setOpened(false)}
style={{ ...transitionStyle, zIndex: 1 }}
>
Click to close
</Paper>
)}
</Transition>
</Flex>
);
}
Progress
component documentationexcludeDate
prophideWithOnePage
prop which hides pagination when there is only one pageexpanded
and onExpandedChange
propslineSize
prop to change lines heighthighlightToday
prop to highlight today's datePublished by rtivital 4 months ago
[@mantine/core]
Select: Fix incorrect state changes handling when both value
and searchValue
are controlled (#6272)[@mantine/core]
Stepper: Fix autoContrast
prop being added to the DOM element[@mantine/charts]
PieChart: Fix inner label not using formatted value (#6328)[@mantine/core]
Fix incorrect color resolving logic in border style prop resolver (#6326)[@mantine/modals]
Fix incorrect styles of the confirmation modal when it is used without any description (#6325)[@mantine/core]
ScrollArea: Fix click events being triggered when scrollbar drag is released over an interactive element in Firefox (#6354)[@mantine/core]
Combobox: Fix clicks on footer and header triggering dropdown close (#6344)[@mantine/core]
PasswordInput: Fix withErrorStyles
prop being passed to the DOM element (#6348)Full Changelog: https://github.com/mantinedev/mantine/compare/7.10.1...7.10.2
Published by rtivital 5 months ago
[@mantine/charts]
BarChart: Add waterfall type (#6231)[@mantine/form]
Fix form.setFieldError
called inside form.onSubmit
not working correctly in some cases (#6101)[@mantine/core]
SegmentedControl: Fix false error reported by React 18.3+ for incorrect key prop usage[@mantine/hooks]
use-fetch: Fix incorrect error handling (#6278)[@mantine/core]
Fix bd
style prop not being applied in some components (#6282)[@mantine/core]
NumberInput: Fix incorrect leading zeros handling (#6232)[@mantine/core]
NumberInput: Fix incorrect logic while editing decimal values (#6232)[@mantine/core]
ScrollArea: Fix scrollbar flickering on reveal with hover and scroll types (#6218)[@mantine/hooks]
Update use-throttled-* hooks to emit updates on trailing edges (#6257)[@mantine/core]
Input: Add inputSize
prop to set size
html attribute on the input elementFull Changelog: https://github.com/mantinedev/mantine/compare/7.10.0...7.10.1
Published by rtivital 5 months ago
View changelog with demos on mantine.dev website
New Tree component:
import { IconFolder, IconFolderOpen } from '@tabler/icons-react';
import { Group, RenderTreeNodePayload, Tree } from '@mantine/core';
import { CssIcon, NpmIcon, TypeScriptCircleIcon } from '@mantinex/dev-icons';
import { data, dataCode } from './data';
import classes from './Demo.module.css';
interface FileIconProps {
name: string;
isFolder: boolean;
expanded: boolean;
}
function FileIcon({ name, isFolder, expanded }: FileIconProps) {
if (name.endsWith('package.json')) {
return <NpmIcon size={14} />;
}
if (name.endsWith('.ts') || name.endsWith('.tsx') || name.endsWith('tsconfig.json')) {
return <TypeScriptCircleIcon size={14} />;
}
if (name.endsWith('.css')) {
return <CssIcon size={14} />;
}
if (isFolder) {
return expanded ? (
<IconFolderOpen color="var(--mantine-color-yellow-9)" size={14} stroke={2.5} />
) : (
<IconFolder color="var(--mantine-color-yellow-9)" size={14} stroke={2.5} />
);
}
return null;
}
function Leaf({ node, expanded, hasChildren, elementProps }: RenderTreeNodePayload) {
return (
<Group gap={5} {...elementProps}>
<FileIcon name={node.value} isFolder={hasChildren} expanded={expanded} />
<span>{node.label}</span>
</Group>
);
}
function Demo() {
return (
<Tree
classNames={classes}
selectOnClick
clearSelectionOnOutsideClick
data={data}
renderNode={(payload) => <Leaf {...payload} />}
/>
);
}
New form.getInputNode(path)
handler returns input DOM node for the given field path.
Form example, it can be used to focus input on form submit if there is an error:
import { Button, Group, TextInput } from '@mantine/core';
import { isEmail, isNotEmpty, useForm } from '@mantine/form';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: {
name: '',
email: '',
},
validate: {
name: isNotEmpty('Name is required'),
email: isEmail('Invalid email'),
},
});
return (
<form
onSubmit={form.onSubmit(
(values) => console.log(values),
(errors) => {
const firstErrorPath = Object.keys(errors)[0];
form.getInputNode(firstErrorPath)?.focus();
}
)}
>
<TextInput
withAsterisk
label="Your name"
placeholder="Your name"
key={form.key('name')}
{...form.getInputProps('name')}
/>
<TextInput
withAsterisk
label="Your email"
placeholder="[email protected]"
key={form.key('email')}
{...form.getInputProps('email')}
/>
<Group justify="flex-end" mt="md">
<Button type="submit">Submit</Button>
</Group>
</form>
);
}
You can now use container queries
in SimpleGrid component. With container queries, grid columns and spacing
will be adjusted based on the container width, not the viewport width.
Example of using container queries. To see how the grid changes, resize the root element
of the demo with the resize handle located at the bottom right corner of the demo:
import { SimpleGrid } from '@mantine/core';
function Demo() {
return (
// Wrapper div is added for demonstration purposes only,
// it is not required in real projects
<div style={{ resize: 'horizontal', overflow: 'hidden', maxWidth: '100%' }}>
<SimpleGrid
type="container"
cols={{ base: 1, '300px': 2, '500px': 5 }}
spacing={{ base: 10, '300px': 'xl' }}
>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</SimpleGrid>
</div>
);
}
New Checkbox.Indicator and Radio.Indicator
components look exactly the same as Checkbox
and Radio
components, but they do not
have any semantic meaning, they are just visual representations of checkbox and radio states.
Checkbox.Indicator
component:
import { Checkbox, Group } from '@mantine/core';
function Demo() {
return (
<Group>
<Checkbox.Indicator />
<Checkbox.Indicator checked />
<Checkbox.Indicator indeterminate />
<Checkbox.Indicator disabled />
<Checkbox.Indicator disabled checked />
<Checkbox.Indicator disabled indeterminate />
</Group>
);
}
Radio.Indicator
component:
import { Group, Radio } from '@mantine/core';
function Demo() {
return (
<Group>
<Radio.Indicator />
<Radio.Indicator checked />
<Radio.Indicator disabled />
<Radio.Indicator disabled checked />
</Group>
);
}
New Checkbox.Card and Radio.Card
components can be used as replacements for Checkbox
and Radio
to build custom cards/buttons/etc.
that work as checkboxes and radios. Components are accessible by default and support the same
keyboard interactions as input[type="checkbox"]
and input[type="radio"]
.
Checkbox.Card
component:
import { useState } from 'react';
import { Checkbox, Group, Text } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [checked, setChecked] = useState(false);
return (
<Checkbox.Card
className={classes.root}
radius="md"
checked={checked}
onClick={() => setChecked((c) => !c)}
>
<Group wrap="nowrap" align="flex-start">
<Checkbox.Indicator />
<div>
<Text className={classes.label}>@mantine/core</Text>
<Text className={classes.description}>
Core components library: inputs, buttons, overlays, etc.
</Text>
</div>
</Group>
</Checkbox.Card>
);
}
Checkbox.Card
component with Checkbox.Group
:
import { useState } from 'react';
import { Checkbox, Group, Stack, Text } from '@mantine/core';
import classes from './Demo.module.css';
const data = [
{
name: '@mantine/core',
description: 'Core components library: inputs, buttons, overlays, etc.',
},
{ name: '@mantine/hooks', description: 'Collection of reusable hooks for React applications.' },
{ name: '@mantine/notifications', description: 'Notifications system' },
];
function Demo() {
const [value, setValue] = useState<string[]>([]);
const cards = data.map((item) => (
<Checkbox.Card className={classes.root} radius="md" value={item.name} key={item.name}>
<Group wrap="nowrap" align="flex-start">
<Checkbox.Indicator />
<div>
<Text className={classes.label}>{item.name}</Text>
<Text className={classes.description}>{item.description}</Text>
</div>
</Group>
</Checkbox.Card>
));
return (
<>
<Checkbox.Group
value={value}
onChange={setValue}
label="Pick packages to install"
description="Choose all packages that you will need in your application"
>
<Stack pt="md" gap="xs">
{cards}
</Stack>
</Checkbox.Group>
<Text fz="xs" mt="md">
CurrentValue: {value.join(', ') || '–'}
</Text>
</>
);
}
Radio.Card
component:
import { useState } from 'react';
import { Group, Radio, Text } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [checked, setChecked] = useState(false);
return (
<Radio.Card
className={classes.root}
radius="md"
checked={checked}
onClick={() => setChecked((c) => !c)}
>
<Group wrap="nowrap" align="flex-start">
<Radio.Indicator />
<div>
<Text className={classes.label}>@mantine/core</Text>
<Text className={classes.description}>
Core components library: inputs, buttons, overlays, etc.
</Text>
</div>
</Group>
</Radio.Card>
);
}
Radio.Card
component with Radio.Group
:
import { useState } from 'react';
import { Group, Radio, Stack, Text } from '@mantine/core';
import classes from './Demo.module.css';
const data = [
{
name: '@mantine/core',
description: 'Core components library: inputs, buttons, overlays, etc.',
},
{ name: '@mantine/hooks', description: 'Collection of reusable hooks for React applications.' },
{ name: '@mantine/notifications', description: 'Notifications system' },
];
function Demo() {
const [value, setValue] = useState<string | null>(null);
const cards = data.map((item) => (
<Radio.Card className={classes.root} radius="md" value={item.name} key={item.name}>
<Group wrap="nowrap" align="flex-start">
<Radio.Indicator />
<div>
<Text className={classes.label}>{item.name}</Text>
<Text className={classes.description}>{item.description}</Text>
</div>
</Group>
</Radio.Card>
));
return (
<>
<Radio.Group
value={value}
onChange={setValue}
label="Pick one package to install"
description="Choose a package that you will need in your application"
>
<Stack pt="md" gap="xs">
{cards}
</Stack>
</Radio.Group>
<Text fz="xs" mt="md">
CurrentValue: {value || '–'}
</Text>
</>
);
}
New bd style prop can be used to set border
CSS property.
It is available in all components that support style props.
Border width value is automatically converted to rem. For border color you can reference
theme colors similar to other style props:
import { Box } from '@mantine/core';
function Demo() {
return <Box bd="1px solid red.5" />;
}
Published by rtivital 5 months ago
[@mantine/dates]
DateTimePicker: Fix some of timeInputProps
not being respected (#6204)[@mantine/core]
NavLink: Add react-router support to display active route (#6180)[@mantine/core]
Fix nonce
attribute not being set on <style />
tag generated in color scheme switching script[@mantine/core]
Input: Fix incorrect margins when input wrapper order is explicitly set[@mantine/core]
Pagination: Fix types definition being incompatible with @tabler/icons-react 3.x[@mantine/charts]
Fix incorrect tooltip position in LineChart, AreaChart and BarChart with vertical orientation[@mantine/core]
Rating: Fix readOnly
prop now working on touch devices (#6202)[@mantine/core]
TagsInput: Fix existing search value being ignored in onPaste
even handler (#6073)[@mantine/core]
TagsInput: Improve clearable
prop logic related to dropdown (#6115)Full Changelog: https://github.com/mantinedev/mantine/compare/7.9.1...7.9.2
Published by rtivital 5 months ago
[@mantine/core]
Fix theme.scale
being ignored in Input, Paper and Table border styles[@mantine/core]
Fix virtualColor
function requring use client
in Next.js[@mantine/core]
FloatingIndicator: Fix incorrect resize observer logic (#6129)[@mantine/core]
NumberInput: Fix incorrect allowNegative
handling with up/down arrows (#6170)[@mantine/core]
Fix error={true}
prop set on Checkbox, Radio and Switch rendering unxpected error element with margin[@mantine/core]
SegmentedControl: Fix theme.primaryColor
not being respected in the focus ring styles[@mantine/core]
CloseButton: Fix incorrect specificity of some selectors[@mantine/core]
Fix incorrect aria-label
handling in Select, Autocomplete, MultiSelect and TagsInputs components (#6123)[@mantine/core]
Modal: Prevent onClose
from being called when modal is not opened (#6156)[@mantine/core]
PasswordInput: Fix duplicated password visibility icon in Edge browser (#6126)[@mantine/hooks]
use-hash: Fix hash value not being updated correctly (#6145)[@mantine/emotion]
Fix incorrect transform logic that was causing extra hooks to render (#6159)Full Changelog: https://github.com/mantinedev/mantine/compare/7.9.0...7.9.1
Published by rtivital 6 months ago
View changelog with demos on mantine.dev website
New @mantine/emotion package is now available to simplify migration
from 6.x to 7.x. It includes createStyles
function and additional
functionality for sx
and styles
props for all components similar to what was available
in @mantine/core
package in v6.
If you still haven't migrated to 7.x because of the change in styling approach, you can now
have a smoother transition by using @mantine/emotion
package. To learn more about the package,
visit the documentation page and updated 6.x to 7.x migration guide.
import { rem } from '@mantine/core';
import { createStyles } from '@mantine/emotion';
const useStyles = createStyles((theme, _, u) => ({
wrapper: {
maxWidth: rem(400),
width: '100%',
height: rem(180),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginLeft: 'auto',
marginRight: 'auto',
borderRadius: theme.radius.sm,
// Use light and dark selectors to change styles based on color scheme
[u.light]: {
backgroundColor: theme.colors.gray[1],
},
[u.dark]: {
backgroundColor: theme.colors.dark[5],
},
// Reference theme.breakpoints in smallerThan and largerThan functions
[u.smallerThan('sm')]: {
// Child reference in nested selectors via ref
[`& .${u.ref('child')}`]: {
fontSize: theme.fontSizes.xs,
},
},
},
child: {
// Assign selector to a ref to reference it in other styles
ref: u.ref('child'),
padding: theme.spacing.md,
borderRadius: theme.radius.sm,
boxShadow: theme.shadows.md,
[u.light]: {
backgroundColor: theme.white,
color: theme.black,
},
[u.dark]: {
backgroundColor: theme.colors.dark[8],
color: theme.white,
},
},
}));
function Demo() {
const { classes } = useStyles();
return (
<div className={classes.wrapper}>
<div className={classes.child}>createStyles demo</div>
</div>
);
}
All @mantine/*
components and hooks have been updated to support React 18.3. It is
recommended to update your application as well to prepare for the upcoming React 19 release.
New use-field hook is now available in @mantine/form
package.
It can be used as a simpler alternative to use-form hook to manage state of a single input without the need to create a form.
The hook supports most of use-form
hook features: validation with function, touched and
dirty state, error message, validation on change/blur and more.
import { TextInput } from '@mantine/core';
import { isEmail, useField } from '@mantine/form';
function Demo() {
const field = useField({
initialValue: '',
validateOnChange: true,
validate: isEmail('Invalid email'),
});
return <TextInput {...field.getInputProps()} label="Email" placeholder="Enter your email" />;
}
use-field
hook also supports async validation:
import { Button, Loader, TextInput } from '@mantine/core';
import { useField } from '@mantine/form';
function validateAsync(value: string): Promise<string | null> {
return new Promise((resolve) => {
window.setTimeout(() => {
resolve(value === 'mantine' ? null : 'Value must be "mantine"');
}, 800);
});
}
function Demo() {
const field = useField({
initialValue: '',
validate: validateAsync,
});
return (
<>
<TextInput
{...field.getInputProps()}
label="Enter 'mantine'"
placeholder="Enter 'mantine'"
rightSection={field.isValidating ? <Loader size={18} /> : null}
mb="md"
/>
<Button onClick={field.validate}>Validate async</Button>
</>
);
}
You can now define custom mixins that are not included in mantine-postcss-preset by specifying them
in the mixins
option. To learn about mixins syntax, follow postcss-mixins documentation.
Note that this feature is available in postcss-preset-mantine
starting from version 1.15.0.
Example of adding clearfix
and circle
mixins:
module.exports = {
plugins: {
'postcss-preset-mantine': {
autoRem: true,
mixins: {
clearfix: {
'&::after': {
content: '""',
display: 'table',
clear: 'both',
},
},
circle: (_mixin, size) => ({
borderRadius: '50%',
width: size,
height: size,
}),
},
},
// ... Other plugins
},
};
Then you can use these mixins in your styles:
.demo {
@mixin clearfix;
@mixin circle 100px;
}
New use-matches
hook exported from @mantine/core
is an alternative to use-media-query
if you need to match multiple media queries and values. It accepts an object with media queries as keys and
values at given breakpoint as values.
Note that use-matches
hook uses the same logic as use-media-query under the hood,
it is not recommended to be used as a primary source of responsive styles, especially if you have ssr in your application.
In the following example:
theme.breakpoints.lg
, color will be red.9
theme.breakpoints.sm
and theme.breakpoints.lg
, color will be orange.9
theme.breakpoints.sm
, color will be blue.9
import { Box, useMatches } from '@mantine/core';
function Demo() {
const color = useMatches({
base: 'blue.9',
sm: 'orange.9',
lg: 'red.9',
});
return (
<Box bg={color} c="white" p="xl">
Box with color that changes based on screen size
</Box>
);
}
BarChart now supports withBarValueLabel
prop that allows
displaying value label on top of each bar:
import { BarChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<BarChart
h={300}
data={data}
dataKey="month"
valueFormatter={(value) => new Intl.NumberFormat('en-US').format(value)}
withBarValueLabel
series={[
{ name: 'Smartphones', color: 'violet.6' },
{ name: 'Laptops', color: 'blue.6' },
{ name: 'Tablets', color: 'teal.6' },
]}
/>
);
}
form.key()
functionPublished by rtivital 6 months ago
Note that if you've already started using uncontrolled form mode introduced in 7.8.0, you need to include form.key()
as described in the documentation.
[@mantine/form]
Add defaultValue
to form.getInputProps
return type[@mantine/form]
Replace key
spread with form.getInputProps
with form.key()
function[@mantine/dropzone]
Fix keyboard activation not working (#6095)[@mantine/dates]
DatePicker: Fix date range being stuck in incorrect state when controlled state changes to an empty value (#6092)[@mantine/core]
Radio: Allow null
to be passed to Radio.Group value to clear the value (#6102)[@mantine/core]
NumberInput: Fix incorrect cursor position when backspace is pressed (#6072)[@mantine/core]
Fix incorrect empty string handling in style props (#6078)Full Changelog: https://github.com/mantinedev/mantine/compare/7.8.0...7.8.1
Published by rtivital 6 months ago
View changelog with demos on mantine.dev website
Start from version 1.14.4
postcss-preset-mantine
supports autoRem
option that can be used to automatically convert all px
values
to rem
units in .css
files.
module.exports = {
plugins: {
'postcss-preset-mantine': {
autoRem: true,
},
},
};
This option works similar to rem
function. The following code:
.demo {
font-size: 16px;
@media (min-width: 320px) {
font-size: 32px;
}
}
Will be transformed to:
.demo {
font-size: calc(1rem * var(--mantine-scale));
@media (min-width: 320px) {
font-size: calc(2rem * var(--mantine-scale));
}
}
Note that autoRem
converts only CSS properties, values in @media
queries are
not converted automatically – you still need to use em
function to convert them.
autoRem
option does not convert values in the following cases:
calc()
, var()
, clamp()
and url()
functionscontent
propertyrgb()
, rgba()
, hsl()
, hsla()
colorsIf you want to convert above values to rem units, use rem
function manually.
useForm hook now supports uncontrolled mode.
Uncontrolled mode provides a significant performance improvement by reducing
the number of re-renders and the amount of state updates almost to 0. Uncontrolled
mode is now the recommended way to use the useForm
hook for almost all use cases.
Example of uncontrolled form (form.values
are not updated):
import { useState } from 'react';
import { Button, Code, Text, TextInput } from '@mantine/core';
import { hasLength, isEmail, useForm } from '@mantine/form';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: { name: '', email: '' },
validate: {
name: hasLength({ min: 3 }, 'Must be at least 3 characters'),
email: isEmail('Invalid email'),
},
});
const [submittedValues, setSubmittedValues] = useState<typeof form.values | null>(null);
return (
<form onSubmit={form.onSubmit(setSubmittedValues)}>
<TextInput {...form.getInputProps('name')} label="Name" placeholder="Name" />
<TextInput {...form.getInputProps('email')} mt="md" label="Email" placeholder="Email" />
<Button type="submit" mt="md">
Submit
</Button>
<Text mt="md">Form values:</Text>
<Code block>{JSON.stringify(form.values, null, 2)}</Code>
<Text mt="md">Submitted values:</Text>
<Code block>{submittedValues ? JSON.stringify(submittedValues, null, 2) : '–'}</Code>
</form>
);
}
With uncontrolled mode, you can not access form.values
as a state variable,
instead, you can use form.getValues()
method to get current form values at any time:
import { useForm } from '@mantine/form';
const form = useForm({
mode: 'uncontrolled',
initialValues: { name: 'John Doe' },
});
form.getValues(); // { name: 'John Doe' }
form.setValues({ name: 'John Smith' });
form.getValues(); // { name: 'John Smith' }
form.getValues()
always returns the latest form values, it is safe to use it
after state updates:
import { useForm } from '@mantine/form';
const form = useForm({
mode: 'uncontrolled',
initialValues: { name: 'John Doe' },
});
const handleNameChange = () => {
form.setFieldValue('name', 'Test Name');
// ❌ Do not use form.values to get the current form values
// form.values has stale name value until next rerender in controlled mode
// and is always outdated in uncontrolled mode
console.log(form.values); // { name: 'John Doe' }
// ✅ Use form.getValues to get the current form values
// form.getValues always returns the latest form values
console.log(form.getValues()); // { name: 'Test Name' }
};
form.watch
is an effect function that allows subscribing to changes of a
specific form field. It accepts field path and a callback function that is
called with new value, previous value, touched and dirty field states:
import { TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';
function Demo() {
const form = useForm({
mode: 'uncontrolled',
initialValues: {
name: '',
email: '',
},
});
form.watch('name', ({ previousValue, value, touched, dirty }) => {
console.log({ previousValue, value, touched, dirty });
});
return (
<div>
<TextInput label="Name" placeholder="Name" {...form.getInputProps('name')} />
<TextInput mt="md" label="Email" placeholder="Email" {...form.getInputProps('email')} />
</div>
);
}
You can now customize middlewares
options in Popover component and
in other components (Menu, Select, Combobox, etc.)
based on Popover.
To customize Floating UI middlewares options, pass them as
an object to the middlewares
prop. For example, to change shift
middleware padding to 20px
use the following configuration:
import { Popover } from '@mantine/core';
function Demo() {
return (
<Popover middlewares={{ shift: { padding: 20 } }} position="bottom">
{/* Popover content */}
</Popover>
);
}
New use-fetch hook:
import { Box, Button, Code, Group, LoadingOverlay, Text } from '@mantine/core';
import { useFetch } from '@mantine/hooks';
interface Item {
userId: number;
id: number;
title: string;
completed: boolean;
}
function Demo() {
const { data, loading, error, refetch, abort } = useFetch<Item[]>(
'https://jsonplaceholder.typicode.com/todos/'
);
return (
<div>
{error && <Text c="red">{error.message}</Text>}
<Group>
<Button onClick={refetch} color="blue">
Refetch
</Button>
<Button onClick={abort} color="red">
Abort
</Button>
</Group>
<Box pos="relative" mt="md">
<Code block>{data ? JSON.stringify(data.slice(0, 3), null, 2) : 'Fetching'}</Code>
<LoadingOverlay visible={loading} />
</Box>
</div>
);
}
New use-map hook:
import { IconPlus, IconTrash } from '@tabler/icons-react';
import { ActionIcon, Group, Table } from '@mantine/core';
import { useMap } from '@mantine/hooks';
function Demo() {
const map = useMap([
['/hooks/use-media-query', 4124],
['/hooks/use-clipboard', 8341],
['/hooks/use-fetch', 9001],
]);
const rows = Array.from(map.entries()).map(([key, value]) => (
<Table.Tr key={key}>
<Table.Td>{key}</Table.Td>
<Table.Td>{value}</Table.Td>
<Table.Td>
<Group>
<ActionIcon variant="default" onClick={() => map.set(key, value + 1)} fw={500}>
<IconPlus stroke={1.5} size={18} />
</ActionIcon>
<ActionIcon variant="default" onClick={() => map.delete(key)} c="red">
<IconTrash stroke={1.5} size={18} />
</ActionIcon>
</Group>
</Table.Td>
</Table.Tr>
));
return (
<Table layout="fixed">
<Table.Thead>
<Table.Tr>
<Table.Th>Page</Table.Th>
<Table.Th>Views last month</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
</Table>
);
}
New use-set hook:
import { useState } from 'react';
import { Code, Stack, TextInput } from '@mantine/core';
import { useSet } from '@mantine/hooks';
function Demo() {
const [input, setInput] = useState('');
const scopes = useSet<string>(['@mantine', '@mantine-tests', '@mantinex']);
const isDuplicate = scopes.has(input.trim().toLowerCase());
const items = Array.from(scopes).map((scope) => <Code key={scope}>{scope}</Code>);
return (
<>
<TextInput
label="Add new scope"
placeholder="Enter scope"
description="Duplicate scopes are not allowed"
value={input}
onChange={(event) => setInput(event.currentTarget.value)}
error={isDuplicate && 'Scope already exists'}
onKeyDown={(event) => {
if (event.nativeEvent.code === 'Enter' && !isDuplicate) {
scopes.add(input.trim().toLowerCase());
setInput('');
}
}}
/>
<Stack gap={5} align="flex-start" mt="md">
{items}
</Stack>
</>
);
}
New use-debounced-callback hook:
import { useState } from 'react';
import { Loader, Text, TextInput } from '@mantine/core';
import { useDebouncedCallback } from '@mantine/hooks';
function getSearchResults(query: string): Promise<{ id: number; title: string }[]> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(
query.trim() === ''
? []
: Array(5)
.fill(0)
.map((_, index) => ({ id: index, title: `${query} ${index + 1}` }))
);
}, 1000);
});
}
function Demo() {
const [search, setSearch] = useState('');
const [searchResults, setSearchResults] = useState<{ id: number; title: string }[]>([]);
const [loading, setLoading] = useState(false);
const handleSearch = useDebouncedCallback(async (query: string) => {
setLoading(true);
setSearchResults(await getSearchResults(query));
setLoading(false);
}, 500);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearch(event.currentTarget.value);
handleSearch(event.currentTarget.value);
};
return (
<>
<TextInput
value={search}
onChange={handleChange}
placeholder="Search..."
rightSection={loading && <Loader size={20} />}
/>
{searchResults.map((result) => (
<Text key={result.id} size="sm">
{result.title}
</Text>
))}
</>
);
}
New use-throttled-state hook:
import { Text, TextInput } from '@mantine/core';
import { useThrottledState } from '@mantine/hooks';
function Demo() {
const [throttledValue, setThrottledValue] = useThrottledState('', 1000);
return (
<>
<TextInput
placeholder="Search"
onChange={(event) => setThrottledValue(event.currentTarget.value)}
/>
<Text>Throttled value: {throttledValue || '–'}</Text>
</>
);
}
New use-throttled-value hook:
import { Text, TextInput } from '@mantine/core';
import { useThrottledValue } from '@mantine/hooks';
function Demo() {
const [value, setValue] = useState('');
const throttledValue = useThrottledValue(value, 1000);
return (
<>
<TextInput placeholder="Search" onChange={(event) => setValue(event.currentTarget.value)} />
<Text>Throttled value: {throttledValue || '–'}</Text>
</>
);
}
New use-throttled-callback hook:
import { Text, TextInput } from '@mantine/core';
import { useThrottledCallback } from '@mantine/hooks';
function Demo() {
const [throttledValue, setValue] = useState('');
const throttledSetValue = useThrottledCallback((value) => setValue(value), 1000);
return (
<>
<TextInput
placeholder="Search"
onChange={(event) => throttledSetValue(event.currentTarget.value)}
/>
<Text>Throttled value: {throttledValue || '–'}</Text>
</>
);
}
New use-orientation hook:
import { Code, Text } from '@mantine/core';
import { useOrientation } from '@mantine/hooks';
function Demo() {
const { angle, type } = useOrientation();
return (
<>
<Text>
Angle: <Code>{angle}</Code>
</Text>
<Text>
Type: <Code>{type}</Code>
</Text>
</>
);
}
New use-is-first-render hook:
import { useState } from 'react';
import { Button, Text } from '@mantine/core';
import { useIsFirstRender } from '@mantine/hooks';
function Demo() {
const [counter, setCounter] = useState(0);
const firstRender = useIsFirstRender();
return (
<div>
<Text>
Is first render:{' '}
<Text span c={firstRender ? 'teal' : 'red'}>
{firstRender ? 'Yes' : 'No!'}
</Text>
</Text>
<Button onClick={() => setCounter((c) => c + 1)} mt="sm">
Rerendered {counter} times, click to rerender
</Button>
</div>
);
}
withKeyboardEvents={false}
to disable up/down arrow keys handlingPublished by rtivital 6 months ago
[@mantine/core]
CloseButton: Add missing disabled styles (#6044)[@mantine/core]
AppShell: Fix incorrect app safe area handling by AppShell.Footer (#6060)[@mantine/core]
NumberInput: Fix cursor position changing when the value is incremented/decremented (#6004)[@mantine/core]
TagsInput: Fix incorrect IME keyboard input handling for Backspace
key (#6011)[@mantine/charts]
Fix incorrect overflow styles of svg element (#6040)[@mantine/core]
PinInput: Add rootRef
prop support (#6032)[@mantine/core]
ScrollArea: Fix viewportProps.onScroll
not working (#6055)[@mantine/core]
ScrollArea: Fix incorrect inset position of the horizontal scrollbar (#6059)[@mantine/hooks]
use-local-storage: Fix infinite rerendering with object values (#6022)Full Changelog: https://github.com/mantinedev/mantine/compare/7.7.1...7.7.2
Published by rtivital 7 months ago
[@mantine/tiptap]
Improve toolbar items alignment for non-native elements (#5993)[@mantine/spotlight]
Fix incorrect down key handling when the spotlight is opened repeatedly (#5995)[@mantine/core]
Image: Fix ref not being assigned for fallback images (#5989)[@mantine/core]
PinInput: Fix incorrect focus logic (#5963)[@mantine/core]
Table: Fix highlightOnHoverColor
prop not working[@mantine/core]
AppShell: Adjust footer position to include env(safe-area-inset-bottom) (#5502)[@mantine/core]
PinInput: Fix placeholder not being visible on the element that had focus when the component becomes disabled (#5831)[@mantine/dates]
Calendar: Fix double timezone shift (#5916)[@mantine/hooks]
use-local-storage: Fix value not being updated when key is changed (#5910)[@mantine/charts]
Fix incorrect charts legends height for multiline values (#5923)[@mantine/core]
NumberInput: Fix incorrect increment/decrement functions logic when step
is a float value (#5926)[@mantine/core]
Combobox: Fix incorrect IME input handling (#5935)[@mantine/core]
Menu: Fix unexpected focus styles in the dropdown element in Firefox (#5957)[@mantine/core]
Fix incorrect disabled
prop handling in TagsInput and MultiSelect (#5959)[@mantine/core]
Fix renderOption
not working for grouped items in Combobox-based components (#5952)[@mantine/core]
AppShell: Fix error when used inside Suspense (#5979)[@mantine/core]
Update CSS selectors hashing algorithm to prevent collisions with other libraries (#5968)[@mantine/carousel]
Fix specificity issues of some selectors (#5973)[@mantine/core]
AppShell: Fix missing Aside offset in Header and Footer for layout=alt
(#5974)Full Changelog: https://github.com/mantinedev/mantine/compare/7.7.0...7.7.1
Published by rtivital 7 months ago
View changelog with demos on mantine.dev website
Virtual color is a special color which values should be different for light and dark color schemes.
To define a virtual color, use virtualColor
function which accepts an object with the following
properties as a single argument:
name
– color name, must be the same as the key in theme.colors
objectlight
– a key of theme.colors
object for light color schemedark
– a key of theme.colors
object for dark color schemeTo see the demo in action, switch between light and dark color schemes (Ctrl + J
):
import { createTheme, MantineProvider, virtualColor } from '@mantine/core';
const theme = createTheme({
colors: {
primary: virtualColor({
name: 'primary',
dark: 'pink',
light: 'cyan',
}),
},
});
function App() {
return <MantineProvider theme={theme}>{/* Your app here */}</MantineProvider>;
}
New FloatingIndicator component:
import { useState } from 'react';
import {
IconArrowDown,
IconArrowDownLeft,
IconArrowDownRight,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpLeft,
IconArrowUpRight,
IconCircle,
} from '@tabler/icons-react';
import { FloatingIndicator, UnstyledButton } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
const [active, setActive] = useState('center');
const setControlRef = (name: string) => (node: HTMLButtonElement) => {
controlsRefs[name] = node;
setControlsRefs(controlsRefs);
};
return (
<div className={classes.root} dir="ltr" ref={setRootRef}>
<FloatingIndicator
target={controlsRefs[active]}
parent={rootRef}
className={classes.indicator}
/>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-left')}
ref={setControlRef('up-left')}
mod={{ active: active === 'up-left' }}
>
<IconArrowUpLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up')}
ref={setControlRef('up')}
mod={{ active: active === 'up' }}
>
<IconArrowUp size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-right')}
ref={setControlRef('up-right')}
mod={{ active: active === 'up-right' }}
>
<IconArrowUpRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('left')}
ref={setControlRef('left')}
mod={{ active: active === 'left' }}
>
<IconArrowLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('center')}
ref={setControlRef('center')}
mod={{ active: active === 'center' }}
>
<IconCircle size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('right')}
ref={setControlRef('right')}
mod={{ active: active === 'right' }}
>
<IconArrowRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-left')}
ref={setControlRef('down-left')}
mod={{ active: active === 'down-left' }}
>
<IconArrowDownLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down')}
ref={setControlRef('down')}
mod={{ active: active === 'down' }}
>
<IconArrowDown size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-right')}
ref={setControlRef('down-right')}
mod={{ active: active === 'down-right' }}
>
<IconArrowDownRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
</div>
);
}
New ScatterChart component:
import { useState } from 'react';
import {
IconArrowDown,
IconArrowDownLeft,
IconArrowDownRight,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconArrowUpLeft,
IconArrowUpRight,
IconCircle,
} from '@tabler/icons-react';
import { FloatingIndicator, UnstyledButton } from '@mantine/core';
import classes from './Demo.module.css';
function Demo() {
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
const [active, setActive] = useState('center');
const setControlRef = (name: string) => (node: HTMLButtonElement) => {
controlsRefs[name] = node;
setControlsRefs(controlsRefs);
};
return (
<div className={classes.root} dir="ltr" ref={setRootRef}>
<FloatingIndicator
target={controlsRefs[active]}
parent={rootRef}
className={classes.indicator}
/>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-left')}
ref={setControlRef('up-left')}
mod={{ active: active === 'up-left' }}
>
<IconArrowUpLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up')}
ref={setControlRef('up')}
mod={{ active: active === 'up' }}
>
<IconArrowUp size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('up-right')}
ref={setControlRef('up-right')}
mod={{ active: active === 'up-right' }}
>
<IconArrowUpRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('left')}
ref={setControlRef('left')}
mod={{ active: active === 'left' }}
>
<IconArrowLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('center')}
ref={setControlRef('center')}
mod={{ active: active === 'center' }}
>
<IconCircle size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('right')}
ref={setControlRef('right')}
mod={{ active: active === 'right' }}
>
<IconArrowRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
<div className={classes.controlsGroup}>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-left')}
ref={setControlRef('down-left')}
mod={{ active: active === 'down-left' }}
>
<IconArrowDownLeft size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down')}
ref={setControlRef('down')}
mod={{ active: active === 'down' }}
>
<IconArrowDown size={26} stroke={1.5} />
</UnstyledButton>
<UnstyledButton
className={classes.control}
onClick={() => setActive('down-right')}
ref={setControlRef('down-right')}
mod={{ active: active === 'down-right' }}
>
<IconArrowDownRight size={26} stroke={1.5} />
</UnstyledButton>
</div>
</div>
);
}
New colorsTuple
function can be used to:
import { colorsTuple, createTheme } from '@mantine/core';
const theme = createTheme({
colors: {
custom: colorsTuple('#FFC0CB'),
dynamic: colorsTuple(Array.from({ length: 10 }, (_, index) => '#FFC0CB')),
},
});
New useMutationObserver hook:
import { useState } from 'react';
import { Kbd, Text } from '@mantine/core';
import { useMutationObserver } from '@mantine/hooks';
function Demo() {
const [lastMutation, setLastMutation] = useState('');
useMutationObserver(
(mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'dir') {
mutation.target instanceof HTMLElement &&
setLastMutation(mutation.target.getAttribute('dir') || '');
}
});
},
{
attributes: true,
attributeFilter: ['dir'],
},
() => document.documentElement
);
return (
<>
<Text>
Press <Kbd>Ctrl</Kbd> + <Kbd>Shift</Kbd> + <Kbd>L</Kbd> to change direction
</Text>
<Text mt={10}>Direction was changed to: {lastMutation || 'Not changed yet'}</Text>
</>
);
}
New useStateHistory hook:
import { Button, Code, Group, Text } from '@mantine/core';
import { useStateHistory } from '@mantine/hooks';
function Demo() {
const [value, handlers, history] = useStateHistory(1);
return (
<>
<Text>Current value: {value}</Text>
<Group my="md">
<Button onClick={() => handlers.set(Math.ceil(Math.random() * 100) + 1)}>Set value</Button>
<Button onClick={() => handlers.back()}>Back</Button>
<Button onClick={() => handlers.forward()}>Forward</Button>
</Group>
<Code block>{JSON.stringify(history, null, 2)}</Code>
</>
);
}
AreaChart, BarChart and LineChart
components now support xAxisLabel
and yAxisLabel
props:
import { AreaChart } from '@mantine/charts';
import { data } from './data';
function Demo() {
return (
<AreaChart
h={300}
data={data}
dataKey="date"
type="stacked"
xAxisLabel="Date"
yAxisLabel="Amount"
series={[
{ name: 'Apples', color: 'indigo.6' },
{ name: 'Oranges', color: 'blue.6' },
{ name: 'Tomatoes', color: 'teal.6' },
]}
/>
);
}
mantine-hidden-from-{x}
and mantine-visible-from-{x}
classes.window.HTMLElement.prototype.scrollIntoView
New articles added to the help center:
transform: scale()
.connectNulls
and areaProps
propsscrollAreaProps
prop to pass props down to the ScrollArea component in the dropdownfade-up
, fade-down
, fade-left
, fade-right
fade-down
. This change resolves issues with SegmentedControl indicator positioning when used inside modals.fz
and lh
style props with h1
, h2
, h3
, h4
, h5
, h6
valuesPublished by rtivital 7 months ago
[@mantine/hooks]
use-resize-observer: Fix types (#5847)[@mantine/hooks]
use-local-storage: Fix undefined
being written to the local storage when defaultValue
is not defined (#5848)[@mantine/core]
NumberInput: Fix onValueChange
not being called in increment/decrement functions (#5856)[@mantine/core]
InputWrapper: Fix className
specified in labelProps
, descriptionProps
and errorProps
not being passed to the corresponding element (#5862)[@mantine/core]
Fix some functions not working correctly with TypeScript 5.4 (#5891)[@mantine/form]
Fix onValuesChange
not using updated function (#5901)[@mantine/core]
Popover: Fix incorrect dropdown selectors (#5903)[@mantine/core]
Indicator: Fix processing animation in Safari (#5908)[@mantine/hooks]
use-headroom: Fix incorrect pinning logic when scrolling up (#5793)[@mantine/dropzone]
Add heic images format to default mime types (#5867)[@mantine/core]
Transition: Fix transitions resolving instantly in some cases (#5873)[@mantine/dropzone]
Add inputProps
prop support to pass props down to the underlying hidden input element (#5880)[@mantine/core]
Timeline: Fix autoContrast
being passed to the dom node as attribute (#5890)Full Changelog: https://github.com/mantinedev/mantine/compare/7.6.1...7.6.2
Published by rtivital 8 months ago
[@mantine/core]
Fix incorrect focus ring styles in Button.Group and ActionIcon.Group components (#5736)[@mantine/core]
Progress: Fix incorrect border-radius with multiple sections[@mantine/dates]
DateTimePicker: Fix minDate
and maxDate
not being respected in time input (#5819)[@mantine/core]
Switch: Use role="switch"
for better accessibility (#5746)[@mantine/hooks]
use-resize-observer: Fix incorrect ref type (#5780)[@mantine/dates]
Fix popoverProps.onClose
overriding original component value in DatePickerInput and other similar components (#4105)[@mantine/core]
Fix incorrect Escape key handling in Modal and Drawer components in some cases (#2827)[@mantine/core]
Combobox: Fix incorrect Escape key handling in Modal, Drawer and Popover[@mantine/core]
Transition: Fix transition resolving instantly in some cases (#3126, #5193)[@mantine/core]
Remove loader from the DOM if loading
prop is not set on ActionIcon and Button components (#5795)[@mantine/hooks]
use-local-storage: Fix inconsistent default value persistence if getInitialValueInEffect
is set (#5796)[@mantine/core]
Select: Fix autoComplete
prop not working (#5813)[@mantine/core]
Tabs: Fix incorrect border styles in outline variant[@mantine/core]
Checkbox: Fix incorrect indeterminate
+ disabled
styles for outline variant (#5806)[@mantine/core]
SegmentedControl: Fix indicator state not being updated correctly when controlled state changes to a value that is not present in the data array (#5689)[@mantine/core]
Fix incorrect label offset with left label position in Checkbox, Switch and Radio components (#5823)[@mantine/core]
PinInput: Fix updating controlled value to an empty string working incorrectly[@mantine/core]
Menu: Fix incorrect role of dropdown elementsFull Changelog: https://github.com/mantinedev/mantine/compare/7.6.0...7.6.1