Version v7.16.0

Release date

use-scroll-spy hook

New use-scroll-spy hook tracks scroll position and returns index of the element that is currently in the viewport. It is useful for creating table of contents components (like in mantine.dev sidebar on the right side) and similar features.

Scroll to heading:

    import { Text, UnstyledButton } from '@mantine/core';
    import { useScrollSpy } from '@mantine/hooks';
    
    function Demo() {
      const spy = useScrollSpy({
        selector: '#mdx :is(h1, h2, h3, h4, h5, h6)',
      });
    
      const headings = spy.data.map((heading, index) => (
        <li
          key={heading.id}
          style={{
            listStylePosition: 'inside',
            paddingInlineStart: heading.depth * 20,
            background: index === spy.active ? 'var(--mantine-color-blue-light)' : undefined,
          }}
        >
          <UnstyledButton onClick={() => heading.getNode().scrollIntoView()}>
            {heading.value}
          </UnstyledButton>
        </li>
      ));
    
      return (
        <div>
          <Text>Scroll to heading:</Text>
          <ul style={{ margin: 0, padding: 0 }}>{headings}</ul>
        </div>
      );
    }

    TableOfContents component

    New TableOfContents component is built on top of use-scroll-spy hook and can be used to create table of contents components like the one on the right side of mantine.dev documentation sidebar:

    Variant
    Color
    Size
    Radius
    import { TableOfContents } from '@mantine/core';
    
    function Demo() {
      return (
        <TableOfContents
          variant="filled"
          color="blue"
          size="sm"
          radius="sm"
          scrollSpyOptions={{
            selector: '#mdx :is(h1, h2, h3, h4, h5, h6)',
          }}
          getControlProps={({ data }) => ({
            onClick: () => data.getNode().scrollIntoView(),
            children: data.value,
          })}
        />
      );
    }

    Input.ClearButton component

    New Input.ClearButton component can be used to add clear button to custom inputs based on Input component. size of the clear button is automatically inherited from the input:

    Size
    import { Input } from '@mantine/core';
    
    function Demo(){
      const [value, setValue] = useState('clearable');
    
      return (
        <Input
          placeholder="Clearable input"
          value={value}
          onChange={(event) => setValue(event.currentTarget.value)}
          rightSection={value !== '' ? <Input.ClearButton onClick={() => setValue('')} /> : undefined}
          rightSectionPointerEvents="auto"
          size="sm"
        />
      );
    }

    Popover with overlay

    Popover and other components based on it now support withOverlay prop:

    import { Popover, Avatar, Text, Group, Anchor, Stack } from '@mantine/core';
    
    function Demo() {
      return (
        <Popover
          width={320}
          shadow="md"
          withArrow
          withOverlay
          overlayProps={{ zIndex: 10000, blur: '8px' }}
          zIndex={10001}
        >
          <Popover.Target>
            <UnstyledButton style={{ zIndex: 10001, position: 'relative' }}>
              <Avatar src="https://avatars.githubusercontent.com/u/79146003?s=200&v=4" radius="xl" />
            </UnstyledButton>
          </Popover.Target>
          <Popover.Dropdown>
            <Group>
              <Avatar src="https://avatars.githubusercontent.com/u/79146003?s=200&v=4" radius="xl" />
              <Stack gap={5}>
                <Text size="sm" fw={700} style={{ lineHeight: 1 }}>
                  Mantine
                </Text>
                <Anchor href="https://x.com/mantinedev" c="dimmed" size="xs" style={{ lineHeight: 1 }}>
                  @mantinedev
                </Anchor>
              </Stack>
            </Group>
    
            <Text size="sm" mt="md">
              Customizable React components and hooks library with focus on usability, accessibility and
              developer experience
            </Text>
    
            <Group mt="md" gap="xl">
              <Text size="sm">
                <b>0</b> Following
              </Text>
              <Text size="sm">
                <b>1,174</b> Followers
              </Text>
            </Group>
          </Popover.Dropdown>
        </Popover>
      );
    }

    Container queries in Carousel

    You can now use container queries in Carousel component. With container queries, all responsive values are 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 { Carousel } from '@mantine/carousel';
    
    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%',
            minWidth: 250,
            padding: 10,
          }}
        >
          <Carousel
            withIndicators
            height={200}
            type="container"
            slideSize={{ base: '100%', '300px': '50%', '500px': '33.333333%' }}
            slideGap={{ base: 0, '300px': 'md', '500px': 'lg' }}
            loop
            align="start"
          >
            <Carousel.Slide>1</Carousel.Slide>
            <Carousel.Slide>2</Carousel.Slide>
            <Carousel.Slide>3</Carousel.Slide>
            {/* ...other slides */}
          </Carousel>
        </div>
      );
    }

    RangeSlider restrictToMarks

    RangeSlider component now supports restrictToMarks prop:

    import { RangeSlider, Slider, Stack } from '@mantine/core';
    
    function Demo() {
      return (
        <Stack>
          <Slider
            restrictToMarks
            defaultValue={25}
            marks={Array.from({ length: 5 }).map((_, index) => ({ value: index * 25 }))}
          />
    
          <RangeSlider
            restrictToMarks
            defaultValue={[5, 15]}
            marks={[
              { value: 5 },
              { value: 15 },
              { value: 25 },
              { value: 35 },
              { value: 70 },
              { value: 80 },
              { value: 90 },
            ]}
          />
        </Stack>
      );
    }

    Pagination withPages prop

    Pagination component now supports withPages prop which allows hiding pages controls and displaying only previous and next buttons:

    Showing 1 – 10 of 145

    import { useState } from 'react';
    import { Group, Pagination, Text } from '@mantine/core';
    
    const limit = 10;
    const total = 145;
    const totalPages = Math.ceil(total / limit);
    
    function Demo() {
      const [page, setPage] = useState(1);
      const message = `Showing ${limit * (page - 1) + 1} – ${Math.min(total, limit * page)} of ${total}`;
    
      return (
        <Group justify="flex-end">
          <Text size="sm">{message}</Text>
          <Pagination total={totalPages} value={page} onChange={setPage} />
        </Group>
      );
    }

    useForm touchTrigger option

    use-form hook now supports touchTrigger option that allows customizing events that change touched state. It accepts two options:

    • change (default) – field will be considered touched when its value changes or it has been focused
    • focus – field will be considered touched only when it has been focused

    Example of using focus trigger:

    import { useForm } from '@mantine/form';
    
    const form = useForm({
      mode: 'uncontrolled',
      initialValues: { a: 1 },
      touchTrigger: 'focus',
    });
    
    form.isTouched('a'); // -> false
    form.setFieldValue('a', 2);
    form.isTouched('a'); // -> false
    
    // onFocus is called automatically when the user focuses the field
    form.getInputProps('a').onFocus();
    form.isTouched('a'); // -> true

    Help Center updates

    Other changes