Mantine Versions Save

A fully featured React components library

7.10.2

2 weeks ago

What's Changed

  • [@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)

New Contributors

Full Changelog: https://github.com/mantinedev/mantine/compare/7.10.1...7.10.2

7.10.1

1 month ago

What's Changed

  • [@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 element

New Contributors

Full Changelog: https://github.com/mantinedev/mantine/compare/7.10.0...7.10.1

7.10.0

1 month ago

View changelog with demos on mantine.dev website

Tree component

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

form.getInputNode

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

Container queries in SimpleGrid

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

Checkbox and Radio indicators

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

Checkbox and Radio cards

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

bd style prop

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" />;
}

7.9.2

1 month ago

What's Changed

  • [@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)

New Contributors

Full Changelog: https://github.com/mantinedev/mantine/compare/7.9.1...7.9.2

7.9.1

1 month ago

What's Changed

  • [@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)

New Contributors

Full Changelog: https://github.com/mantinedev/mantine/compare/7.9.0...7.9.1

7.9.0

2 months ago

View changelog with demos on mantine.dev website

@mantine/emotion package

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

React 18.3 support

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.

use-field hook

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

Custom PostCSS mixins

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

use-matches hook

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:

  • Starting from theme.breakpoints.lg, color will be red.9
  • Between theme.breakpoints.sm and theme.breakpoints.lg, color will be orange.9
  • Below 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 value label

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' },
      ]}
    />
  );
}

Documentation updates

Other changes

  • Advanced templates now include GitHub workflows to run tests on CI
  • AspectRatio component has been migrated to aspect-ratio CSS property

7.8.1

2 months ago

Notes

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.

What's Changed

  • [@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)

New Contributors

Full Changelog: https://github.com/mantinedev/mantine/compare/7.8.0...7.8.1

7.8.0

2 months ago

View changelog with demos on mantine.dev website

Auto convert px to rem in .css files

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:

  • Values in calc(), var(), clamp() and url() functions
  • Values in content property
  • Values that contain rgb(), rgba(), hsl(), hsla() colors

If you want to convert above values to rem units, use rem function manually.

Uncontrolled form mode

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

form.getValues

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

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

Customize Popover middlewares

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

use-fetch hook

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

use-map hook

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

use-set hook

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

use-debounced-callback hook

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

use-throttled-state hook

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

use-throttled-value hook

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

use-throttled-callback hook

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

use-orientation hook

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

use-is-first-render hook

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

Documentation updates

Other changes

  • NumberInput now supports withKeyboardEvents={false} to disable up/down arrow keys handling
  • Popover shift middleware now has default padding of 5px to offset dropdown near the edge of the viewport

7.7.2

2 months ago

What's Changed

  • [@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)

New Contributors

Full Changelog: https://github.com/mantinedev/mantine/compare/7.7.1...7.7.2

7.7.1

3 months ago

What's Changed

  • [@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)

New Contributors

Full Changelog: https://github.com/mantinedev/mantine/compare/7.7.0...7.7.1