Input

Input (입력)

Input 컴포넌트는 커스텀 입력을 만들기 위한 기반 컴포넌트예요. 폴리모픽 컴포넌트이며, 다른 여러 입력 컴포넌트의 공통 스타일과 기능을 제공해요.

출처: 문서

본문

!important: 대부분의 경우 애플리케이션에서 Input을 직접 사용하면 안 돼요. Input은 다른 입력들의 기반이며 직접 사용하도록 설계되지 않았어요. Input을 사용해 커스텀 입력을 만드는 경우를 제외하고는 TextInput이나 다른 컴포넌트를 사용하는 것이 좋아요.

import { Input, TextInput } from '@mantine/core';

// Incorrect usage, input is not accessible
function Incorrect() {
  return (
    <Input.Wrapper label="Input label">
      <Input />
    </Input.Wrapper>
  );
}

// Use TextInput instead of Input everywhere you want to use Input,
// it is accessible by default and includes Input.Wrapper
function Correct() {
  return (
    <TextInput label="Input label" description="Input description" />
  );
}

사용법 (Usage)

Input 컴포넌트는 일부 다른 입력들(NativeSelect, TextInput, Textarea 등)의 기반으로 사용돼요. Input의 목적은 다른 입력들에 공통 스타일과 기능을 제공하는 것이에요.

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

function Demo() {
  return <Input placeholder="Input component" />;
}

로딩 상태 (Loading state)

loading prop을 설정하면 로딩 인디케이터를 표시해요. 기본적으로 로더는 input 오른쪽에 표시돼요. loadingPosition prop을 'left'나 'right'로 바꿔 위치를 조절할 수 있어요.

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

function Demo() {
  return <Input placeholder="Your email" loading />;
}

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

Input은 leftSection과 rightSection prop을 지원해요. 이 섹션들은 input wrapper 안에 절대 위치(absolute positioning)로 렌더링돼요. 아이콘이나 다른 요소를 표시하는 데 사용할 수 있어요.

섹션 스타일과 콘텐츠를 제어하는 props:

  • rightSection / leftSection – input의 해당 쪽에 렌더링할 React 노드
  • rightSectionWidth/leftSectionWidth – right section의 너비와 input 해당 쪽의 패딩을 제어해요. 기본적으로 컴포넌트 size prop으로 제어돼요.
  • rightSectionPointerEvents/leftSectionPointerEvents – 섹션의 pointer-events 속성을 제어해요. 상호작용하지 않는 요소를 렌더링하려면 none으로 설정해 클릭이 input을 통과하게 해요.
import { useState } from 'react';
import { Input } from '@mantine/core';
import { AtIcon } from '@phosphor-icons/react';

function Demo() {
  const [value, setValue] = useState('Clear me');
  return (
    <>
      <Input placeholder="Your email" leftSection={<AtIcon size={16} />} />
      <Input
        placeholder="Clearable input"
        value={value}
        onChange={(event) => setValue(event.currentTarget.value)}
        rightSectionPointerEvents="all"
        mt="md"
        rightSection={
          value ? (
            <Input.ClearButton
              aria-label="Clear input"
              onClick={() => setValue('')}
            />
          ) : null
        }
      />
    </>
  );
}

input 요소 바꾸기 (Change input element)

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

Input을 button과 select로 사용하는 예시:

import { Input } from '@mantine/core';
import { CaretDownIcon } from '@phosphor-icons/react';

function Demo() {
  return (
    <>
      <Input component="button" pointer>
        Button input
      </Input>

      <Input
        component="select"
        rightSection={<CaretDownIcon size={14} />}
        pointer
        mt="md"
      >
        <option value="1">1</option>
        <option value="2">2</option>
      </Input>
    </>
  );
}

Input.Wrapper 컴포넌트

Input.Wrapper 컴포넌트는 다른 모든 입력들(TextInput, NativeSelect, Textarea 등) 내부에서 사용돼요. 이 컴포넌트가 이미 모든 입력에 포함되어 있으므로 직접 입력을 감쌀 필요는 없어요. Input.Wrapper는 커스텀 입력을 만들 때만 사용해요.

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

function Wrapper() {
  return (
    <Input.Wrapper label="Input label" description="Input description" error="Input error">
      <Input placeholder="Input inside Input.Wrapper" />
    </Input.Wrapper>
  );
}

inputWrapperOrder

inputWrapperOrder는 Input.Wrapper 부분들의 순서를 설정할 수 있게 해 줘요. label, input, error, description 네 가지 요소의 배열을 받아요. 모두 포함할 필요는 없으며 필요한 것만 사용할 수 있고, 포함되지 않은 부분은 렌더링되지 않아요.

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

function Demo() {
  return (
    <>
      <TextInput
        label="Custom layout"
        placeholder="Custom layout"
        description="Description below the input"
        inputWrapperOrder={['label', 'error', 'input', 'description']}
      />
      <TextInput
        mt="xl"
        label="Custom layout"
        placeholder="Custom layout"
        description="Error and description are"
        error="both below the input"
        inputWrapperOrder={['label', 'input', 'description', 'error']}
      />
    </>
  );
}

inputContainer

inputContainer prop으로 내부에서 Input.Wrapper를 사용하는 입력을 향상시킬 수 있어요. 예를 들어 TextInput이 포커스되었을 때 Tooltip을 추가할 수 있어요.

import { useState } from 'react';
import { TextInput, Tooltip } from '@mantine/core';

function Demo() {
  const [focused, setFocused] = useState(false);

  return (
    <TextInput
      label="TextInput with tooltip"
      description="Tooltip will be relative to the input"
      placeholder="Focus me to see tooltip"
      onFocus={() => setFocused(true)}
      onBlur={() => setFocused(false)}
      inputContainer={(children) => (
        <Tooltip label="Additional information" position="top-start" opened={focused}>
          {children}
        </Tooltip>
      )}
    />
  );
}

required와 withAsterisk prop

Input.Wrapper를 기반으로 하는 모든 컴포넌트는 required와 withAsterisk prop을 지원해요. true로 설정하면 두 prop 모두 라벨 끝에 빨간 별표를 추가해요. 차이는 input 요소에 required 속성이 추가되는지 여부뿐이에요. TextInput 컴포넌트를 사용한 예시:

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

// Will display required asterisk and add `required` attribute to the input element
function RequiredDemo() {
  return <TextInput label="test-label" required />;
}

// Will only display the asterisk, `required` attribute is not added to the input element
function AsteriskDemo() {
  return <TextInput label="test-label" withAsterisk />;
}

error prop

내부에서 Input.Wrapper를 사용하는 모든 입력은 error prop을 지원해요. true로 설정하면 input에 빨간 테두리를 추가해요. input 아래에 오류 메시지를 표시하려면 React 노드를 전달할 수도 있어요. 빨간 테두리 없이 오류 메시지만 표시하려면 error prop에 React 노드를 전달하고 withErrorStyles={false}를 설정해요.

import { TextInput } from '@mantine/core';
import { WarningCircleIcon } from '@phosphor-icons/react';

function Demo() {
  return (
    <>
      <TextInput placeholder="Error as boolean" label="Error as boolean" error />
      <TextInput
        mt="md"
        placeholder="Error as react node"
        label="Error as react node"
        error="Something went wrong"
      />

      <TextInput
        mt="md"
        placeholder="Without error styles on input"
        label="Without error styles on input"
        error="Something went wrong"
        withErrorStyles={false}
        rightSectionPointerEvents="none"
        rightSection={
          <WarningCircleIcon
            size={20}
            color="var(--mantine-color-error)"
          />
        }
      />
    </>
  );
}

success prop

내부에서 Input.Wrapper를 사용하는 모든 입력은 success prop을 지원해요. true로 설정하면 input에 초록색 테두리를 추가해요. input 아래에 성공 메시지를 표시하려면 React 노드를 전달할 수도 있어요. error와 success prop이 모두 설정되면 error가 우선해요.

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

function Demo() {
  return (
    <>
      <TextInput placeholder="Success as boolean" label="Success as boolean" success />
      <TextInput
        mt="md"
        placeholder="Success as react node"
        label="Success as react node"
        success="Username is available"
      />
    </>
  );
}

Input.Label, Input.Description, Input.Error 컴포넌트

기본 Input.Wrapper 레이아웃이 요구사항을 충족하지 못하면 Input.Label, Input.Error, Input.Description 컴포넌트로 커스텀 폼 레이아웃을 만들 수 있어요.

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

function Demo() {
  return (
    <>
      <Input.Label required>Input label</Input.Label>
      <Input.Description>Input description</Input.Description>
      <Input.Error>Input error</Input.Error>
    </>
  );
}

Input.Placeholder 컴포넌트

Input.Placeholder 컴포넌트는 button 요소를 기반으로 하거나 placeholder 속성을 기본 지원하지 않는 Input과 InputBase 컴포넌트에 placeholder를 추가하는 데 사용할 수 있어요.

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

function Demo() {
  return (
    <Input component="button" pointer>
      <Input.Placeholder>Placeholder content</Input.Placeholder>
    </Input>
  );
}

Input.ClearButton 컴포넌트

Input.ClearButton 컴포넌트로 Input 컴포넌트를 기반으로 하는 커스텀 입력에 지우기 버튼을 추가할 수 있어요. 지우기 버튼의 size는 input에서 자동으로 상속돼요.

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

테마에 기본 props 추가 (Default props on theme)

theme의 Input과 Input.Wrapper 컴포넌트에 기본 props를 추가할 수 있어요. 이 기본 props는 내부에서 Input과 Input.Wrapper를 사용하는 모든 입력(TextInput, NativeSelect, Textarea 등)이 상속받아요.

import { TextInput, NativeSelect, MantineProvider, createTheme, Input } from '@mantine/core';

const theme = createTheme({
  components: {
    Input: Input.extend({
      defaultProps: {
        variant: 'filled',
      },
    }),

    InputWrapper: Input.Wrapper.extend({
      defaultProps: {
        inputWrapperOrder: ['label', 'input', 'description', 'error'],
      },
    }),
  },
});

function Demo() {
  return (
    <MantineProvider theme={theme}>
      <TextInput
        label="Text input"
        placeholder="Text input"
        description="Description below the input"
      />

      <NativeSelect
        mt="md"
        label="Native select"
        data={['React', 'Angular', 'Vue', 'Svelte']}
        description="Description below the input"
      />
    </MantineProvider>
  );
}

모든 입력을 위한 공유 기본 props

Input과 Input.Wrapper의 기본 props는 이들을 기반으로 만들어진 모든 컴포넌트(TextInput, Textarea, NumberInput, Select, DateInput 등)에 전파돼요. 이것이 동일한 size, radius, variant, withAsterisk 또는 다른 공유 prop을 모든 입력에 한 번에 적용하는 가장 쉬운 방법이에요. 컴포넌트별 기본 props가 항상 공유 props보다 우선하므로 필요할 때 개별 컴포넌트를 여전히 덮어쓸 수 있어요.

import { TextInput, NumberInput, NativeSelect, MantineProvider, createTheme, Input } from '@mantine/core';

const theme = createTheme({
  components: {
    Input: Input.extend({
      defaultProps: {
        size: 'md',
        radius: 'md',
      },
    }),

    InputWrapper: Input.Wrapper.extend({
      defaultProps: {
        withAsterisk: true,
      },
    }),

    NumberInput: NumberInput.extend({
      defaultProps: {
        size: 'lg',
      },
    }),
  },
});

function Demo() {
  return (
    <MantineProvider theme={theme}>
      <TextInput label="Text input" placeholder="Inherits size and radius from Input" />

      <NativeSelect
        mt="md"
        label="Native select"
        data={['React', 'Angular', 'Vue', 'Svelte']}
      />

      <NumberInput mt="md" label="Number input" placeholder="Overrides shared size with lg" />
    </MantineProvider>
  );
}

테마에 스타일 적용 (Styles on theme)

기본 props와 마찬가지로 theme에서 Input과 Input.Wrapper의 Styles API를 사용해 모든 입력에 스타일을 추가할 수 있어요.

import { TextInput, NativeSelect, MantineProvider, createTheme, Input } from '@mantine/core';
import classes from './Demo.module.css';

const theme = createTheme({
  components: {
    Input: Input.extend({
      classNames: {
        input: classes.input,
      },
    }),

    InputWrapper: Input.Wrapper.extend({
      classNames: {
        label: classes.label,
      },
    }),
  },
});

function Demo() {
  return (
    <MantineProvider theme={theme}>
      <TextInput label="Text input" placeholder="Text input" />

      <NativeSelect
        mt="md"
        label="Native select"
        data={['React', 'Angular', 'Vue', 'Svelte']}
      />
    </MantineProvider>
  );
}

포커스 스타일 바꾸기

&:focus-within 선택자로 입력의 포커스 스타일을 바꿀 수 있어요. classNames prop으로 단일 컴포넌트에 적용하거나 theme의 Styles API로 모든 입력에 적용할 수 있어요.

.input {
  transition: none;

  &:focus-within {
    outline: 2px solid var(--mantine-color-blue-filled);
    border-color: transparent;
  }
}

InputBase 컴포넌트

InputBase 컴포넌트는 Input과 Input.Wrapper 컴포넌트를 결합하며 component prop을 지원해요.

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

function Demo() {
  return (
    <>
      <InputBase label="Your phone" component="input" placeholder="Your phone" />

      <InputBase label="Custom native select" component="select" mt="md">
        <option value="react">React</option>
        <option value="react">Angular</option>
        <option value="svelte">Svelte</option>
      </InputBase>
    </>
  );
}

Styles API

Input과 Input.Wrapper 컴포넌트는 Styles API를 지원해요. classNames와 styles prop으로 내부 요소의 스타일을 커스터마이즈할 수 있어요.

Input Styles API 선택자:

  • wrapper – Input의 루트 요소
  • input – input 요소
  • section – 왼쪽·오른쪽 섹션
  • bottomSection – input border 아래쪽에 렌더링되는 하단 섹션 요소

Input.Wrapper Styles API 선택자:

  • root – 루트 요소
  • label – 라벨 요소
  • required – 라벨 안에 렌더링되는 필수 별표 요소
  • description – 설명 요소
  • error – 오류 요소
  • success – 성공 요소

요소 ref 가져오기

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

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

접근성 (Accessibility)

연결된 라벨 요소 없이 Input 컴포넌트를 사용한다면 aria-label을 설정해요.

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

// ok – the input is labelled by the aria-label
function WithAriaLabel() {
  return <Input aria-label="Your email" />;
}

// ok – the input is labelled by the label element
function WithLabel() {
  return (
    <>
      <label htmlFor="my-email">Your email</label>
      <Input id="my-email" />
    </>
  );
}

Input을 Input.Wrapper와 함께 사용할 때는 라벨과 다른 요소를 input에 연결하기 위해 두 컴포넌트 모두에 id를 설정해야 해요.

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

function Demo() {
  return (
    <Input.Wrapper label="Your email" id="your-email">
      <Input id="your-email" />
    </Input.Wrapper>
  );
}

use-id로 고유한 id를 생성할 수 있어요.

import { Input } from '@mantine/core';
import { useId } from '@mantine/hooks';

function Demo() {
  const id = useId();
  return (
    <Input.Wrapper label="Your email" id={id}>
      <Input id={id} />
    </Input.Wrapper>
  );
}

더 알아보기 (Learn more)

  • TextInput — 텍스트 입력 컴포넌트
  • Textarea — 텍스트 영역 컴포넌트
  • Select — 셀렉트 컴포넌트