Stars
Mantine UI: A Complete Guide to Building Modern Web Interfaces
calendar30 Sep 2024
read20 minute read

Mantine UI: A Complete Guide to Building Modern Web Interfaces

Mantine UI is a comprehensive React component library designed to help developers build feature-rich and responsive web applications with minimal effort. In this blog, we will explore the core features of Mantine UI, how it compares to other libraries, and how you can use it to improve your web development projects.

Why Consider Using Mantine UI?

  1. Speed Up Development: With over 123 responsive components, Mantine UI allows you to build websites quickly using pre-made, customizable elements. 
  2. Flexibility: All components are built with the Mantine theme in mind, making it easy to change colors, fonts, shadows, and other properties to match your design needs.
  3. Light and Dark Mode Support: Most components come with built-in support for both light and dark themes, so you don’t need to make any additional adjustments to accommodate different color schemes.
  4. Free and Open Source: Mantine UI is free, open-source, and licensed under MIT, meaning you can use it in any project, including commercial ones, without any restrictions.

Types of Components Available in Mantine UI

Mantine UI provides a rich selection of components tailored for different use cases, allowing developers to quickly build modern and responsive interfaces. Here, we break down three major categories of components:

1. Application UI

mantinue ui tutorial mantine ui application ui

Application UI components are the building blocks for creating feature-rich user interfaces. These components include everything from navigational elements to interactive forms. 

• Example: Navbar With Nested Links

Code: NavbarNested.tsx

import { Group, Code, ScrollArea, rem } from '@mantine/core';
import {
  IconNotes,
  IconCalendarStats,
  IconGauge,
  IconPresentationAnalytics,
  IconFileAnalytics,
  IconAdjustments,
  IconLock,
} from '@tabler/icons-react';
import { UserButton } from '../UserButton/UserButton';
import { LinksGroup } from '../NavbarLinksGroup/NavbarLinksGroup';
import { Logo } from './Logo';
import classes from './NavbarNested.module.css';


const mockdata = [
  { label: 'Dashboard', icon: IconGauge },
  {
    label: 'Market news',
    icon: IconNotes,
    initiallyOpened: true,
    links: [
      { label: 'Overview', link: '/' },
      { label: 'Forecasts', link: '/' },
      { label: 'Outlook', link: '/' },
      { label: 'Real time', link: '/' },
    ],
  },
  {
    label: 'Releases',
    icon: IconCalendarStats,
    links: [
      { label: 'Upcoming releases', link: '/' },
      { label: 'Previous releases', link: '/' },
      { label: 'Releases schedule', link: '/' },
    ],
  },
  { label: 'Analytics', icon: IconPresentationAnalytics },
  { label: 'Contracts', icon: IconFileAnalytics },
  { label: 'Settings', icon: IconAdjustments },
  {
    label: 'Security',
    icon: IconLock,
    links: [
      { label: 'Enable 2FA', link: '/' },
      { label: 'Change password', link: '/' },
      { label: 'Recovery codes', link: '/' },
    ],
  },
];


export function NavbarNested() {
  const links = mockdata.map((item) => <LinksGroup {...item} key={item.label} />);


  return (
    <nav className={classes.navbar}>
      <div className={classes.header}>
        <Group justify="space-between">
          <Logo style={{ width: rem(120) }} />
          <Code fw={700}>v3.1.2</Code>
        </Group>
      </div>


      <ScrollArea className={classes.links}>
        <div className={classes.linksInner}>{links}</div>
      </ScrollArea>


      <div className={classes.footer}>
        <UserButton />
      </div>
    </nav>
  );
}

Code: NavbarNested.module.css

.navbar {
  background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
  height: rem(800px);
  width: rem(300px);
  padding: var(--mantine-spacing-md);
  padding-bottom: 0;
  display: flex;
  flex-direction: column;
  border-right: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}


.header {
  padding: var(--mantine-spacing-md);
  padding-top: 0;
  margin-left: calc(var(--mantine-spacing-md) * -1);
  margin-right: calc(var(--mantine-spacing-md) * -1);
  color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
  border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}


.links {
  flex: 1;
  margin-left: calc(var(--mantine-spacing-md) * -1);
  margin-right: calc(var(--mantine-spacing-md) * -1);
}


.linksInner {
  padding-top: var(--mantine-spacing-xl);
  padding-bottom: var(--mantine-spacing-xl);
}


.footer {
  margin-left: calc(var(--mantine-spacing-md) * -1);
  margin-right: calc(var(--mantine-spacing-md) * -1);
  border-top: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}

• Example: Footer With Links

Code: FooterLinks.tsx

import { Text, Container, ActionIcon, Group, rem } from '@mantine/core';
import { IconBrandTwitter, IconBrandYoutube, IconBrandInstagram } from '@tabler/icons-react';
import { MantineLogo } from '@mantinex/mantine-logo';
import classes from './FooterLinks.module.css';


const data = [
  {
    title: 'About',
    links: [
      { label: 'Features', link: '#' },
      { label: 'Pricing', link: '#' },
      { label: 'Support', link: '#' },
      { label: 'Forums', link: '#' },
    ],
  },
  {
    title: 'Project',
    links: [
      { label: 'Contribute', link: '#' },
      { label: 'Media assets', link: '#' },
      { label: 'Changelog', link: '#' },
      { label: 'Releases', link: '#' },
    ],
  },
  {
    title: 'Community',
    links: [
      { label: 'Join Discord', link: '#' },
      { label: 'Follow on Twitter', link: '#' },
      { label: 'Email newsletter', link: '#' },
      { label: 'GitHub discussions', link: '#' },
    ],
  },
];


export function FooterLinks() {
  const groups = data.map((group) => {
    const links = group.links.map((link, index) => (
      <Text<'a'>
        key={index}
        className={classes.link}
        component="a"
        href={link.link}
        onClick={(event) => event.preventDefault()}
      >
        {link.label}
      </Text>
    ));


    return (
      <div className={classes.wrapper} key={group.title}>
        <Text className={classes.title}>{group.title}</Text>
        {links}
      </div>
    );
  });


  return (
    <footer className={classes.footer}>
      <Container className={classes.inner}>
        <div className={classes.logo}>
          <MantineLogo size={30} />
          <Text size="xs" c="dimmed" className={classes.description}>
            Build fully functional accessible web applications faster than ever
          </Text>
        </div>
        <div className={classes.groups}>{groups}</div>
      </Container>
      <Container className={classes.afterFooter}>
        <Text c="dimmed" size="sm">
          © 2020 mantine.dev. All rights reserved.
        </Text>


        <Group gap={0} className={classes.social} justify="flex-end" wrap="nowrap">
          <ActionIcon size="lg" color="gray" variant="subtle">
            <IconBrandTwitter style={{ width: rem(18), height: rem(18) }} stroke={1.5} />
          </ActionIcon>
          <ActionIcon size="lg" color="gray" variant="subtle">
            <IconBrandYoutube style={{ width: rem(18), height: rem(18) }} stroke={1.5} />
          </ActionIcon>
          <ActionIcon size="lg" color="gray" variant="subtle">
            <IconBrandInstagram style={{ width: rem(18), height: rem(18) }} stroke={1.5} />
          </ActionIcon>
        </Group>
      </Container>
    </footer>
  );
}

Code: FooterLinks.module.css

.footer {
  margin-top: rem(120px);
  padding-top: calc(var(--mantine-spacing-xl) * 2);
  padding-bottom: calc(var(--mantine-spacing-xl) * 2);
  background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
  border-top: rem(1px) solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
}


.logo {
  max-width: rem(200px);


  @media (max-width: $mantine-breakpoint-sm) {
    display: flex;
    flex-direction: column;
    align-items: center;
  }
}


.description {
  margin-top: rem(5px);


  @media (max-width: $mantine-breakpoint-sm) {
    margin-top: var(--mantine-spacing-xs);
    text-align: center;
  }
}


.inner {
  display: flex;
  justify-content: space-between;


  @media (max-width: $mantine-breakpoint-sm) {
    flex-direction: column;
    align-items: center;
  }
}


.groups {
  display: flex;
  flex-wrap: wrap;


  @media (max-width: $mantine-breakpoint-sm) {
    display: none;
  }
}


.wrapper {
  width: rem(160px);
}


.link {
  display: block;
  color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-1));
  font-size: var(--mantine-font-size-sm);
  padding-top: rem(3px);
  padding-bottom: rem(3px);


  &:hover {
    text-decoration: underline;
  }
}


.title {
  font-size: var(--mantine-font-size-lg);
  font-weight: 700;
  font-family:
    Greycliff CF,
    var(--mantine-font-family);
  margin-bottom: calc(var(--mantine-spacing-xs) / 2);
  color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
}


.afterFooter {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: var(--mantine-spacing-xl);
  padding-top: var(--mantine-spacing-xl);
  padding-bottom: var(--mantine-spacing-xl);
  border-top: rem(1px) solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));


  @media (max-width: $mantine-breakpoint-sm) {
    flex-direction: column;
  }
}


.social {
  @media (max-width: $mantine-breakpoint-sm) {
    margin-top: var(--mantine-spacing-xs);
  }
}

• Example: Carousel With Cards

Code: CardsCarousel.tsx

import { Carousel } from '@mantine/carousel';
import { useMediaQuery } from '@mantine/hooks';
import { Paper, Text, Title, Button, useMantineTheme, rem } from '@mantine/core';
import classes from './CardsCarousel.module.css';


interface CardProps {
  image: string;
  title: string;
  category: string;
}


function Card({ image, title, category }: CardProps) {
  return (
    <Paper
      shadow="md"
      p="xl"
      radius="md"
      style={{ backgroundImage: `url(${image})` }}
      className={classes.card}
    >
      <div>
        <Text className={classes.category} size="xs">
          {category}
        </Text>
        <Title order={3} className={classes.title}>
          {title}
        </Title>
      </div>
      <Button variant="white" color="dark">
        Read article
      </Button>
    </Paper>
  );
}


const data = [
  {
    image:
      'https://images.unsplash.com/photo-1508193638397-1c4234db14d8?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
    title: 'Best forests to visit in North America',
    category: 'nature',
  },
  {
    image:
      'https://images.unsplash.com/photo-1559494007-9f5847c49d94?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
    title: 'Hawaii beaches review: better than you think',
    category: 'beach',
  },
  {
    image:
      'https://images.unsplash.com/photo-1608481337062-4093bf3ed404?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
    title: 'Mountains at night: 12 best locations to enjoy the view',
    category: 'nature',
  },
  {
    image:
      'https://images.unsplash.com/photo-1507272931001-fc06c17e4f43?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
    title: 'Aurora in Norway: when to visit for best experience',
    category: 'nature',
  },
  {
    image:
      'https://images.unsplash.com/photo-1510798831971-661eb04b3739?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
    title: 'Best places to visit this winter',
    category: 'tourism',
  },
  {
    image:
      'https://images.unsplash.com/photo-1582721478779-0ae163c05a60?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=400&q=80',
    title: 'Active volcanos reviews: travel at your own risk',
    category: 'nature',
  },
];


export function CardsCarousel() {
  const theme = useMantineTheme();
  const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
  const slides = data.map((item) => (
    <Carousel.Slide key={item.title}>
      <Card {...item} />
    </Carousel.Slide>
  ));


  return (
    <Carousel
      slideSize={{ base: '100%', sm: '50%' }}
      slideGap={{ base: rem(2), sm: 'xl' }}
      align="start"
      slidesToScroll={mobile ? 1 : 2}
    >
      {slides}
    </Carousel>
  );
}

Code: CardsCarousel.module.css

.card {
  height: rem(440px);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  background-size: cover;
  background-position: center;
}


.title {
  font-family:
    Greycliff CF,
    sans-serif;
  font-weight: 900;
  color: var(--mantine-color-white);
  line-height: 1.2;
  font-size: rem(32px);
  margin-top: var(--mantine-spacing-xs);
}


.category {
  color: var(--mantine-color-white);
  opacity: 0.7;
  font-weight: 700;
  text-transform: uppercase;
}

2. Page Sections

mantinue ui tutorial mantine ui page sections

Page sections allow you to create distinct, structured parts of a webpage such as headers, footers, and hero sections. 

• Example: Hero Section With Background Image

Code: HeroContent.tsx

import { Overlay, Container, Title, Button, Text } from '@mantine/core';
import classes from './HeroContentLeft.module.css';


export function HeroContentLeft() {
  return (
    <div className={classes.hero}>
      <Overlay
        gradient="linear-gradient(180deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, .65) 40%)"
        opacity={1}
        zIndex={0}
      />
      <Container className={classes.container} size="md">
        <Title className={classes.title}>A fully featured React components library</Title>
        <Text className={classes.description} size="xl" mt="xl">
          Build fully functional accessible web applications faster than ever – Mantine includes
          more than 120 customizable components and hooks to cover you in any situation
        </Text>


        <Button variant="gradient" size="xl" radius="xl" className={classes.control}>
          Get started
        </Button>
      </Container>
    </div>
  );
}

Code: HeroContent.module.css

.hero {
  position: relative;
  background-image: url(https://images.unsplash.com/photo-1519389950473-47ba0277781c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80);
  background-size: cover;
  background-position: center;
}


.container {
  height: rem(700px);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: flex-start;
  padding-bottom: calc(var(--mantine-spacing-xl) * 6);
  z-index: 1;
  position: relative;


  @media (max-width: $mantine-breakpoint-sm) {
    height: rem(500px);
    padding-bottom: calc(var(--mantine-spacing-xl) * 3);
  }
}


.title {
  color: var(--mantine-color-white);
  font-size: rem(60px);
  font-weight: 900;
  line-height: 1.1;


  @media (max-width: $mantine-breakpoint-sm) {
    font-size: rem(40px);
    line-height: 1.2;
  }


  @media (max-width: $mantine-breakpoint-xs) {
    font-size: rem(28px);
    line-height: 1.3;
  }
}


.description {
  color: var(--mantine-color-white);
  max-width: rem(600px);


  @media (max-width: $mantine-breakpoint-sm) {
    max-width: 100%;
    font-size: var(--mantine-font-size-sm);
  }
}


.control {
  margin-top: calc(var(--mantine-spacing-xl) * 1.5);


  @media (max-width: $mantine-breakpoint-sm) {
    width: 100%;
  }
}

• Example: Authentication Form

Code: AuthForm.tsx

import { useToggle, upperFirst } from '@mantine/hooks';
import { useForm } from '@mantine/form';
import {
  TextInput,
  PasswordInput,
  Text,
  Paper,
  Group,
  PaperProps,
  Button,
  Divider,
  Checkbox,
  Anchor,
  Stack,
} from '@mantine/core';
import { GoogleButton } from './GoogleButton';
import { TwitterButton } from './TwitterButton';


export function AuthenticationForm(props: PaperProps) {
  const [type, toggle] = useToggle(['login', 'register']);
  const form = useForm({
    initialValues: {
      email: '',
      name: '',
      password: '',
      terms: true,
    },


    validate: {
      email: (val) => (/^\S+@\S+$/.test(val) ? null : 'Invalid email'),
      password: (val) => (val.length <= 6 ? 'Password should include at least 6 characters' : null),
    },
  });


  return (
    <Paper radius="md" p="xl" withBorder {...props}>
      <Text size="lg" fw={500}>
        Welcome to Mantine, {type} with
      </Text>


      <Group grow mb="md" mt="md">
        <GoogleButton radius="xl">Google</GoogleButton>
        <TwitterButton radius="xl">Twitter</TwitterButton>
      </Group>


      <Divider label="Or continue with email" labelPosition="center" my="lg" />


      <form onSubmit={form.onSubmit(() => {})}>
        <Stack>
          {type === 'register' && (
            <TextInput
              label="Name"
              placeholder="Your name"
              value={form.values.name}
              onChange={(event) => form.setFieldValue('name', event.currentTarget.value)}
              radius="md"
            />
          )}


          <TextInput
            required
            label="Email"
            placeholder="hello@mantine.dev"
            value={form.values.email}
            onChange={(event) => form.setFieldValue('email', event.currentTarget.value)}
            error={form.errors.email && 'Invalid email'}
            radius="md"
          />


          <PasswordInput
            required
            label="Password"
            placeholder="Your password"
            value={form.values.password}
            onChange={(event) => form.setFieldValue('password', event.currentTarget.value)}
            error={form.errors.password && 'Password should include at least 6 characters'}
            radius="md"
          />


          {type === 'register' && (
            <Checkbox
              label="I accept terms and conditions"
              checked={form.values.terms}
              onChange={(event) => form.setFieldValue('terms', event.currentTarget.checked)}
            />
          )}
        </Stack>


        <Group justify="space-between" mt="xl">
          <Anchor component="button" type="button" c="dimmed" onClick={() => toggle()} size="xs">
            {type === 'register'
              ? 'Already have an account? Login'
              : "Don't have an account? Register"}
          </Anchor>
          <Button type="submit" radius="xl">
            {upperFirst(type)}
          </Button>
        </Group>
      </form>
    </Paper>
  );
}

Code: GoogleButton.tsx

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


function GoogleIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      preserveAspectRatio="xMidYMid"
      viewBox="0 0 256 262"
      style={{ width: '0.9rem', height: '0.9rem' }}
      {...props}
    >
      <path
        fill="#4285F4"
        d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
      />
      <path
        fill="#34A853"
        d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
      />
      <path
        fill="#FBBC05"
        d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
      />
      <path
        fill="#EB4335"
        d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
      />
    </svg>
  );
}


export function GoogleButton(props: ButtonProps & React.ComponentPropsWithoutRef<'button'>) {
  return <Button leftSection={<GoogleIcon />} variant="default" {...props} />;
}

Code: TwitterButton.tsx

import { Button, ButtonProps } from '@mantine/core';
import { TwitterIcon } from '@mantinex/dev-icons';


export function TwitterButton(props: ButtonProps & React.ComponentPropsWithoutRef<'button'>) {
  return (
    <Button
      leftSection={<TwitterIcon style={{ width: '1rem', height: '1rem' }} color="#00ACEE" />}
      variant="default"
      {...props}
    />
  );
}

• Example: 404 Page

Code: NotFoundBackground.tsx

import { Container, Title, Text, Button, Group } from '@mantine/core';
import { Illustration } from './Illustration';
import classes from './NothingFoundBackground.module.css';


export function NothingFoundBackground() {
  return (
    <Container className={classes.root}>
      <div className={classes.inner}>
        <Illustration className={classes.image} />
        <div className={classes.content}>
          <Title className={classes.title}>Nothing to see here</Title>
          <Text c="dimmed" size="lg" ta="center" className={classes.description}>
            Page you are trying to open does not exist. You may have mistyped the address, or the
            page has been moved to another URL. If you think this is an error contact support.
          </Text>
          <Group justify="center">
            <Button size="md">Take me back to home page</Button>
          </Group>
        </div>
      </div>
    </Container>
  );
}

Code: Illustration.tsx

export function Illustration(props: React.ComponentPropsWithoutRef<'svg'>) {
  return (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 362 145" {...props}>
      <path
        fill="currentColor"
        d="M62.6 142c-2.133 0-3.2-1.067-3.2-3.2V118h-56c-2 0-3-1-3-3V92.8c0-1.333.4-2.733 1.2-4.2L58.2 4c.8-1.333 2.067-2 3.8-2h28c2 0 3 1 3 3v85.4h11.2c.933 0 1.733.333 2.4 1 .667.533 1 1.267 1 2.2v21.2c0 .933-.333 1.733-1 2.4-.667.533-1.467.8-2.4.8H93v20.8c0 2.133-1.067 3.2-3.2 3.2H62.6zM33 90.4h26.4V51.2L33 90.4zM181.67 144.6c-7.333 0-14.333-1.333-21-4-6.666-2.667-12.866-6.733-18.6-12.2-5.733-5.467-10.266-13-13.6-22.6-3.333-9.6-5-20.667-5-33.2 0-12.533 1.667-23.6 5-33.2 3.334-9.6 7.867-17.133 13.6-22.6 5.734-5.467 11.934-9.533 18.6-12.2 6.667-2.8 13.667-4.2 21-4.2 7.467 0 14.534 1.4 21.2 4.2 6.667 2.667 12.8 6.733 18.4 12.2 5.734 5.467 10.267 13 13.6 22.6 3.334 9.6 5 20.667 5 33.2 0 12.533-1.666 23.6-5 33.2-3.333 9.6-7.866 17.133-13.6 22.6-5.6 5.467-11.733 9.533-18.4 12.2-6.666 2.667-13.733 4-21.2 4zm0-31c9.067 0 15.6-3.733 19.6-11.2 4.134-7.6 6.2-17.533 6.2-29.8s-2.066-22.2-6.2-29.8c-4.133-7.6-10.666-11.4-19.6-11.4-8.933 0-15.466 3.8-19.6 11.4-4 7.6-6 17.533-6 29.8s2 22.2 6 29.8c4.134 7.467 10.667 11.2 19.6 11.2zM316.116 142c-2.134 0-3.2-1.067-3.2-3.2V118h-56c-2 0-3-1-3-3V92.8c0-1.333.4-2.733 1.2-4.2l56.6-84.6c.8-1.333 2.066-2 3.8-2h28c2 0 3 1 3 3v85.4h11.2c.933 0 1.733.333 2.4 1 .666.533 1 1.267 1 2.2v21.2c0 .933-.334 1.733-1 2.4-.667.533-1.467.8-2.4.8h-11.2v20.8c0 2.133-1.067 3.2-3.2 3.2h-27.2zm-29.6-51.6h26.4V51.2l-26.4 39.2z"
      />
    </svg>
  );
}

Code: NotFoundBackground.module.css

.root {
  padding-top: rem(80px);
  padding-bottom: rem(80px);
}


.inner {
  position: relative;
}


.image {
  position: absolute;
  inset: 0;
  opacity: 0.75;
  color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
}


.content {
  padding-top: rem(220px);
  position: relative;
  z-index: 1;


  @media (max-width: $mantine-breakpoint-sm) {
    padding-top: rem(120px);
  }
}


.title {
  font-family:
    Greycliff CF,
    var(--mantine-font-family);
  text-align: center;
  font-weight: 900;
  font-size: rem(38px);


  @media (max-width: $mantine-breakpoint-sm) {
    font-size: rem(32px);
  }
}


.description {
  max-width: rem(540px);
  margin: auto;
  margin-top: var(--mantine-spacing-xl);
  margin-bottom: calc(var(--mantine-spacing-xl) * 1.5);
}

3. Blog UI

mantinue ui tutorial mantine ui blog ui

Blog UI components are designed to enhance the readability and structure of blog content. These components focus on making your blog visually appealing while maintaining a smooth user experience.

• Example: Article Cards

Code: ArticlesCardsGrid.tsx

import { SimpleGrid, Card, Image, Text, Container, AspectRatio } from '@mantine/core';
import classes from './ArticlesCardsGrid.module.css';


const mockdata = [
  {
    title: 'Top 10 places to visit in Norway this summer',
    image:
      'https://images.unsplash.com/photo-1527004013197-933c4bb611b3?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=720&q=80',
    date: 'August 18, 2022',
  },
  {
    title: 'Best forests to visit in North America',
    image:
      'https://images.unsplash.com/photo-1448375240586-882707db888b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=720&q=80',
    date: 'August 27, 2022',
  },
  {
    title: 'Hawaii beaches review: better than you think',
    image:
      'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=720&q=80',
    date: 'September 9, 2022',
  },
  {
    title: 'Mountains at night: 12 best locations to enjoy the view',
    image:
      'https://images.unsplash.com/photo-1519681393784-d120267933ba?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=720&q=80',
    date: 'September 12, 2022',
  },
];


export function ArticlesCardsGrid() {
  const cards = mockdata.map((article) => (
    <Card key={article.title} p="md" radius="md" component="a" href="#" className={classes.card}>
      <AspectRatio ratio={1920 / 1080}>
        <Image src={article.image} />
      </AspectRatio>
      <Text c="dimmed" size="xs" tt="uppercase" fw={700} mt="md">
        {article.date}
      </Text>
      <Text className={classes.title} mt={5}>
        {article.title}
      </Text>
    </Card>
  ));


  return (
    <Container py="xl">
      <SimpleGrid cols={{ base: 1, sm: 2 }}>{cards}</SimpleGrid>
    </Container>
  );
}

Code: ArticlesCardsGrid.module.css

.card {
  transition:
    transform 150ms ease,
    box-shadow 150ms ease;


  &:hover {
    transform: scale(1.01);
    box-shadow: var(--mantine-shadow-md);
  }
}


.title {
  font-family: 'Greycliff CF', var(--mantine-font-family);
  font-weight: bold;
}

• Example: Table of Contents

Code: TableOfContents.tsx

import cx from 'clsx';
import { Box, Text, Group, rem } from '@mantine/core';
import { IconListSearch } from '@tabler/icons-react';
import classes from './TableOfContents.module.css';


const links = [
  { label: 'Usage', link: '#usage', order: 1 },
  { label: 'Position and placement', link: '#position', order: 1 },
  { label: 'With other overlays', link: '#overlays', order: 1 },
  { label: 'Manage focus', link: '#focus', order: 1 },
  { label: 'Examples', link: '#1', order: 1 },
  { label: 'Show on focus', link: '#2', order: 2 },
  { label: 'Show on hover', link: '#3', order: 2 },
  { label: 'With form', link: '#4', order: 2 },
];


const active = '#overlays';


export function TableOfContents() {
  const items = links.map((item) => (
    <Box<'a'>
      component="a"
      href={item.link}
      onClick={(event) => event.preventDefault()}
      key={item.label}
      className={cx(classes.link, { [classes.linkActive]: active === item.link })}
      style={{ paddingLeft: `calc(${item.order} * var(--mantine-spacing-md))` }}
    >
      {item.label}
    </Box>
  ));


  return (
    <div>
      <Group mb="md">
        <IconListSearch style={{ width: rem(18), height: rem(18) }} stroke={1.5} />
        <Text>Table of contents</Text>
      </Group>
      {items}
    </div>
  );
}

Code: TableOfContents.module.css

.link {
  display: block;
  text-decoration: none;
  color: var(--mantine-color-text);
  line-height: 1.2;
  font-size: var(--mantine-font-size-sm);
  padding: var(--mantine-spacing-xs);
  border-top-right-radius: var(--mantine-radius-sm);
  border-bottom-right-radius: var(--mantine-radius-sm);
  border-left: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));


  @mixin hover {
    background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
  }
}


.linkActive {
  font-weight: 500;
  border-left-color: light-dark(var(--mantine-color-blue-6), var(--mantine-color-blue-4));
  color: light-dark(var(--mantine-color-blue-6), var(--mantine-color-blue-4));


  &,
  &:hover {
    background-color: var(--mantine-color-blue-light) !important;
  }
}

• Example: Comment With HTML Links

Code: CommentHtml.tsx

import { Text, Avatar, Group, TypographyStylesProvider, Paper } from '@mantine/core';
import classes from './CommentHtml.module.css';


export function CommentHtml() {
  return (
    <Paper withBorder radius="md" className={classes.comment}>
      <Group>
        <Avatar
          src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-2.png"
          alt="Jacob Warnhalter"
          radius="xl"
        />
        <div>
          <Text fz="sm">Jacob Warnhalter</Text>
          <Text fz="xs" c="dimmed">
            10 minutes ago
          </Text>
        </div>
      </Group>
      <TypographyStylesProvider className={classes.body}>
        <div
          className={classes.content}
          dangerouslySetInnerHTML={{
            __html:
              '<p>I use <a href="https://heroku.com/" rel="noopener noreferrer" target="_blank">Heroku</a> to host my Node.js application, but MongoDB add-on appears to be too <strong>expensive</strong>. I consider switching to <a href="https://www.digitalocean.com/" rel="noopener noreferrer" target="_blank">Digital Ocean</a> VPS to save some cash.</p>',
          }}
        />
      </TypographyStylesProvider>
    </Paper>
  );
}

Code: CommentHtml.module.css

.comment {
  padding: var(--mantine-spacing-lg) var(--mantine-spacing-xl);
}


.body {
  padding-left: rem(54px);
  padding-top: var(--mantine-spacing-sm);
  font-size: var(--mantine-font-size-sm);
}


.content {
  & > p:last-child {
    margin-bottom: 0;
  }
}

Mantine UI vs Other Component Libraries: Why Choose Mantine?

mantinue ui tutorial mantine ui comparison

When comparing Mantine UI to other popular libraries such as Material UI, Chakra UI, and Bootstrap, each has its strengths. Here’s a straightforward comparison to help you decide which fits your project best:

1. Mantine UI vs Material UI

  • Customization: Mantine UI offers extensive customization options with its sx prop, allowing easy changes to styles. Material UI follows strict Material Design guidelines, which ensures consistency but can limit flexibility.
  • Performance: Mantine UI is known for its smaller bundle sizes, potentially improving performance in large applications. Material UI, while feature-rich, can result in larger bundle sizes.

2. Mantine UI vs Chakra UI

  • Component Variety: Mantine UI has a larger number of pre-built components, covering more advanced UI elements such as notifications and modals. Chakra UI focuses on simplicity and ease of use with a more minimal set of components.
  • Responsiveness: Both libraries are highly responsive, but Mantine UI includes light and dark mode support for most components, while Chakra UI offers similar functionality but may require additional configuration.

3. Mantine UI vs Bootstrap

  • Design Flexibility: Mantine UI offers a more modern design with more flexible customization options. Bootstrap follows a grid-based, utility-first design system, which is more opinionated but widely adopted for traditional layouts.
  • Component Modernity: Mantine UI includes more modern, React-focused components like modals, notifications, and rich text editors, while Bootstrap is primarily focused on general-purpose UI components, suitable for both static and dynamic websites.

Conclusion

Mantine UI offers a flexible and modern solution for building responsive web applications with its extensive component library, easy customization, and built-in support for theming. Compared to other popular libraries like Material UI, Chakra UI, and Bootstrap, Mantine stands out for its smaller bundle sizes, broader component variety, and more developer-friendly styling options. However, each library has its strengths, and the choice ultimately depends on your project’s specific requirements.


Code Icon
Fasttrack Frontend
Development using CodeParrot AI
Background
CodeParrot Logo

CodeParrot

Ship stunning UI Lightning Fast

Y Combinator

Resources