TagsInput

Data Input

여러 태그를 입력하고 관리하는 입력 필드.

Usage

사용자가 여러 키워드를 입력·확정·삭제해 태그 배열을 만들 때 사용한다.

import

import

import { TagsInput } from '@mildang/design-system/Combobox';

API Reference

TagsInput Props

Prop

Type

Default

allowedValues

string[]

지정 안 함

inputValue

string

지정 안 함

isComboboxOpen

boolean

false

onInputChange

(details: InputValueChangeDetails) => void

지정 안 함

onKeyDown

(e: KeyboardEvent<HTMLInputElement>) => void

지정 안 함

placeholder

string

지정 안 함

renderTag

TagsInputRenderTag

지정 안 함

showChip

boolean

true

showSearchIcon

boolean

true

size

"xs" | "sm" | "md" | "lg" | "xl"

md

type

"search" | "free"

search

같은 패밀리

@mildang/design-system/Combobox 에서 같이 내보내는 컴포넌트다.

예제

기본 사용

입력한 값이 칩으로 쌓이는 형태 — 태그처럼 개수가 정해지지 않은 값을 여러 개 받을 때 씁니다.

import type { ComponentProps } from 'react';
import { TagsInput } from '@mildang/design-system/Combobox';
import { useState } from 'react';

const STORY_DEFAULT_ARGS = { ...({}), ...({}) } as ComponentProps<typeof TagsInput>;

const TagsInputDefaultExampleRender = (args: ComponentProps<typeof TagsInput>) => {
    const [values, setValues] = useState<string[]>();
    return (
      <TagsInput
        value={values}
        onValueChange={(details) => setValues(details.value)}
        placeholder="프레임워크를 입력하세요..."
        {...args}
      />
    );
  };

export default function TagsInputDefaultExample(props: Partial<ComponentProps<typeof TagsInput>>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof TagsInput>;
  return TagsInputDefaultExampleRender(mergedProps);
}

크기

xs부터 xl까지 칩과 입력 높이가 함께 커지는 5단계를 비교합니다.

import { TagsInput } from '@mildang/design-system/Combobox';

const TagsInputSizesExample = () => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '400px' }}>
      {(['sm', 'md', 'lg', 'xl'] as const).map((size) => (
        <div key={size} style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
          <span style={{ width: '24px', fontSize: '12px', color: '#666', flexShrink: 0 }}>{size}</span>
          <TagsInput size={size} defaultValue={['React', 'Vue']} placeholder={`size=${size}`} />
        </div>
      ))}
    </div>
  );

export default TagsInputSizesExample;

상태

import { useState } from 'react';
import { TagsInput } from '@mildang/design-system/Combobox';

const TagsInputDisabledExample = () => {
    const [values] = useState<string[]>(['React', 'Vue']);
    return <TagsInput value={values} disabled placeholder="비활성화된 상태입니다" />;
  };

export default TagsInputDisabledExample;

WithMaxTags

import { useState } from 'react';
import { TagsInput } from '@mildang/design-system/Combobox';

const TagsInputWithMaxTagsExample = () => {
    const [values, setValues] = useState<string[]>(['React', 'Vue']);
    return (
      <TagsInput
        value={values}
        onValueChange={(details) => setValues(details.value)}
        max={3}
        placeholder="최대 3개까지 입력 가능"
      />
    );
  };

export default TagsInputWithMaxTagsExample;

CustomDelimiter

import { useState } from 'react';
import { TagsInput } from '@mildang/design-system/Combobox';

const TagsInputCustomDelimiterExample = () => {
    const [values, setValues] = useState<string[]>([]);
    return (
      <TagsInput
        value={values}
        onValueChange={(details) => setValues(details.value)}
        delimiter={/;|\|/}
        placeholder="세미콜론(;) 또는 파이프(|)로 구분"
      />
    );
  };

export default TagsInputCustomDelimiterExample;

WithAllowedValues

import { useState } from 'react';
import { TagsInput } from '@mildang/design-system/Combobox';

const TagsInputWithAllowedValuesExample = () => {
    const [values, setValues] = useState<string[]>([]);
    const allowedFrameworks = ['React', 'Vue', 'Angular', 'Svelte', 'Next.js', 'Nuxt.js'];

    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '400px' }}>
        <TagsInput
          value={values}
          onValueChange={(details) => setValues(details.value)}
          allowedValues={allowedFrameworks}
          placeholder="허용된 프레임워크만 입력 가능"
        />
        <div style={{ fontSize: '14px', color: '#666' }}>
          <p>현재 태그: {values.join(', ') || '(없음)'}</p>
          <p style={{ marginTop: '8px', padding: '8px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
            💡 허용된 값: {allowedFrameworks.join(', ')}
            <br />
            <br />
            allowedValues에 포함된 값만 태그로 추가할 수 있습니다. 다른 값을 입력하면 Enter 키를 눌러도
            추가되지 않습니다.
          </p>
        </div>
      </div>
    );
  };

export default TagsInputWithAllowedValuesExample;

WithValidation

import { useState } from 'react';
import { TagsInput } from '@mildang/design-system/Combobox';

const TagsInputWithValidationExample = () => {
    const [values, setValues] = useState<string[]>([]);
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '400px' }}>
        <TagsInput
          value={values}
          onValueChange={(details) => setValues(details.value)}
          validate={(details: { value: unknown[]; inputValue: string }) => {
            const { value, inputValue } = details;
            const stringValues = value as string[];
            // 입력값이 비어있으면 false
            if (!inputValue || inputValue.trim() === '') {
              return false;
            }

            // 이미 존재하는 태그면 false
            if (stringValues.includes(inputValue)) {
              return false;
            }

            // 2-10글자 검증
            if (inputValue.length < 2 || inputValue.length > 10) {
              return false;
            }

            return true;
          }}
          placeholder="2-10글자 태그 입력"
        />
        <div style={{ fontSize: '14px', color: '#666' }}>
          <p>현재 태그: {values.join(', ') || '(없음)'}</p>
          <p style={{ marginTop: '8px', padding: '8px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
            💡 검증 규칙:
            <br />- 2-10글자만 허용
            <br />- 중복 불가
            <br />- 빈 값 불가
          </p>
        </div>
      </div>
    );
  };

export default TagsInputWithValidationExample;

WithoutChip

import { useState } from 'react';
import { TagsInput } from '@mildang/design-system/Combobox';

const TagsInputWithoutChipExample = () => {
    const [values, setValues] = useState<string[]>(['React', 'Vue', 'Angular']);
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '400px' }}>
        <TagsInput
          value={values}
          onValueChange={(details) => setValues(details.value)}
          showChip={false}
          placeholder="텍스트로 이어서 표시됩니다"
        />
        <div style={{ fontSize: '14px', color: '#666' }}>
          <p>태그 개수: {values.length}</p>
          <p>표시 형식: {values.join(', ')}</p>
          <p style={{ marginTop: '8px', padding: '8px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
            💡 팁: showChip={false}이면 선택된 값들이 텍스트로 이어서 표시됩니다.
            <br />
            예: "React, Vue, Angular"와 같이 쉼표로 구분되어 표시됩니다.
            <br />칩 삭제 기능은 사용할 수 없지만, Clear 버튼으로 모든 값을 한 번에 지울 수 있습니다.
          </p>
        </div>
      </div>
    );
  };

export default TagsInputWithoutChipExample;

WithoutSearchIcon

import { useState } from 'react';
import { TagsInput } from '@mildang/design-system/Combobox';

const TagsInputWithoutSearchIconExample = () => {
    const [values, setValues] = useState<string[]>(['React', 'Vue', 'Angular']);
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '400px' }}>
        <TagsInput
          value={values}
          onValueChange={(details) => setValues(details.value)}
          showSearchIcon={false}
          placeholder="검색 아이콘 없이 표시됩니다"
        />
        <div style={{ fontSize: '14px', color: '#666' }}>
          <p>태그 개수: {values.length}</p>
          <p style={{ marginTop: '8px', padding: '8px', backgroundColor: '#f5f5f5', borderRadius: '4px' }}>
            💡 팁: showSearchIcon={false}로 설정하면 검색 아이콘이 표시되지 않습니다.
            <br />
            기본값은 showSearchIcon={true}로, 검색 아이콘이 표시됩니다.
            <br />
            <br />
            입력 검증(allowedValues)은 별도로 제어됩니다.
          </p>
        </div>
      </div>
    );
  };

export default TagsInputWithoutSearchIconExample;