Button

Button (버튼)

Button 컴포넌트는 버튼 또는 링크를 렌더링하는 컴포넌트예요. 다양한 variant, 크기, 색상과 함께 로딩·비활성 상태를 지원하는 Mantine의 핵심 컴포넌트예요.

출처: 문서

본문

Button은 버튼을 렌더링하며 기본 variant는 filled예요. variant prop으로 default, filled, light, outline, subtle, transparent, white 등을 지정할 수 있어요.

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

function Demo() {
  return <Button variant="filled">Button</Button>;
}

전체 너비 (Full width)

fullWidth prop을 설정하면 Button이 부모 너비의 100%를 차지해요.

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

function Demo() {
  return <Button fullWidth>Full width button</Button>;
}

왼쪽·오른쪽 섹션 (Left and right sections)

leftSection과 rightSection은 버튼의 왼쪽과 오른쪽에 아이콘이나 다른 요소를 추가할 수 있게 해 줘요. 섹션이 추가되면 해당 쪽의 패딩이 줄어들어요.

주의할 점: leftSection과 rightSection은 RTL 모드에서 반전돼요(leftSection은 오른쪽에, rightSection은 왼쪽에 표시돼요).

import { Group, Button } from '@mantine/core';
import { ImageIcon, DownloadSimpleIcon, ArrowRightIcon } from '@phosphor-icons/react';

function Demo() {
  return (
    <Group justify="center">
      <Button leftSection={<ImageIcon size={14} />} variant="default">
        Gallery
      </Button>

      <Button rightSection={<DownloadSimpleIcon size={14} />}>Download</Button>

      <Button
        variant="light"
        leftSection={<ImageIcon size={14} />}
        rightSection={<ArrowRightIcon size={14} />}
      >
        Visit gallery
      </Button>
    </Group>
  );
}

섹션 위치 (Sections position)

justify prop은 inner 요소의 justify-content를 설정해요. 이를 사용해 왼쪽·오른쪽 섹션의 정렬을 바꿀 수 있어요. 예를 들어 버튼 전체에 걸쳐 펼치려면 justify="space-between"을 설정해요.

한쪽 섹션만 버튼의 한쪽 끝에 정렬하고 싶다면 justify를 space-between으로 설정하고 반대쪽 섹션에 빈 요소를 추가하면 돼요.

import { Button } from '@mantine/core';
import { ImageIcon } from '@phosphor-icons/react';

function Demo() {
  const icon = <ImageIcon size={14} />;
  return (
    <>
      <Button justify="center" fullWidth leftSection={icon} rightSection={icon} variant="default">
        Button label
      </Button>

      <Button justify="center" fullWidth leftSection={icon} variant="default" mt="md">
        Button label
      </Button>

      <Button justify="center" fullWidth rightSection={icon} variant="default" mt="md">
        Button label
      </Button>

      <Button
        justify="center"
        fullWidth
        rightSection={icon}
        leftSection={<span />}
        variant="default"
        mt="md"
      >
        Button label
      </Button>
    </>
  );
}

컴팩트 크기 (Compact size)

Button은 xs–xl과 compact-xs–compact-xl 크기를 지원해요. compact 크기는 xs–xl과 같은 폰트 크기를 가지지만 패딩과 높이가 줄어들어요.

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

function Demo() {
  return (
    <Group justify="center">
      <Button size="md">Regular md</Button>
      <Button size="compact-md">Compact md</Button>
    </Group>
  );
}

그라디언트 variant

variant prop을 gradient로 설정하면 gradient prop으로 그라디언트를 제어할 수 있어요. gradient prop은 from, to, deg 속성을 가진 객체를 받아요. gradient prop이 설정되지 않으면 Button은 theme object에서 설정할 수 있는 theme.defaultGradient를 사용해요. variant가 gradient가 아니면 gradient prop은 무시돼요.

주의: variant="gradient"는 두 가지 색상의 선형 그라디언트만 지원해요. 더 복잡한 그라디언트가 필요하면 Styles API로 Button 스타일을 수정해요.

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

function Demo() {
  return (
    <Button
      variant="gradient"
      gradient={{ from: 'blue', to: 'cyan', deg: 90 }}
    >
      Gradient button
    </Button>
  );
}

비활성 상태 (Disabled state)

Button을 비활성화하려면 disabled prop을 설정해요. 이렇게 하면 버튼과의 모든 상호작용이 차단되고 비활성 스타일이 적용돼요. 버튼이 비활성 상태처럼 보이지만 여전히 상호작용 가능하게 하려면 대신 data-disabled prop을 설정해요. 비활성 스타일은 모든 variant에서 동일하다는 점을 참고해요.

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

function Demo() {
  return <Button disabled>Disabled button</Button>;
}

Button이 링크일 때의 비활성 상태

<a> 요소는 disabled 속성을 지원하지 않아요. Button이 링크로 렌더링될 때 비활성화하려면 data-disabled 속성을 설정하고 onClick 이벤트 핸들러에서 기본 동작을 막으면 돼요.

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

function Demo() {
  return (
    <Button
      component="a"
      href="https://mantine.dev"
      data-disabled
      onClick={(event) => event.preventDefault()}
    >
      Disabled link
    </Button>
  );
}

비활성 스타일 커스터마이즈

비활성 스타일을 커스터마이즈하려면 &:disabled와 &[data-disabled] 선택자를 모두 사용하는 것이 좋아요.

  • &:disabled는 disabled prop이 설정되었을 때, 그리고 부모 컴포넌트에 의해 버튼이 비활성화되었을 때(예: Button을 포함하는 fieldset 요소에 disabled prop이 설정된 경우) 버튼을 스타일링하는 데 사용돼요.
  • &[data-disabled]는 실제로는 비활성화되지 않았지만 비활성화된 것처럼 보이게 해야 할 때 스타일링하는 데 사용돼요(예: 비활성 Button과 함께 Tooltip을 사용해야 하거나 Button을 링크로 사용할 때 data-disabled를 사용해야 해요).
.button {
  &:disabled,
  &[data-disabled] {
    border-color: light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
    background-color: transparent;
  }
}

비활성 Button과 Tooltip

Button이 비활성화되면 onMouseLeave 이벤트가 트리거되지 않아서, 비활성 Button과 함께 Tooltip을 사용해야 한다면 disabled 대신 Button에 data-disabled prop을 설정해야 해요. 이때 Button이 실제로는 비활성화되지 않아 onClick 이벤트가 여전히 발생하므로 onClick 핸들러를 (event) => event.preventDefault()로 바꿔야 해요.

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

function Demo() {
  return (
    <Tooltip label="Tooltip for disabled button">
      <Button data-disabled onClick={(event) => event.preventDefault()}>
        Disabled button with tooltip
      </Button>
    </Tooltip>
  );
}

로딩 상태 (Loading state)

loading prop이 설정되면 Button이 비활성화되고, 가운데에 오버레이를 가진 Loader가 렌더링돼요. Loader 색상은 Button variant에 따라 달라져요.

import { Button, Group, Switch } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [loading, { toggle }] = useDisclosure();
  return (
    <>
      <Group>
        <Button loading={loading}>Filled button</Button>
        <Button variant="light" loading={loading}>
          Light button
        </Button>
        <Button variant="outline" loading={loading}>
          Outline button
        </Button>
      </Group>

      <Switch checked={loading} onChange={toggle} label="Loading state" mt="md" />
    </>
  );
}

Loader props

loaderProps prop으로 Loader를 커스터마이즈할 수 있어요. Loader 컴포넌트가 가진 모든 prop을 받아요.

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

function Demo() {
  return (
    <Button loading loaderProps={{ type: 'dots' }}>
      Loading button
    </Button>
  );
}

Styles API

Button은 Styles API를 지원해요. classNames prop으로 컴포넌트의 내부 요소에 스타일을 추가할 수 있어요.

주요 선택자는 다음과 같아요.

  • root – 루트 요소
  • loader – loading prop이 설정되었을 때만 표시되는 Loader 컴포넌트
  • inner – 다른 모든 요소를 포함하며, root 요소의 자식
  • section – 버튼의 왼쪽·오른쪽 섹션
  • label – 버튼 children

Styles API와 data-* attributes로 Button을 커스터마이즈하는 예시:

.root {
  border-top-left-radius: var(--mantine-radius-xl);
  border-bottom-left-radius: var(--mantine-radius-xl);
  padding-left: 4px;

  /* The following styles will be applied only when button is disabled */
  &[data-disabled] {
    /* You can use Mantine PostCSS mixins inside data attributes */
    @mixin light {
      border: 1px solid var(--mantine-color-gray-2);
    }

    @mixin dark {
      border: 1px solid var(--mantine-color-dark-4);
    }

    /* You can target child elements that are inside .root[data-disabled] */
    & .section[data-position='left'] {
      opacity: 0.6;
    }
  }
}

.section {
  /* Apply styles only to left section */
  &[data-position='left'] {
    --section-size: calc(var(--button-height) - 8px);

    background-color: var(--mantine-color-body);
    color: var(--mantine-color-text);
    height: var(--section-size);
    width: var(--section-size);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--mantine-radius-xl);
  }

  &[data-position='right'] {
    @mixin rtl {
      transform: rotate(180deg);
    }
  }
}

커스텀 variant

새 Button variant를 추가하려면 data-variant 속성을 사용해요. 보통 새 variant는 theme에 추가해서 애플리케이션의 모든 Button 컴포넌트에서 사용할 수 있게 해요.

import { Group, Button, MantineProvider, createTheme } from '@mantine/core';
import classes from './Demo.module.css';

const theme = createTheme({
  components: {
    Button: Button.extend({
      classNames: classes,
    }),
  },
});

function Demo() {
  return (
    <MantineProvider theme={theme}>
      <Group>
        <Button variant="danger">Danger variant</Button>
        <Button variant="primary">Primary variant</Button>
      </Group>
    </MantineProvider>
  );
}

variant 색상 커스터마이즈

variantColorResolver를 테마에 추가하면 Button과 다른 컴포넌트 variant의 색상을 커스터마이즈할 수 있어요.

import {
  Button,
  Group,
  MantineProvider,
  defaultVariantColorsResolver,
  VariantColorsResolver,
  parseThemeColor,
  rgba,
  darken,
} from '@mantine/core';

const variantColorResolver: VariantColorsResolver = (input) => {
  const defaultResolvedColors = defaultVariantColorsResolver(input);
  const parsedColor = parseThemeColor({
    color: input.color || input.theme.primaryColor,
    theme: input.theme,
  });

  // Override some properties for variant
  if (parsedColor.isThemeColor && parsedColor.color === 'lime' && input.variant === 'filled') {
    return {
      ...defaultResolvedColors,
      color: 'var(--mantine-color-black)',
      hoverColor: 'var(--mantine-color-black)',
    };
  }

  // Completely override variant
  if (input.variant === 'light') {
    return {
      background: rgba(parsedColor.value, 0.1),
      hover: rgba(parsedColor.value, 0.15),
      border: `1px solid ${parsedColor.value}`,
      color: darken(parsedColor.value, 0.1),
    };
  }

  // Add new variants support
  if (input.variant === 'danger') {
    return {
      background: 'var(--mantine-color-red-9)',
      hover: 'var(--mantine-color-red-8)',
      color: 'var(--mantine-color-white)',
      border: 'none',
    };
  }

  return defaultResolvedColors;
};

function Demo() {
  return (
    <MantineProvider theme={{ variantColorResolver }}>
      <Group>
        <Button color="lime.4" variant="filled">
          Lime filled button
        </Button>

        <Button color="orange" variant="light">
          Orange light button
        </Button>

        <Button variant="danger">Danger button</Button>
      </Group>
    </MantineProvider>
  );
}

autoContrast

Button은 autoContrast prop과 theme.autoContrast를 지원해요. Button이나 테마에 autoContrast가 설정되면, color prop에 지정된 값과 충분한 대비를 가지도록 콘텐츠 색상이 조정돼요.

주의: autoContrast 기능은 배경색을 바꾸기 위해 color prop을 사용할 때만 동작해요. autoContrast는 filled variant에서만 동작해요.

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

function Demo() {
  return (
    <Group>
      <Button color="lime.4">Default</Button>
      <Button color="lime.4" autoContrast>
        Auto contrast
      </Button>
    </Group>
  );
}

Button.Group

Button.Group을 사용하면 여러 버튼을 하나로 묶어 표시할 수 있어요.

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

function Demo() {
  return (
    <Button.Group>
      <Button variant="default">First</Button>
      <Button variant="default">Second</Button>
      <Button variant="default">Third</Button>
    </Button.Group>
  );
}

주의: 자식 Button 컴포넌트를 추가 요소로 감싸면 안 돼요.

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

function Demo() {
  return (
    <Button.Group>
      <div>
        <Button>This will not work</Button>
      </div>
      <Button>Buttons will have incorrect borders</Button>
    </Button.Group>
  );
}

Button.GroupSection

Button.Group 안에서 버튼이 아닌 섹션을 렌더링하려면 Button.GroupSection 컴포넌트를 사용해요.

import { CaretDownIcon, CaretUpIcon } from '@phosphor-icons/react';
import { Button } from '@mantine/core';
import { useCounter } from '@mantine/hooks';

function Demo() {
  const [value, { increment, decrement }] = useCounter(135, { min: 0 });

  return (
    <Button.Group>
      <Button variant="default" onClick={decrement}>
        <CaretDownIcon color="var(--mantine-color-red-text)" />
      </Button>
      <Button.GroupSection variant="default" bg="var(--mantine-color-body)" miw={80}>
        {value}
      </Button.GroupSection>
      <Button variant="default" onClick={increment}>
        <CaretUpIcon color="var(--mantine-color-teal-text)" />
      </Button>
    </Button.Group>
  );
}

폴리모픽 컴포넌트

Button은 폴리모픽 컴포넌트예요. 기본 루트 요소는 button이지만, component prop으로 다른 요소나 컴포넌트로 바꿀 수 있어요.

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

function Demo() {
  return <Button component="a" />;
}

component prop에는 컴포넌트를 넣을 수도 있어요. 예를 들어 Next.js의 Link를 넣을 수 있어요.

import Link from 'next/link';
import { Button } from '@mantine/core';

function Demo() {
  return <Button component={Link} href="/" />;
}

TypeScript와 함께 쓰는 폴리모픽 컴포넌트

폴리모픽 컴포넌트의 prop 타입은 일반 컴포넌트와 달라요. 기본 요소의 HTML 요소 props를 확장하지 않아요. 예를 들어 ButtonProps는 button이 기본 요소임에도 React.ComponentProps를 확장하지 않아요.

폴리모픽(또는 component prop을 지원하지 않는) 컴포넌트의 wrapper를 만들고 싶다면, 컴포넌트 props 인터페이스가 HTML 요소 props를 확장하도록 해야 해요.

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

interface MyButtonProps extends ButtonProps,
  ElementProps<'a', keyof ButtonProps> {}

래핑 후에도 컴포넌트가 폴리모픽으로 유지되길 원한다면, 가이드에서 설명하는 polymorphic 함수를 사용해요.

요소 ref 가져오기

useRef를 활용해 Button 요소의 ref를 얻을 수 있어요.

import { useRef } from 'react';
import { Button } from '@mantine/core';

function Demo() {
  const ref = useRef<HTMLButtonElement>(null);
  return <Button ref={ref} />;
}

더 알아보기 (Learn more)

  • Loader — 로딩 인디케이터 컴포넌트
  • Tooltip — 툴팁 컴포넌트
  • Styles API — 스타일 커스터마이즈 문서