Skip to content

7.4.0 ⭐

Compare
Choose a tag to compare
@rtivital rtivital released this 03 Jan 06:26
· 677 commits to master since this release

View changelog with demos on mantine.dev website

@mantine/charts

New @mantine/charts package provides a set of components
to build charts and graphs. All components are based on recharts.
Currently, the package provides AreaChart, BarChart,
LineChart and Sparkline components.
More components will be added in the next minor releases.

AreaChart component

New AreaChart component:

import { AreaChart } from '@mantine/charts';
import { data } from './data';

function Demo() {
  return (
    <AreaChart
      h={300}
      data={data}
      dataKey="date"
      type="stacked"
      series={[
        { name: 'Apples', color: 'indigo.6' },
        { name: 'Oranges', color: 'blue.6' },
        { name: 'Tomatoes', color: 'teal.6' },
      ]}
    />
  );
}

LineChart component

New LineChart component:

import { LineChart } from '@mantine/charts';
import { data } from './data';

function Demo() {
  return (
    <LineChart
      h={300}
      data={data}
      dataKey="date"
      withLegend
      series={[
        { name: 'Apples', color: 'indigo.6' },
        { name: 'Oranges', color: 'blue.6' },
        { name: 'Tomatoes', color: 'teal.6' },
      ]}
    />
  );
}

BarChart component

New BarChart component:

import { BarChart } from '@mantine/charts';
import { data } from './data';

function Demo() {
  return (
    <BarChart
      h={300}
      data={data}
      dataKey="month"
      type="stacked"
      orientation="vertical"
      yAxisProps={{ width: 80 }}
      series={[
        { name: 'Smartphones', color: 'violet.6' },
        { name: 'Laptops', color: 'blue.6' },
        { name: 'Tablets', color: 'teal.6' },
      ]}
    />
  );
}

Sparkline component

New Sparkline component:

import { Sparkline } from '@mantine/charts';

function Demo() {
  return (
    <Sparkline
      w={200}
      h={60}
      data={[10, 20, 40, 20, 40, 10, 50]}
      curveType="linear"
      color="blue"
      fillOpacity={0.6}
      strokeWidth={2}
    />
  );
}

OKLCH colors support

You can now use OKLCH colors in theme.colors.
OKLCH color model has 88.18% browser support,
it is supported in all modern browsers. OKLCH model provides 30% more colors than HSL model and
has several other advantages.

Example of adding OKLCH color to the theme:

import { Button, createTheme, Group, MantineProvider } from '@mantine/core';

const theme = createTheme({
  colors: {
    'oklch-blue': [
      'oklch(96.27% 0.0217 238.66)',
      'oklch(92.66% 0.0429 240.01)',
      'oklch(86.02% 0.0827 241.66)',
      'oklch(78.2% 0.13 243.83)',
      'oklch(71.8% 0.1686 246.06)',
      'oklch(66.89% 0.1986 248.32)',
      'oklch(62.59% 0.2247 250.29)',
      'oklch(58.56% 0.2209 251.26)',
      'oklch(54.26% 0.2067 251.67)',
      'oklch(49.72% 0.1888 251.59)',
    ],
  },
});

function Demo() {
  return (
    <MantineProvider theme={theme}>
      <Group>
        <Button color="oklch-blue">Filled</Button>
        <Button color="oklch-blue" variant="outline">
          Outline
        </Button>
        <Button color="oklch-blue" variant="light">
          Light
        </Button>
      </Group>
    </MantineProvider>
  );
}

autoContrast

New theme.autoContrast property controls whether text color should be changed based on the given color prop
in the following components:

autoContrast can be set globally on the theme level or individually for each component via autoContrast prop,
except for Spotlight and @mantine/dates components, which only support global theme setting.

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

function Demo() {
  return (
    <>
      <Code>autoContrast: true</Code>
      <Group mt="xs" mb="lg">
        <Button color="lime.4" autoContrast>
          Lime.4 button
        </Button>
        <Button color="blue.2" autoContrast>
          Blue.2 button
        </Button>
        <Button color="orange.3" autoContrast>
          Orange.3 button
        </Button>
      </Group>

      <Code>autoContrast: false</Code>
      <Group mt="xs">
        <Button color="lime.4">Lime.4 button</Button>
        <Button color="blue.2">Blue.2 button</Button>
        <Button color="orange.3">Orange.3 button</Button>
      </Group>
    </>
  );
}

autoContrast checks whether the given color luminosity is above or below the luminanceThreshold value
and changes text color to either theme.white or theme.black accordingly:

import { Button, createTheme, MantineProvider, Stack } from '@mantine/core';

const theme = createTheme({
  autoContrast: true,
  luminanceThreshold: 0.3,
});

function Wrapper(props: any) {
  const buttons = Array(10)
    .fill(0)
    .map((_, index) => (
      <Button key={index} color={`blue.${index}`}>
        Button
      </Button>
    ));

  return (
    <MantineProvider theme={theme}>
      <Stack>{buttons}</Stack>
    </MantineProvider>
  );
}

Color functions improvements

alpha, lighten and darken functions now support CSS variables (with color-mix) and OKLCH colors.
All functions are available both in @mantine/core (.ts/.js files) and postcss-preset-mantine (.css files, requires version 1.12.0 or higher).

In .css files:

.demo-alpha {
  color: alpha(var(--mantine-color-red-4), 0.5);
  border: 1px solid alpha(#ffc, 0.2);
}

.demo-lighten-darken {
  color: lighten(var(--mantine-color-red-4), 0.5);
  border: 1px solid darken(#ffc, 0.2);
}

Will be transformed to:

.demo-alpha {
  color: color-mix(in srgb, var(--mantine-color-red-4), transparent 50%);
  border: 1px solid color-mix(in srgb, #ffc, transparent 80%);
}

.demo-lighten-darken {
  color: color-mix(in srgb, var(--mantine-color-red-4), white 50%);
  border: 1px solid color-mix(in srgb, #ffc, black 20%);
}

In .ts/.js files:

import { alpha, lighten } from '@mantine/core';

alpha('#4578FC', 0.45); // -> rgba(69, 120, 252, 0.45)
alpha('var(--mantine-color-gray-4)', 0.74);
// -> color-mix(in srgb, var(--mantine-color-gray-4), transparent 26%)

lighten('#4578FC', 0.45); // -> #a3c1ff
lighten('var(--mantine-color-gray-4)', 0.74);
// -> color-mix(in srgb, var(--mantine-color-gray-4), white 74%)

Note that alpha function is a replacement for rgba. It was renamed to
have a more clear meaning, as it can now be used with CSS variables and OKLCH colors.
rgba function is still available as an alias for alpha function.

enhanceGetInputProps

@mantine/form now supports enhanceGetInputProps. enhanceGetInputProps is a function that can be used to add additional props to the object returned by form.getInputProps.
You can define it in useForm hook options. Its argument is an object with the following properties:

  • inputProps – object returned by form.getInputProps by default
  • field – field path, first argument of form.getInputProps, for example name, user.email, users.0.name
  • options – second argument of form.getInputProps, for example { type: 'checkbox' }, can be used to pass additional
    options to enhanceGetInputProps function
  • form – form instance

Example of using enhanceGetInputProps to disable input based on field path:

import { NumberInput, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';

interface FormValues {
  name: string;
  age: number | string;
}

function Demo() {
  const form = useForm<FormValues>({
    initialValues: { name: '', age: '' },
    enhanceGetInputProps: (payload) => ({
      disabled: payload.field === 'name',
    }),
  });

  return (
    <>
      <TextInput {...form.getInputProps('name')} label="Name" placeholder="Name" />
      <NumberInput {...form.getInputProps('age')} label="Age" placeholder="Age" mt="md" />
    </>
  );
}

Example of using enhanceGetInputProps to add additional props to the input based on option passed to form.getInputProps:

import { NumberInput, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';

interface FormValues {
  name: string;
  age: number | string;
}

function Demo() {
  const form = useForm<FormValues>({
    initialValues: { name: '', age: '' },
    enhanceGetInputProps: (payload) => {
      if (payload.options.fieldType === 'name') {
        return {
          label: 'Your name',
          placeholder: 'Your name',
          withAsterisk: true,
          description: 'Your personal information is stored securely. (Just kidding!)',
        };
      }

      return {};
    },
  });

  return (
    <>
      <TextInput {...form.getInputProps('name', { fieldType: 'name' })} />
      <NumberInput {...form.getInputProps('age')} label="Age" placeholder="Age" mt="md" />
    </>
  );
}

form.initialize

@mantine/form now supports form.initialize handler.

When called form.initialize handler sets initialValues and values to the same value
and marks form as initialized. It can be used only once, next form.initialize calls
are ignored.

form.initialize is useful when you want to sync form values with backend API response:

import { Button, NumberInput, TextInput } from '@mantine/core';
import { isInRange, isNotEmpty, useForm } from '@mantine/form';

interface FormValues {
  name: string;
  age: number | string;
}

function apiRequest(): Promise<FormValues> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ name: 'John Doe', age: 25 });
    }, 1000);
  });
}

function Demo() {
  const form = useForm<FormValues>({
    initialValues: { name: '', age: 0 },
    validate: {
      name: isNotEmpty('Name is required'),
      age: isInRange({ min: 18 }, 'You must be at least 18 to register'),
    },
  });

  return (
    <>
      <TextInput {...form.getInputProps('name')} label="Name" placeholder="Name" />
      <NumberInput {...form.getInputProps('age')} label="Age" placeholder="Age" mt="md" />
      <Button onClick={() => apiRequest().then((values) => form.initialize(values))} mt="md">
        Initialize form
      </Button>
    </>
  );
}

Example with TanStack Query (react-query):

import { useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useForm } from '@mantine/form';

function Demo() {
  const query = useQuery({
    queryKey: ['current-user'],
    queryFn: () => fetch('/api/users/me').then((res) => res.json()),
  });

  const form = useForm({
    initialValues: {
      name: '',
      email: '',
    },
  });

  useEffect(() => {
    if (query.data) {
      // Even if query.data changes, form will be initialized only once
      form.initialize(query.data);
    }
  }, [query.data]);
}

Note that form.initialize will erase all values that were set before it was called.
It is usually a good idea to set readOnly or disabled on all form fields before
form.initialize is called to prevent data loss. You can implement this with
enhanceGetInputProps:

import { NumberInput, TextInput } from '@mantine/core';
import { useForm } from '@mantine/form';

interface FormValues {
  name: string;
  age: number | string;
}

function Demo() {
  const form = useForm<FormValues>({
    initialValues: { name: '', age: '' },
    enhanceGetInputProps: (payload) => {
      if (!payload.form.initialized) {
        return { disabled: true };
      }

      return {};
    },
  });

  return (
    <>
      <TextInput {...form.getInputProps('name')} label="Your name" placeholder="Your name" />
      <NumberInput {...form.getInputProps('age')} label="Age" placeholder="Age" mt="md" />
      <Button onClick={() => form.initialize({ name: 'John', age: 20 })} mt="md">
        Initialize form
      </Button>
    </>
  );
}

valibot form resolver

@mantine/form now supports validbot schema resolver:

yarn add valibot mantine-form-valibot-resolver

Basic fields validation:

import { valibotResolver } from 'mantine-form-valibot-resolver';
import { email, minLength, minValue, number, object, string } from 'valibot';
import { useForm } from '@mantine/form';

const schema = object({
  name: string([minLength(2, 'Name should have at least 2 letters')]),
  email: string([email('Invalid email')]),
  age: number([minValue(18, 'You must be at least 18 to create an account')]),
});

const form = useForm({
  initialValues: {
    name: '',
    email: '',
    age: 16,
  },
  validate: valibotResolver(schema),
});

form.validate();
form.errors;
// -> {
//  name: 'Name should have at least 2 letters',
//  email: 'Invalid email',
//  age: 'You must be at least 18 to create an account'
// }

Nested fields validation

import { valibotResolver } from 'mantine-form-valibot-resolver';
import { minLength, object, string } from 'valibot';
import { useForm } from '@mantine/form';

const nestedSchema = object({
  nested: object({
    field: string([minLength(2, 'Field should have at least 2 letters')]),
  }),
});

const form = useForm({
  initialValues: {
    nested: {
      field: '',
    },
  },
  validate: valibotResolver(nestedSchema),
});

form.validate();
form.errors;
// -> {
//  'nested.field': 'Field should have at least 2 letters',
// }

List fields validation:

import { valibotResolver } from 'mantine-form-valibot-resolver';
import { array, minLength, object, string } from 'valibot';
import { useForm } from '@mantine/form';

const listSchema = object({
  list: array(
    object({
      name: string([minLength(2, 'Name should have at least 2 letters')]),
    })
  ),
});

const form = useForm({
  initialValues: {
    list: [{ name: '' }],
  },
  validate: valibotResolver(listSchema),
});

form.validate();
form.errors;
// -> {
//  'list.0.name': 'Name should have at least 2 letters',
// }

ScrollArea scrollbars prop

ScrollArea now supports scrollbars prop, which allows controlling directions at which scrollbars should be rendered.
Supported values are x, y and xy. If scrollbars="y" is set, only the vertical scrollbar will be rendered, and it will not be possible to scroll horizontally:

import { Box, ScrollArea } from '@mantine/core';

function Demo() {
  return (
    <ScrollArea w={300} h={200} scrollbars="y">
      <Box w={600}>{/* ... content */}</Box>
    </ScrollArea>
  );
}

Title lineClamp prop

Title component now supports lineClamp prop, which allows truncating text after a specified number of lines:

import { Box, Title } from '@mantine/core';

function Demo() {
  return (
    <Box maw={400}>
      <Title order={2} lineClamp={2}>
        Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure doloremque quas dolorum. Quo
        amet earum alias consequuntur quam accusamus a quae beatae, odio, quod provident consectetur
        non repudiandae enim adipisci?
      </Title>
    </Box>
  );
}

Primary color CSS variables

CSS variables for primary color are now available, you can use the following variables in your styles:

--mantine-primary-color-0
--mantine-primary-color-1
--mantine-primary-color-2
--mantine-primary-color-3
--mantine-primary-color-4
--mantine-primary-color-5
--mantine-primary-color-6
--mantine-primary-color-7
--mantine-primary-color-8
--mantine-primary-color-9
--mantine-primary-color-contrast
--mantine-primary-color-filled
--mantine-primary-color-filled-hover
--mantine-primary-color-light
--mantine-primary-color-light-hover
--mantine-primary-color-light-color

Help center

Help center is a new website with guides, tutorials and frequently
asked questions. Currently, it has 14 questions, more FAQs will be added in the next releases.

Documentation updates

  • form.getInputProps guide now has a separate page. It describes form.getInputProps, enhanceGetInputProps and how to integrate form.getInputProps with custom inputs.
  • assignRef function documentation has been added.
  • clampUseMovePosition function documentation has been added.
  • Additional documentation about hook arguments and types has been added to use-hotkeys.
  • UseListStateHandlers type documentation has been added.
  • Functions reference page has been added. Currently, it contains all functions that are exported from @mantine/hooks package. It is planned to document functions from other packages in next releases.
  • Examples on how to change the close icon have been added to Drawer and Modal components.
  • variantColorsResolver demos have been added to ActionIcon, ThemeIcon and Badge components.

Other changes

  • RichTextEditor no longer depends on @tabler/icons package. It is no longer required to install @tabler/icons package to use RichTextEditor component. Icons used in the editor are now a part of the @mantine/tiptap package. This change improves bundling performance in several cases (mostly when using RichTextEditor in Next.js apps).
  • Badge component now supports circle prop which makes the badge round.
  • You can now reference theme values in ff style prop with mono, text and heading values: <Box ff="mono" />.
  • RichTextEditor now has RichTextEditor.Undo and RichTextEditor.Redo controls.
  • A new luminance color function was added. It returns color luminance as a number between 0 and 1.
  • All components now support new flex style prop which allows setting flex CSS property on the root element.
  • Collapse markup was reduced to single element, it can now be used in contexts that were previously not supported, for example, table rows.
  • stepHoldDelay and stepHoldInterval props have been added to NumberInput.
  • mantine-form-zod-resolver now supports errorPriority configuration which allows controlling the order of errors specified in the schema. This feature requires updating mantine-form-zod-resolver to version 1.1.0 or higher.
  • CloseButton now supports icon prop, which allows overriding default icon. It is useful when it is not possible to replace CloseButton, for example, in Drawer component.
  • Select component now calls onChange with an additional argument – option object. It contains label, value and optional disabled properties.
  • It is now possible to define CSS variables in styles prop of all components.
  • New use-in-viewport hook
  • All Vite templates have been updated to Vite 5.0 and Vitest 1.0