Chip

Data Input

짧은 텍스트나 상태·분류를 작게 담는 칩.

Usage

짧은 텍스트/상태/분류를 작게 표시하고 선택·제거·실행. 필터·태그·키워드

import

import

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

예제를 복사해 쓸 때 필요한 준비

@mildang/icons 를 따로 설치한다. DS 패키지에 아이콘 컴포넌트가 포함되지 않는다.

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

API Reference

Chip Props

Prop

Type

Default

as

"div" | "span"

div

asChild

boolean

false

className

string

지정 안 함

color

"brand" | "neutral" | "green" | "orange" | "blue" | "red"

neutral

deleteIcon

ReactNode

지정 안 함

disabled

boolean

false

endIcon

ReactNode

지정 안 함

id

string

지정 안 함

label

ReactNode

지정 안 함

multiline

boolean

false

onClick

((event: MouseEvent<HTMLElement, MouseEvent>) => void) & MouseEventHandler<HTMLDivElement>

지정 안 함

onDelete

(event: KeyboardEvent<HTMLElement> | MouseEvent<HTMLButtonElement, MouseEvent>) => void

지정 안 함

size

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

md

startIcon

ReactNode

지정 안 함

sx

SystemStyleObject | (SystemStyleObject & SystemStyleObject[])

지정 안 함

tabIndex

number

지정 안 함

type

"fill" | "outlined" | "text" | "transparent"

fill

크기

xs부터 lg까지 4단계다. 글자 크기·좌우 여백·아이콘 크기가 함께 바뀌고, 칩 높이는 그 조합의 결과다.

size높이(px, mildang 기준)주 용도
lg32가시성이 필요한 태그/선택(강조용), 멀티라인 최대 2줄 허용. 모바일 주요 필터·선택 토큰 등 터치 우선 화면
md(기본값)24기본 사이즈(대부분의 화면). 필터/선택 토큰/카테고리 등 일반 용도
sm20조밀 레이아웃(리스트·카드·테이블 보조 정보). 보조 액션/짧은 라벨 표기
xs18PC 전용 초밀도(테이블 셀 상태 태그). 정적 태그 위주, 모바일 핵심 입력은 비권장

높이는 mildang/school/goes 테마 기준이다. educore는 sm 20→24px, md 24→28px로 다르다(xs·lg는 동일).

Sizes

xs부터 lg까지 4단계 크기 스케일을 비교합니다.

import { Chip, type ChipProps } from '@mildang/design-system/Chip';
import { useState } from 'react';

const ChipArrayDemo = ({
  color,
  type,
  size,
  disabled,
}: Pick<ChipProps, 'color' | 'type' | 'size' | 'disabled'>) => {
  const [items, setItems] = useState<string[]>(['Angular', 'Polymer', 'React', 'Vue.js']);
  return (
    <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
      {items.map((label) => (
        <Chip
          key={label}
          label={label}
          color={color}
          type={type}
          size={size}
          disabled={disabled}
          onDelete={() => setItems((prev) => prev.filter((l) => l !== label))}
        />
      ))}
    </div>
  );
};

const STORY_DEFAULT_ARGS = { ...({}), ...({ color: 'neutral', type: 'fill', size: 'md' }) } as ChipProps;

const ChipArrayDeletableExampleRender = (args: ChipProps) => <ChipArrayDemo {...args} />;

export default function ChipArrayDeletableExample(props: Partial<ChipProps>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ChipProps;
  return ChipArrayDeletableExampleRender(mergedProps);
}

타입 · 색

type 4가지(fill/outlined/text/transparent)는 배경·테두리·글자색 슬롯을 채우는 방식이 다르고, color 6가지(brand/neutral/green/orange/blue/red)와 조합된다. 슬롯 매핑은 색마다 개별로 정의돼 있어 조합별로 값이 다르다.

예외가 하나 있다: fill+orange 조합만 다른 색들과 달리 전용 글자색 토큰을 쓴다 — 나머지 색은 공통 흰색 글자를 쓰지만, 오렌지 배경 위 흰 글자는 대비가 부족해 별도로 보정한 값이다.

Types

6가지 color와 4가지 type의 24개 조합을 한 화면에서 비교합니다.

코드

import { Chip, type ChipProps } from '@mildang/design-system/Chip';
import { ChipVariant } from '@mildang/styled-system/recipes';

const chipVariantValues = {
  size: ['xs', 'sm', 'md', 'lg'] as ChipVariant['size'][],
  color: ['brand', 'neutral', 'green', 'orange', 'blue', 'red'] as ChipVariant['color'][],
  type: ['fill', 'outlined', 'text', 'transparent'] as ChipVariant['type'][],
  multiline: [true, false] as ChipVariant['multiline'][],
};

const chipColorLabels: Record<NonNullable<ChipVariant['color']>, string> = {
  brand: 'Brand',
  neutral: 'Neutral',
  green: 'Green',
  orange: 'Orange',
  blue: 'Blue',
  red: 'Red',
};

const chipTypeLabels: Record<NonNullable<ChipVariant['type']>, string> = {
  fill: 'Fill',
  outlined: 'Outlined',
  text: 'Text',
  transparent: 'Transparent',
};

const STORY_DEFAULT_ARGS = { ...({}), ...({ size: 'md' }) } as ChipProps;

const ChipTypeColorMatrixExampleRender = (args: ChipProps) => (
    <div style={{ display: 'grid', gap: 20 }}>
      {chipVariantValues.type.map((type) => (
        <div key={type} style={{ display: 'grid', gap: 8 }}>
          <strong>{chipTypeLabels[type]}</strong>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {chipVariantValues.color.map((color) => (
              <Chip
                key={`${type}-${color}`}
                {...args}
                label={chipColorLabels[color]}
                color={color}
                type={type}
              />
            ))}
          </div>
        </div>
      ))}
    </div>
  );

export default function ChipTypeColorMatrixExample(props: Partial<ChipProps>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ChipProps;
  return ChipTypeColorMatrixExampleRender(mergedProps);
}

예제

Action vs Input

onClick(Action)과 onDelete(Input) — Chip이 실제로 구분하는 두 상호작용입니다.

코드

import { Chip, type ChipProps } from '@mildang/design-system/Chip';
import Hashtag from '@mildang/icons/react/custom/hashtag';
import { ChipVariant } from '@mildang/styled-system/recipes';

const chipVariantValues = {
  size: ['xs', 'sm', 'md', 'lg'] as ChipVariant['size'][],
  color: ['brand', 'neutral', 'green', 'orange', 'blue', 'red'] as ChipVariant['color'][],
  type: ['fill', 'outlined', 'text', 'transparent'] as ChipVariant['type'][],
  multiline: [true, false] as ChipVariant['multiline'][],
};

const chipColorLabels: Record<NonNullable<ChipVariant['color']>, string> = {
  brand: 'Brand',
  neutral: 'Neutral',
  green: 'Green',
  orange: 'Orange',
  blue: 'Blue',
  red: 'Red',
};

const chipTypeLabels: Record<NonNullable<ChipVariant['type']>, string> = {
  fill: 'Fill',
  outlined: 'Outlined',
  text: 'Text',
  transparent: 'Transparent',
};

const STORY_DEFAULT_ARGS = { ...({}), ...({ size: 'md' }) } as ChipProps;

const ChipStatusStatesExampleRender = (args: ChipProps) => (
    <div style={{ display: 'grid', gap: 32 }}>
      {chipVariantValues.type.map((type) => (
        <div key={type} style={{ display: 'grid', gap: 8 }}>
          <strong>{type ? chipTypeLabels[type] : ''}</strong>
          <div
            style={{
              display: 'grid',
              // `auto` 최대 트랙은 남는 폭을 흡수하고, grid item 은 기본적으로 stretch 되므로
              // Chip(Figma 정본 HUG = `inline-flex`)이 셀 폭까지 늘어난다.
              // 늘어난 칩은 `justifyContent: center` 때문에 좌우 패딩이 커진 것처럼 보인다.
              // → 트랙 최대치를 `max-content` 로, item 정렬을 `start` 로 고정해 고유 폭을 유지한다.
              gridTemplateColumns: '96px repeat(6, minmax(72px, max-content))',
              gap: 12,
              alignItems: 'center',
              justifyItems: 'start',
            }}
          >
            <span />
            {chipVariantValues.color.map((color) => (
              <span key={color} style={{ fontSize: 12, color: '#8b8b8b' }}>
                {color ? chipColorLabels[color] : ''}
              </span>
            ))}

            <span style={{ fontSize: 12, color: '#8b8b8b' }}>default</span>
            {chipVariantValues.color.map((color) => (
              <Chip
                key={color}
                {...args}
                type={type}
                color={color}
                label="Chips"
                startIcon={<Hashtag />}
                onDelete={() => {}}
              />
            ))}

            {/* hover 행: pseudo 애드온이 :hover 를 강제. onDelete(hasDelete) 로 clickable=true → 오버레이 CSS 생성 */}
            <span style={{ fontSize: 12, color: '#8b8b8b' }}>hover</span>
            {chipVariantValues.color.map((color) => (
              <Chip
                key={color}
                {...args}
                type={type}
                color={color}
                label="Chips"
                startIcon={<Hashtag />}
                onDelete={() => {}}
                id={`chip-${type}-${color}-hover`}
              />
            ))}
          </div>
        </div>
      ))}
    </div>
  );

export default function ChipStatusStatesExample(props: Partial<ChipProps>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ChipProps;
  return ChipStatusStatesExampleRender(mergedProps);
}

TypeGuide

import { Chip, type ChipProps } from '@mildang/design-system/Chip';

const STORY_DEFAULT_ARGS = { ...({}), ...({ color: 'green', size: 'md' }) } as ChipProps;

const ChipTypeGuideExampleRender = (args: ChipProps) => (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, auto)', gap: 24 }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center' }}>
        <strong>Fill</strong>
        <Chip {...args} label="Chips" type="fill" />
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center' }}>
        <strong>Text</strong>
        <Chip {...args} label="Chips" type="text" />
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center' }}>
        <strong>Transparency</strong>
        <Chip {...args} label="Chips" type="transparent" />
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center' }}>
        <strong>Outline</strong>
        <Chip {...args} label="Chips" type="outlined" />
      </div>
    </div>
  );

export default function ChipTypeGuideExample(props: Partial<ChipProps>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ChipProps;
  return ChipTypeGuideExampleRender(mergedProps);
}

IconGuide

코드

import { Chip, type ChipProps } from '@mildang/design-system/Chip';
import { Fragment } from 'react';
import { ChipVariant } from '@mildang/styled-system/recipes';
import Hashtag from '@mildang/icons/react/custom/hashtag';

const chipVariantValues = {
  size: ['xs', 'sm', 'md', 'lg'] as ChipVariant['size'][],
  color: ['brand', 'neutral', 'green', 'orange', 'blue', 'red'] as ChipVariant['color'][],
  type: ['fill', 'outlined', 'text', 'transparent'] as ChipVariant['type'][],
  multiline: [true, false] as ChipVariant['multiline'][],
};

const chipColorLabels: Record<NonNullable<ChipVariant['color']>, string> = {
  brand: 'Brand',
  neutral: 'Neutral',
  green: 'Green',
  orange: 'Orange',
  blue: 'Blue',
  red: 'Red',
};

const chipTypeLabels: Record<NonNullable<ChipVariant['type']>, string> = {
  fill: 'Fill',
  outlined: 'Outlined',
  text: 'Text',
  transparent: 'Transparent',
};

const iconConfigs = [
  { label: 'None', props: {} },
  { label: 'Left icon', props: { startIcon: <Hashtag /> } },
  { label: 'Right icon', props: { onDelete: () => {} } },
  { label: 'Left + Right', props: { startIcon: <Hashtag />, onDelete: () => {} } },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({ size: 'md' }) } as ChipProps;

const ChipIconGuideExampleRender = (args: ChipProps) => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
      {chipVariantValues.type.map((type) => (
        <div key={type}>
          <p style={{ fontWeight: 700, marginBottom: 12 }}>{chipTypeLabels[type]}</p>
          <div
            style={{
              display: 'grid',
              gridTemplateColumns: `120px repeat(${iconConfigs.length}, auto)`,
              gap: 12,
              alignItems: 'center',
            }}
          >
            <div />
            {iconConfigs.map(({ label }) => (
              <strong key={label} style={{ textAlign: 'center', fontSize: 12 }}>
                {label}
              </strong>
            ))}
            {chipVariantValues.color.map((color) => (
              <Fragment key={color}>
                <strong style={{ fontSize: 12 }}>{chipColorLabels[color]}</strong>
                {iconConfigs.map(({ label, props }) => (
                  <div key={label} style={{ display: 'flex', justifyContent: 'center' }}>
                    <Chip {...args} label="Chips" color={color} type={type} {...props} />
                  </div>
                ))}
              </Fragment>
            ))}
          </div>
        </div>
      ))}
    </div>
  );

export default function ChipIconGuideExample(props: Partial<ChipProps>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ChipProps;
  return ChipIconGuideExampleRender(mergedProps);
}

Multiline

import { Chip, type ChipProps } from '@mildang/design-system/Chip';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: 'This is a chip that has multiple lines. ',
    color: 'green',
    type: 'fill',
    multiline: true,
  }) } as ChipProps;

const ChipMultilineExampleRender = (args: ChipProps) => (
    <div style={{ width: '100px' }}>
      <Chip {...args} />
    </div>
  );

export default function ChipMultilineExample(props: Partial<ChipProps>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ChipProps;
  return ChipMultilineExampleRender(mergedProps);
}

Ellipsis

import { Chip, type ChipProps } from '@mildang/design-system/Chip';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: 'This is a chip that has multiple lines. ',
    color: 'green',
    type: 'fill',
  }) } as ChipProps;

const ChipEllipsisExampleRender = (args: ChipProps) => (
    <div style={{ width: '100px' }}>
      <Chip {...args} />
    </div>
  );

export default function ChipEllipsisExample(props: Partial<ChipProps>) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ChipProps;
  return ChipEllipsisExampleRender(mergedProps);
}

Demo

import type { ComponentProps } from 'react';
import { Chip, type ChipProps } from '@mildang/design-system/Chip';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: 'Chip',
    color: 'neutral',
    type: 'fill',
    size: 'md',
    // 문자열 키로 둔다 — argTypes 의 `mapping` 이 실제 ReactNode 로 변환한다
    startIcon: 'none',
    endIcon: 'none',
    onDelete: false,
  } as unknown as ChipProps) } as ComponentProps<typeof Chip>;

export default function ChipDemoExample(props: Partial<ComponentProps<typeof Chip>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chip>;
  return <Chip {...mergedProps} />;
}