Version v8.0.0

Release date

Migration guide

This changelog covers breaking changes and new features in Mantine 8.0. To migrate your application to Mantine 8.0, follow 7.x → 8.x migration guide.

Granular global styles exports

Global styles are now splitted between 3 files:

  • baseline.css – a minimal CSS reset, sets box-sizing: border-box and changes font properties
  • default-css-variables.css – contains all CSS variables generated from the default theme
  • global.css – global classes used in Mantine components

If you previously imported individual styles from @mantine/core package, you need to update imports to use new files:

import '@mantine/core/styles/baseline.css';
import '@mantine/core/styles/default-css-variables.css';
import '@mantine/core/styles/global.css';

If you imported @mantine/core/styles.css, no changes are required – all new files are already included in styles.css.

Menu with submenus

Menu component now supports submenus:

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>
          <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.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>
  );
}

Popover hideDetached

Popover component now supports hideDetached prop to configure how the dropdown behaves when the target element is hidden with styles (display: none, visibility: hidden, etc.), removed from the DOM, or when the target element is scrolled out of the viewport.

By default, hideDetached is enabled – the dropdown is hidden with the target element. You can change this behavior with hideDetached={false}. To see the difference, try to scroll the root element of the following demo:

import { Box, Button, Group, Popover } from '@mantine/core';

function Demo() {
  return (
    <Box
      bd="1px solid var(--mantine-color-dimmed)"
      p="xl"
      w={400}
      h={200}
      style={{ overflow: 'auto' }}
    >
      <Box w={1000} h={400}>
        <Group>
          <Popover width="target" position="bottom" opened>
            <Popover.Target>
              <Button>Toggle popover</Button>
            </Popover.Target>
            <Popover.Dropdown>This popover dropdown is hidden when detached</Popover.Dropdown>
          </Popover>

          <Popover width="target" position="bottom" opened hideDetached={false}>
            <Popover.Target>
              <Button>Toggle popover</Button>
            </Popover.Target>
            <Popover.Dropdown>This popover dropdown is visible when detached</Popover.Dropdown>
          </Popover>
        </Group>
      </Box>
    </Box>
  );
}

Date values as strings

All @mantine/dates components now use date strings in YYYY-MM-DD or YYYY-MM-DD HH:mm:ss format instead of Date objects. This change was made to resolve issues related to timezones – now the output of all @mantine/dates components does not include any timezone specific information. Follow 7.x → 8.x migration guide to learn how to update the code to use new string values.

Example of using DatePicker component with string values:

MoTuWeThFrSaSu
import { useState } from 'react';
import { DatePicker } from '@mantine/dates';

function Demo() {
  const [value, setValue] = useState<string | null>(null);
  return <DatePicker value={value} onChange={setValue} />;
}

DatesProvider timezone

DatesProvider component no longer supports timezone option – all @mantine/dates components now use strings in YYYY-MM-DD HH:mm:ss format as values and do not contain timezone information.

If you need to handle timezones in your application, you can use a dedicated dates library (dayjs, luxon, date-fns) to update timezone values.

Example of using Mantine components with dayjs:

import dayjs from 'dayjs';
import { DatePicker } from '@mantine/dates';

function Demo() {
  const [value, setValue] = useState<string | null>('2022-08-21');

  // Mantine components use strings as values, you can pass these
  // strings to a dates library of your choice to assign timezone
  const dateWithTimeZone = dayjs(value).tz("America/Toronto").toDate();

  return <DatePicker value={value} onChange={setValue} />;
}

TimePicker component

New TimePicker component is an alternative to TimeInput that offers more features. It supports 24-hour and 12-hour formats, dropdown with hours, minutes and seconds, and more.

::
::
import { TimePicker } from '@mantine/dates';

function Demo() {
  return (
    <>
      <TimePicker label="Enter time (24h format)" withSeconds withDropdown />
      <TimePicker label="Enter time (12h format)" withSeconds withDropdown format="12h" mt="md" />
    </>
  );
}

DateTimePicker component changes

DateTimePicker component now uses TimePicker under the hood instead of TimeInput. You can now use all TimePicker features with DateTimePicker component.

Prop timeInputProps is no longer available, to pass props down to the underlying TimePicker you need to use timePickerProps prop.

Example of enabling dropdown and setting 12h format for time picker:

import { DateTimePicker } from '@mantine/dates';

function Demo() {
  return (
    <DateTimePicker
      label="Pick date and time"
      placeholder="Pick date and time"
      timePickerProps={{
        withDropdown: true,
        popoverProps: { withinPortal: false },
        format: '12h',
      }}
    />
  );
}

TimeValue component

New TimeValue component can be used to display a formatted time string with similar formatting options to TimePicker component.

24h format: 18:45

12h format: 6:45 PM

import { Text } from '@mantine/core';
import { TimeValue } from '@mantine/dates';

function Demo() {
  return (
    <div>
      <Text>
        24h format: <TimeValue value="18:45:34" />
      </Text>
      <Text>
        12h format: <TimeValue value="18:45:34" format="12h" />
      </Text>
    </div>
  );
}

TimeGrid component

New TimeGrid component allows to capture time value from the user with a predefined set of time slots:

Format
Size
Radius
import { getTimeRange, TimeGrid } from '@mantine/dates';

function Demo() {
  return (
    <TimeGrid
      data={getTimeRange({ startTime: '10:00', endTime: '21:00', interval: '01:00' })}
      simpleGridProps={{
        type: 'container',
        cols: { base: 1, '180px': 2, '320px': 3 },
        spacing: 'xs',
      }}
      withSeconds={false}
    />
  );
}

Heatmap component

New Heatmap component allows to display data in a calendar heatmap format:

MonWedFriSunFebMarAprMayJunJulAugSepOctNovDecJan
import dayjs from 'dayjs';
import { Heatmap } from '@mantine/charts';
import { data } from './data';

function Demo() {
  return (
    <Heatmap
      data={data}
      startDate="2024-02-16"
      endDate="2025-02-16"
      withTooltip
      withWeekdayLabels
      withMonthLabels
      getTooltipLabel={({ date, value }) =>
        `${dayjs(date).format('DD MMM, YYYY')} – ${value === null || value === 0 ? 'No contributions' : `${value} contribution${value > 1 ? 's' : ''}`}`
      }
    />
  );
}

CodeHighlight changes

@mantine/code-highlight package no longer depends options highlight.js. Instead it now provides a new API based on adapters that allows using any syntax highlighter of your choice. Out of the box @mantine/code-highlight provides adapters for shiki and highlight.js.

To learn about the migration process and how to use new adapters API, check the updated CodeHighlight documentation and 7.x → 8.x migration guide.

CodeHighlight with shiki

You can now use CodeHighlight component with shiki.

Shiki library provides the most advanced syntax highlighting for TypeScript and CSS/Sass code. It uses textmate grammars to highlight code (same as in VSCode). Shiki adapter is recommended if you need to highlight advanced TypeScript (generics, jsx nested in props) or CSS code (custom syntaxes, newest features). Shiki adapter is used for all code highlighting in Mantine documentation.

To use shiki adapter you need to install shiki package:

yarn add shiki

Then wrap your app with CodeHighlightAdapterProvider and provide createShikiAdapter as adapter prop:

import { MantineProvider } from '@mantine/core';
import { CodeHighlightAdapterProvider, createShikiAdapter } from '@mantine/code-highlight';

// Shiki requires async code to load the highlighter
async function loadShiki() {
  const { createHighlighter } = await import('shiki');
  const shiki = await createHighlighter({
    langs: ['tsx', 'scss', 'html', 'bash', 'json'],
    themes: [],
  });

  return shiki;
}

const shikiAdapter = createShikiAdapter(loadShiki);

function App() {
  return (
    <MantineProvider>
      <CodeHighlightAdapterProvider adapter={shikiAdapter}>
        {/* Your app here */}
      </CodeHighlightAdapterProvider>
    </MantineProvider>
  );
}

After that, you can use CodeHighlight component in your application:

type FilterPropsRes<T extends Record<string, any>> = {
  [Key in keyof T]-?: T[Key] extends undefined ? never : T[Key];
};

export function filterProps<T extends Record<string, any>>(props: T) {
  return Object.keys(props).reduce<FilterPropsRes<T>>((acc, key: keyof T) => {
    if (props[key] !== undefined) {
      acc[key] = props[key];
    }
    return acc;
  }, {} as FilterPropsRes<T>);
}
import { CodeHighlight } from '@mantine/code-highlight';

const exampleCode = `
type FilterPropsRes<T extends Record<string, any>> = {
  [Key in keyof T]-?: T[Key] extends undefined ? never : T[Key];
};

export function filterProps<T extends Record<string, any>>(props: T) {
  return Object.keys(props).reduce<FilterPropsRes<T>>((acc, key: keyof T) => {
    if (props[key] !== undefined) {
      acc[key] = props[key];
    }
    return acc;
  }, {} as FilterPropsRes<T>);
}
`;

function Demo() {
  return <CodeHighlight code={exampleCode} language="tsx" radius="md" />;
}

Carousel changes

@mantine/carousel package was updated to use the latest version of embla-carousel-react package. This update includes breaking changes:

  • speed and draggable props were removed – they are no longer supported by embla-carousel-react
  • It is now required to install both embla-carousel and embla-carousel-react packages explicitly
  • useAnimationOffsetEffect hook was removed – the issue it addressed was fixed in embla-carousel-react
  • Embla type export was removed, you should use EmblaCarouselType from embla-carousel instead
  • Props that were previously passed to embla are now grouped under emblaOptions prop

Follow the 7.x → 8.x migration guide to update your application to use the latest version of @mantine/carousel.

Switch withThumbIndicator

Switch component styles were updated to include indicator inside the thumb. You can change it by setting withThumbIndicator prop:

Color
Label position
Size
Radius
import { Switch } from '@mantine/core';

function Demo() {
  return (
    <Switch
      defaultChecked
      label="I agree to sell my privacy"
    />
  );
}

Other changes

  • Kbd component now supports size prop
  • DateInput component no longer supports preserveTime prop, use different component to capture time values
  • ScrollArea component no longer has forced display: table styles on the wrapper element of the content. It also now supports content Styles API selector to apply styles to the content element.
  • Image component no longer includes flex: 0 styles by default
  • SegmentedControl default height values were changed to match sizes of Input components
  • Type of wrapperProps prop in all components that support it (Checkbox, Radio, Chip, most inputs) was changed to more strict type
  • Portal component now has reuseTargetNode prop enabled by default
  • use-form setFieldValue handler types are now more strict
  • Menu.Item no longer has data-hovered attribute, use :hover and :focus selectors instead to apply styles
  • use-os now supports Chrome OS detection, its return type now includes chromeos value in the union