Autocomplete

Autocomplete

사용자 입력을 옵션 목록으로 자동 완성하는 컴포넌트예요. Combobox 위에 만들어진 의견 반영(opinionated) 컴포넌트예요.

출처: 문서

본문

Made with Combobox

Autocomplete는 Combobox 컴포넌트 위에 만들어진 의견 반영 컴포넌트예요. 기본 사용 사례만 다루는 제한된 기능 세트를 가져요. 더 고급 기능이 필요하다면 Combobox로 직접 컴포넌트를 만들 수 있어요. 커스텀 autocomplete 컴포넌트 예시는 examples page에서 찾을 수 있어요.

Not a searchable select

Autocomplete는 검색 가능한 select가 아니고, 제안이 있는 텍스트 입력이에요. 값이 반드시 제안 중 하나로 강제되지는 않아요. 사용자는 어떤 것이든 입력할 수 있어요. 검색 가능한 select가 필요하면 Select 컴포넌트를 사용하세요. Autocomplete와 Select의 차이점을 더 알아보려면 help center article을 확인하세요.

Usage

Autocomplete는 입력에 기반해 제안 목록을 제공하지만, 사용자는 제안에 제한되지 않고 어떤 것이든 입력할 수 있어요.

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

function Demo() {
  return (
    <Autocomplete
      label="Your favorite library"
      placeholder="Pick value or enter anything"
      data={['React', 'Angular', 'Vue', 'Svelte']}
    />
  );
}

Loading state

loading prop을 설정해 로딩 지시자를 표시할 수 있어요. 기본적으로 로더는 입력 오른쪽에 표시돼요. loadingPosition prop으로 위치를 'left' 또는 'right'로 바꿀 수 있어요. API 호출, 검색, 검증 같은 비동기 작업에 유용해요.

Controlled

Autocomplete 값은 문자열이어야 해요. 다른 타입은 지원하지 않아요. onChange 함수는 단일 인자로 문자열 값과 함께 호출돼요.

import { useState } from 'react';
import { Autocomplete } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState('');
  return <Autocomplete data={['React', 'Angular', 'Vue']} value={value} onChange={setValue} />;
}

Uncontrolled

Autocomplete는 네이티브 input 요소와 같은 방식으로 비제어 폼과 함께 사용할 수 있어요. name 속성을 설정하면 폼 제출 시 autocomplete 값을 FormData 객체에 포함시킬 수 있어요. 비제어 폼에서 초기 값을 제어하려면 defaultValue prop을 사용하세요.

FormData와 함께 비제어 Autocomplete를 사용하는 예시:

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

function Demo() {
  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const formData = new FormData(event.currentTarget);
        console.log('Autocomplete value:', formData.get('country'));
      }}
    >
      <Autocomplete name="country" data={['US', 'CA', 'MX']} />
      <button type="submit">Submit</button>
    </form>
  );
}

Select first option on change

selectFirstOptionOnChange prop을 설정하면 입력 값이 바뀔 때 드롭다운의 첫 번째 옵션을 자동으로 선택해요. 이 기능으로 사용자가 값을 입력하고 바로 Enter를 눌러 첫 번째 일치 옵션을 선택할 수 있어요. 먼저 화살표 아래 키를 누를 필요가 없어요.

autoSelectOnBlur

autoSelectOnBlur prop을 설정하면 입력이 포커스를 잃을 때 강조된 옵션을 자동으로 선택해요. 위/아래 화살표로 옵션을 선택한 후 입력 밖을 클릭하면 동작을 확인할 수 있어요.

Data formats

Autocomplete data prop은 다음 형식 중 하나로 데이터를 받아요.

문자열 배열:

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

function Demo() {
  return <Autocomplete data={['React', 'Angular', 'Vue']} />;
}

문자열 옵션이 있는 그룹 배열:

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

function Demo() {
  return (
    <Autocomplete
      data={[
        { group: 'Frontend', items: ['React', 'Angular', 'Vue'] },
        { group: 'Backend', items: ['Node.js', 'Django', 'Rails'] },
      ]}
    />
  );
}

Options filtering

기본적으로 Autocomplete는 옵션 라벨이 입력 값을 포함하는지 확인해 옵션을 필터링해요. filter prop으로 이 동작을 바꿀 수 있어요. filter 함수는 다음 속성을 가진 객체를 단일 인자로 받아요.

  • options – 옵션 배열 또는 옵션 그룹, 모든 옵션은 { value: string; label: string; disabled?: boolean } 형식
  • search – 현재 검색어
  • limit – Autocomplete에 전달된 limit prop 값

문자 시퀀스 대신 단어로 옵션을 매칭하는 커스텀 filter 함수 예시:

import { Autocomplete, ComboboxItem, OptionsFilter } from '@mantine/core';

const optionsFilter: OptionsFilter = ({ options, search }) => {
  const splittedSearch = search.toLowerCase().trim().split(' ');
  return (options as ComboboxItem[]).filter((option) => {
    const words = option.label.toLowerCase().trim().split(' ');
    return splittedSearch.every((searchWord) => words.some((word) => word.includes(searchWord)));
  });
};

function Demo() {
  return <Autocomplete data={['React', 'Angular', 'Vue', 'Svelte']} filter={optionsFilter} />;
}

Sort options

기본적으로 옵션은 데이터 배열의 위치에 따라 정렬돼요. filter 함수로 이 동작을 바꿀 수 있어요.

import { Autocomplete, ComboboxItem, OptionsFilter } from '@mantine/core';

const optionsFilter: OptionsFilter = ({ options, search }) => {
  const filtered = (options as ComboboxItem[]).filter((option) =>
    option.label.toLowerCase().trim().includes(search.toLowerCase().trim())
  );

  filtered.sort((a, b) => a.label.localeCompare(b.label));
  return filtered;
};

function Demo() {
  return <Autocomplete data={['React', 'Angular', 'Vue', 'Svelte']} filter={optionsFilter} />;
}

Fuzzy search with fuse.js

fuse.js 라이브러리를 사용해 오타나 부분 일치에도 매칭되는 퍼지 검색을 구현할 수 있어요.

import { Autocomplete, ComboboxItem, OptionsFilter } from '@mantine/core';
import Fuse from 'fuse.js';

const optionsFilter: OptionsFilter = ({ options, search }) => {
  if (!search.trim()) {
    return options;
  }

  const fuse = new Fuse(options as ComboboxItem[], {
    keys: ['label'],
    threshold: 0.3,
    minMatchCharLength: 1,
  });

  return fuse.search(search).map((result) => result.item);
};

function Demo() {
  return <Autocomplete data={['React', 'Angular', 'Vue', 'Svelte']} filter={optionsFilter} />;
}

Large data sets

대용량 데이터셋의 가장 좋은 전략은 동시에 렌더링되는 옵션 수를 제한하는 것이에요. limit prop으로 이렇게 할 수 있어요. 커스텀 filter 함수를 사용한다면 filter에서 옵션 수를 제한하는 로직을 직접 구현해야 해요.

100,000개 옵션이 있는 Autocomplete 예시(5개 옵션만 동시 렌더링):

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

const largeData = Array(100_000)
  .fill(0)
  .map((_, index) => `Option ${index}`);

function Demo() {
  return <Autocomplete data={largeData} limit={5} />;
}

renderOption

renderOption 콜백으로 옵션 렌더링을 커스터마이즈할 수 있어요. 옵션 객체와 함께 호출되고 React 노드를 반환해야 해요.

import { Autocomplete, AutocompleteProps, Avatar, Group, Text } from '@mantine/core';

const usersData: Record<string, { image: string; email: string }> = {
  'Emily Johnson': {
    image: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-7.png',
    email: '[email protected]',
  },
  'Ava Rodriguez': {
    image: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-8.png',
    email: '[email protected]',
  },
  'Olivia Chen': {
    image: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-4.png',
    email: '[email protected]',
  },
  'Ethan Barnes': {
    image: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-1.png',
    email: '[email protected]',
  },
  'Mason Taylor': {
    image: 'https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-2.png',
    email: '[email protected]',
  },
};

const renderAutocompleteOption: AutocompleteProps['renderOption'] = ({ option }) => (
  <Group gap="sm">
    <Avatar src={usersData[option.value].image} size="sm" radius="xl" />
    <div>
      <Text>{option.value}</Text>
      <Text size="xs" c="dimmed">{usersData[option.value].email}</Text>
    </div>
  </Group>
);

function Demo() {
  return <Autocomplete data={['Emily Johnson', 'Ava Rodriguez', 'Olivia Chen', 'Ethan Barnes', 'Mason Taylor']} renderOption={renderAutocompleteOption} />;
}

Nothing found message

Autocomplete 컴포넌트는 nothing found(결과 없음) 메시지를 지원하지 않아요. 어떤 문자열이든 값으로 받아들이도록 설계되었기 때문에 결과 없음 메시지를 보여주는 것이 의미가 없어요. 사용자 입력을 제안으로 제한하고 싶다면 검색 가능한 Select 컴포넌트를 사용하세요. Autocomplete와 Select의 차이를 더 알아보려면 help center article을 확인하세요.

Scrollable dropdown

기본적으로 옵션 목록은 ScrollArea.Autosize로 감싸져요. 기본 설정을 바꾸지 않았다면 maxDropdownHeight prop으로 드롭다운 최대 높이를 제어할 수 있어요.

네이티브 스크롤바를 사용하려면 withScrollArea={false}를 설정하세요. 이 경우 Styles API로 드롭다운 스타일을 바꿔야 해요.

Fit dropdown to viewport height

floatingHeight="viewport"를 설정하면 드롭다운이 뷰포트의 사용 가능한 세로 공간을 채우도록 자라요. 이 모드에서는 flip 미들웨어가 비활성화되어 드롭다운이 항상 설정된 방향으로 열리고 반대쪽으로 뒤집히는 대신 뷰포트 가장자리로 제한돼요. 큰 옵션 목록을 다룰 때 유용해요.

Group options

옵션을 그룹으로 나눠 표시할 수 있어요.

Disabled options

옵션이 비활성화되면 선택할 수 없고 키보드 탐색에서 무시돼요.

Combobox props

comboboxProps로 Combobox props를 재정의할 수 있어요. Autocomplete가 노출하지 않는 일부 props(예: withinPortal)를 바꿔야 할 때 유용해요.

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

function Demo() {
  return <Autocomplete data={['React', 'Angular', 'Vue']} comboboxProps={{ withinPortal: false }} />;
}

Change dropdown z-index

드롭다운의 z-index를 바꿀 수 있어요.

Inside Popover

popover 안에서 Autocomplete를 사용하려면 withinPortal: false를 설정해야 해요.

Clearable

clearable prop을 설정하면 오른쪽 섹션에 지우기 버튼을 표시해요. 버튼이 표시되지 않는 경우:

  • 컴포넌트에 값이 없을 때
  • 컴포넌트가 비활성화될 때
  • 컴포넌트가 읽기 전용일 때

Clear section mode

clearSectionMode prop은 지우기 버튼과 rightSection이 어떻게 렌더링되는지 결정해요.

  • 'both' (default) – 지우기 버튼과 rightSection을 모두 렌더링
  • 'rightSection' – 사용자가 제공한 rightSection만 렌더링, 지우기 버튼 무시
  • 'clear' – 지우기 버튼만 렌더링, rightSection 무시
import { CaretDownIcon } from '@phosphor-icons/react';
import { Autocomplete, Stack } from '@mantine/core';

function Demo() {
  return (
    <Stack>
      <Autocomplete
        data={['React', 'Angular', 'Vue']}
        rightSection={<CaretDownIcon />}
        clearable
        clearSectionMode="both"
      />
      <Autocomplete
        data={['React', 'Angular', 'Vue']}
        rightSection={<CaretDownIcon />}
        clearable
        clearSectionMode="rightSection"
      />
      <Autocomplete
        data={['React', 'Angular', 'Vue']}
        rightSection={<CaretDownIcon />}
        clearable
        clearSectionMode="clear"
      />
    </Stack>
  );
}

Control dropdown opened state

dropdownOpened prop으로 드롭다운 열림 상태를 제어할 수 있어요. onDropdownClose와 onDropdownOpen으로 드롭다운 열림 상태 변경을 구독할 수도 있어요.

기본적으로 공간이 충분하면 드롭다운은 입력 아래에, 아니면 입력 위에 표시돼요. position과 middlewares props로 이 동작을 바꿀 수 있으며, 이 props는 내부 Popover 컴포넌트로 전달돼요.

항상 입력 위에 표시되는 드롭다운 예시:

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

function Demo() {
  return <Autocomplete data={['React', 'Angular', 'Vue']} position="top" />;
}

기본적으로 드롭다운 애니메이션은 비활성화돼요. transitionProps를 설정하면 활성화할 수 있으며, 내부 Transition 컴포넌트로 전달돼요.

드롭다운 패딩을 0(제로 패딩) 또는 10px 등으로 조절할 수 있어요.

드롭다운에 그림자를 추가할 수 있어요.

Left and right sections

Autocomplete는 leftSection과 rightSection props를 지원해요. 이 섹션들은 입력 래퍼 안에 절대 위치로 렌더링돼요. 아이콘, 입력 컨트롤 등 어떤 요소든 표시하는 데 사용할 수 있어요.

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

  • rightSection / leftSection – 입력의 해당 쪽에 렌더링할 React 노드
  • rightSectionWidth / leftSectionWidth – 오른쪽 섹션의 너비와 입력 해당 쪽의 패딩을 제어. 기본적으로 컴포넌트의 size prop으로 제어됨
  • rightSectionPointerEvents / leftSectionPointerEvents – 섹션의 pointer-events 속성을 제어. 상호작용하지 않는 요소를 렌더링한다면 none으로 설정해 클릭이 입력으로 통과하게 함

Input props

Autocomplete 컴포넌트는 Input과 Input.Wrapper 컴포넌트 기능과 모든 input 요소 props를 지원해요. Autocomplete 문서에는 컴포넌트가 지원하는 모든 기능이 포함되어 있지 않으니, 사용 가능한 모든 기능을 보려면 Input 문서를 확인하세요.

Read only

readOnly를 설정하면 입력을 읽기 전용으로 만들어요. readOnly가 설정되면 Autocomplete는 제안을 표시하지 않고 onChange 함수도 호출하지 않아요.

Disabled

disabled를 설정하면 입력을 비활성화해요. disabled가 설정되면 사용자는 입력과 상호작용할 수 없고 Autocomplete는 제안을 표시하지 않아요.

Error state

Boolean error와 에러 메시지로 오류 상태를 표시할 수 있어요.

Success state

성공 상태를 표시할 수 있어요.

Styles API

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

Get element ref

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

function Demo() {
  const ref = useRef<HTMLInputElement>(null);
  return <Autocomplete data={['React']} ref={ref} />;
}

Accessibility

label prop 없이 Autocomplete를 사용하면 스크린 리더가 제대로 안내하지 못해요.

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

// Inaccessible input – screen reader will not announce it properly
function Demo() {
  return <Autocomplete data={['React']} />;
}

aria-label을 설정하면 입력이 접근 가능해져요. 이 경우 라벨은 보이지 않지만 스크린 리더가 안내해요.

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

// Accessible input – it has aria-label
function Demo() {
  return <Autocomplete data={['React']} aria-label="Choose a library" />;
}

label prop을 설정하면 입력이 접근 가능하며 aria-label을 설정할 필요가 없어요.

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

// Accessible input – it has associated label element
function Demo() {
  return <Autocomplete data={['React']} label="Choose a library" />;
}

더 알아보기 (Learn more)