Input · TagInput

TagInput

Taginput is a input component that can add content as a tag.

Demos

How to import

import { Avatar, Button, TagInput, Toast } from '@fibrous/semi-ui-solid';

Basic Usage

After pressing the Enter key, the input will add value as a tag. If the tag content is an empty string or pure space, it will be filtered.

抖音

火山

西瓜视频

Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return <TagInput defaultValue={['抖音', '火山', '西瓜视频']} placeholder="请输入..." onChange={(v) => console.log(v)} />;
}

Batch Add

You can use separator to set the separator to achieve batch input, and its default value is a comma. Multiple separators are supported in string[] format.



Code
import { TagInput } from '@fibrous/semi-ui-solid';

const separators = ['-', '/', '|', '++'];

export default function TagInputDemo() {
    return (
        <>
            <TagInput separator="-" placeholder="使用 - 进行批量输入" onChange={(v) => console.log(v)} />
            <br />
            <br />
            <TagInput separator={separators} placeholder="支持多个分隔符进行批量输入" onChange={(v) => console.log(v)} />
        </>
    );
}

Batch Remove

You can also use showClear to set whether to support one-click deletion of all tags and input.

抖音

火山

Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return <TagInput showClear defaultValue={['抖音', '火山']} placeholder="请输入..." onChange={(v) => console.log(v)} />;
}

Disabled

抖音

火山

西瓜视频

Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return <TagInput disabled showClear defaultValue={['抖音', '火山', '西瓜视频']} placeholder="请输入..." />;
}

Size

Use size to set the size of the TagInput, optional: small, default, large.





Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return (
        <>
            <TagInput size="small" placeholder="small" />
            <br />
            <br />
            <TagInput placeholder="default" />
            <br />
            <br />
            <TagInput size="large" placeholder="large" />
        </>
    );
}

Different validate status

validateStatus: default, warning, error.





Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return (
        <>
            <TagInput placeholder="default" />
            <br />
            <br />
            <TagInput placeholder="warning" validateStatus="warning" />
            <br />
            <br />
            <TagInput placeholder="error" validateStatus="error" />
        </>
    );
}

Prefix / Suffix

You can pass the input box prefix through prefix and the suffix through suffix, as text or JSX elements.
The left and right padding is automatically brought when the content passed in by prefix and suffix is text or an Icon. For a custom JSX element the padding is 0; set it on the element you pass in if needed.



Prefix




Suffix
Code
import { IconGift, IconVigoLogo } from '@fibrous/semi-icons-solid';
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return (
        <>
            <TagInput prefix={<IconVigoLogo />} />
            <br />
            <br />
            <TagInput prefix="Prefix" />
            <br />
            <br />
            <TagInput suffix={<IconGift />} />
            <br />
            <br />
            <TagInput suffix="Suffix" />
        </>
    );
}

Autocomplete

You can use addOnBlur to set whether the current input value is automatically created as a tag when the blur event is triggered.

抖音

火山

西瓜视频

Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return <TagInput defaultValue={['抖音', '火山', '西瓜视频']} addOnBlur placeholder="请输入..." onChange={(v) => console.log(v)} />;
}

Allow Duplicates

You can use allowDuplicates to set whether to allow the creation of the same tag.

抖音

火山

西瓜视频

Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return <TagInput defaultValue={['抖音', '火山', '西瓜视频']} allowDuplicates={false} placeholder="请输入..." onChange={(v) => console.log(v)} />;
}

Limits

You can use max to limit the number of tags. Tags added past the limit are dropped, and the onExceed() callback is invoked.

You can use maxLength to limit the maximum length of a single tag, and the onInputExceed() callback will be invoked when this value is exceeded.

Code
import { TagInput, Toast } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return (
        <>
            <TagInput
                max={3}
                placeholder="最多输入3条标签.."
                onChange={(v) => console.log(v)}
                onExceed={(v) => {
                    Toast.warning('超过 max');
                    console.log(v);
                }}
            />
            <TagInput
                maxLength={5}
                placeholder="单个标签长度不超过5..."
                style={{ 'margin-top': '12px' }}
                onChange={(v) => console.log(v)}
                onInputExceed={(v) => {
                    Toast.warning('超过 maxLength');
                    console.log(v);
                }}
            />
        </>
    );
}

Limit the number of tags displayed

You can use maxTagCount to limit the number of tags displayed, and the excess will be displayed as +N. You can use showRestTagsPopover to set whether hover +N displays Popover after maxTagCount is exceeded, and you can configure Popover in the restTagsPopoverProps property.

抖音

火山

+1
Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return <TagInput maxTagCount={2} showRestTagsPopover restTagsPopoverProps={{ position: 'top' }} defaultValue={['抖音', '火山', '西瓜视频']} onChange={(v) => console.log(v)} />;
}

Controlled Tag

You can use value to set tags, and use onChange to achieve control of the tags.

抖音

Code
import { createSignal } from 'solid-js';
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    const [value, setValue] = createSignal(['抖音']);
    return <TagInput value={value()} onChange={(v) => setValue(v)} />;
}

Controlled Input

You can use inputValue to set the input box, and use onInputChange to control the input content.

Code
import { createSignal } from 'solid-js';
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    const [value, setValue] = createSignal('abc');
    return <TagInput inputValue={value()} onInputChange={(v) => setValue(v)} />;
}

Callback

抖音

Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return (
        <TagInput
            defaultValue={['抖音']}
            placeholder="请输入..."
            showClear
            onFocus={() => {
                console.log(`onFocus`);
            }}
            onBlur={() => {
                console.log(`onBlur`);
            }}
            onChange={(tag) => {
                console.log(`onChange,当前标签数组:${tag}`);
            }}
            onAdd={(tag) => {
                console.log(`onAdd,新增:${tag}`);
            }}
            onRemove={(v, i) => {
                console.log(`onRemove,移除:${v}, 序号:${i}`);
            }}
            onInputChange={(input) => {
                console.log(`onInputChange,当前输入内容:${input}`);
            }}
        />
    );
}

Focus Management

Get the root element through the component's ref, then call its focus() and blur() methods to manage the focus. focus() focuses the input and activates the component; blur() removes the focus from whichever element inside the component holds it.

抖音

火山

Code
import { Button, TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    let tagInput: HTMLDivElement | undefined;

    return (
        <>
            <TagInput defaultValue={['抖音', '火山']} ref={(node) => (tagInput = node)} />
            <Button style={{ 'margin-top': '10px' }} onClick={() => tagInput?.focus()}>
                点击按钮聚焦
            </Button>
        </>
    );
}

Custom TagInput rendering

You can use renderTagItem to customize tag rendering. renderTagItem(value: string, index: number, onClose: () => void) => JSX.Element; the third parameter onClose removes that tag and can be passed directly as a button's onClick.

avatar夏可漫@semi.com
Code
import { createSignal } from 'solid-js';
import { IconClose } from '@fibrous/semi-icons-solid';
import { Avatar, Button, TagInput } from '@fibrous/semi-ui-solid';

const list = [
    {
        name: '夏可漫',
        avatar: '/assets/root-web-sites/avatarDemo.jpeg',
    },
    {
        name: '申悦',
        avatar: '/assets/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg',
    },
    {
        name: '曲晨一',
        avatar: '/assets/root-web-sites/bf8647bffab13c38772c9ff94bf91a9d.jpg',
    },
    {
        name: '文嘉茂',
        avatar: '/assets/root-web-sites/avatarDemo.jpeg',
    },
];
const mapList = new Map(list.map((item) => [item.name, item]));
const fallbackAvatar = '/assets/placeholder/avatar-a.svg';

function renderTagItem(value: string, _index: number, onClose: () => void) {
    const data = mapList.get(value);
    return (
        <div
            style={{
                'background-color': 'var(--semi-color-info-light-default)',
                padding: '4px 8px',
                'border-radius': '12px',
                display: 'flex',
                'align-items': 'center',
                'font-size': '14px',
                'margin-right': '10px',
            }}
        >
            <Avatar alt="avatar" src={data ? data.avatar : fallbackAvatar} size="extra-small" />
            <span style={{ 'margin-left': '8px' }}>{`${value}@semi.com`}</span>
            <Button theme="borderless" type="tertiary" size="small" icon={<IconClose size="small" />} aria-label={`移除 ${value}`} onClick={onClose} />
        </div>
    );
}

export default function TagInputDemo() {
    const [tags, setTags] = createSignal(['夏可漫']);
    return <TagInput value={tags()} onChange={(v) => setTags(v)} renderTagItem={renderTagItem} />;
}

Drag to sort

Set draggable to true to enable drag and drop sorting. After the drag function is enabled, once TagInput is focused (by click or Tab) the tags can be dragged; after the focus leaves TagInput they cannot. The drag handle sits beside its tag and works from the keyboard, see "Keyboard and focus" below. Tags with the same value can be sorted separately.

抖音

火山

西瓜视频

Code
import { TagInput } from '@fibrous/semi-ui-solid';

export default function TagInputDemo() {
    return <TagInput draggable allowDuplicates={false} defaultValue={['抖音', '火山', '西瓜视频']} placeholder="请输入..." onChange={(v) => console.log(v)} />;
}

API Reference

Props

PropertyDescriptionTypeDefault
addOnBlurWhether the input text becomes a tag when the focus leaves the component.booleanfalse
allowDuplicatesWhether the same tag can be added more than once.booleantrue
aria-labelAccessible name of the input. It is set on the input, not on the root.string-
autofocusWhether the input is focused and the component activated on mount; ignored when disabled.booleanfalse
clearIconReplaces the icon in the clear button; empty content keeps the default icon.JSX.Element-
defaultValueInitial tags when uncontrolled.string[]-
disabledWhether the component is disabled.boolean-
draggableWhether tags can be reordered by dragging.booleanfalse
expandRestTagsOnClickWhen not draggable, whether the collapsed tags expand once focus enters the component (click, Tab or the focus method).booleantrue
inputValueCurrent input text. Pair it with onInputChange for a controlled input.string-
insetLabelInset label shown before the input; when prefix is also given, only prefix is shown. When shown, it names an input that has no aria-label or aria-labelledby.JSX.Element-
insetLabelIdid of the inset label container, for aria-labelledby; the component generates one when omitted.string-
maxMaximum number of tags.number-
maxLengthMaximum length of one tag.number-
maxTagCountMaximum number of tags shown; the rest are shown as +N.number-
nameThe name of the text input, for forms.string-
onAddCalled when tags are added, with the added tags.Type(addedValue: string[]) => void-
onBlurCalled once when the focus leaves the whole component; moving inside it does not call it.Type(e: FocusEvent & { currentTarget: HTMLDivElement; target: Element }) => void-
onChangeCalled when the tags change, with the whole list of tags.Type(value: string[]) => void-
onExceedCalled when adding would exceed max, with the whole list before it is cut.Type(value: string[]) => void-
onFocusCalled once when the focus enters from outside; moving between the input, tags and clear button does not call it.Type(e: FocusEvent & { currentTarget: HTMLDivElement; target: Element }) => void-
onInputChangeCalled when the input text changes. The event may be an input event, a KeyboardEvent (Enter adds), a FocusEvent (addOnBlur), a MouseEvent (clear) or a CompositionEvent (IME truncation); narrow it by event.type before reading its own fields.Type(value: string, e: Event) => void-
onInputExceedCalled when the input text exceeds maxLength, with the rejected input text.Type(value: string) => void-
onKeyDownCalled on keydown in the input after the component handled it; calling preventDefault there does not stop Enter from adding a tag.Type(e: InputElementEvent<KeyboardEvent>) => void-
onRemoveCalled when a tag is removed, with the removed tag and its index.Type(removedValue: string, idx: number) => void-
placeholderPlaceholder of the input while there are no tags.string-
prefixPrefix of the input; text or an icon gets side spacing automatically, while a custom element gets none and can set its own.JSX.Element-
preventScrollWhether the focus calls inside the component stop the browser from scrolling the focused element into view.boolean-
renderTagItemRenders each tag with a custom element in place of the default tag. The third argument, onClose, removes that tag and can be passed directly as a button's onClick.Type(value: string, index: number, onClose: () => void) => JSX.Element-
restTagsPopoverPropsPopover props of the +N overlay, such as position and trigger. Do not pass content: it replaces the list of remaining tags.RestTagsPopoverProps-
separatorSeparator(s) for adding several tags at once; null disables splitting.string | string[] | null","
showClearWhether to show the button that clears all tags and the input text.booleanfalse
showContentTooltipWhether hovering a truncated tag shows its full text in an overlay; an object sets the overlay type (tooltip or popover) and passes opts to it.boolean | ShowTooltiptrue
showRestTagsPopoverWhether hovering +N shows the remaining tags in a Popover once maxTagCount is exceeded.booleantrue
sizeSize: small, default or large.TagInputSize"default"
splitCustom split function that receives the raw string and the separator, and returns the split strings.Type(originString: string, separators: string | string[] | null) => string[]-
suffixSuffix of the input; text or an icon gets side spacing automatically, while a custom element gets none and can set its own.JSX.Element-
validateStatusValidation status, which only affects styling; error also sets aria-invalid on the input.TagInputValidateStatus"default"
valueCurrent tags, pair it with onChange for a controlled list; undefined means uncontrolled, null means controlled with no tags.string[] | null-

class, style, ref, and native attributes and events land on the root element, except id, name, role, tabIndex, and the aria-* attributes that describe the input (aria-label, aria-labelledby, aria-describedby, aria-invalid, aria-required, aria-controls, aria-expanded, aria-haspopup, aria-autocomplete, aria-activedescendant, aria-errormessage), which land on the inner input. onClick, onMouseDown, onFocusIn and onFocusOut call the function you pass in addition to the component's own handling.

Accessibility

ARIA

  • TagInput supports the input of aria-label to indicate the function of the TagInput; it lands on the input box, not on the root node;
  • The root node has the group role, which holds the input, the tags and the clear button as one control; when disabled it carries aria-disabled="true", otherwise it has no aria-disabled;
  • When disabled, the input is natively disabled and the tags are static text without an aria-label; with validateStatus set to error the input carries aria-invalid, and an explicit aria-invalid wins;
  • With showClear, tags or input text, and no disabled, a clear button is rendered. It is a native button named "Clear TagInput value" from the locale, shown on hover or while focus is inside the TagInput. From the input, Tab moves to the clear button; Enter or Space clears, as a mouse click does, and the focus goes back to the input. Backspace in the empty input also removes the last tag.

Keyboard and focus

  • Enter in the input adds the input text as a tag; with empty input it does nothing. Enter always has its default action prevented, so a TagInput inside a form never submits it.
  • Backspace in the empty input removes the last tag.
  • A closable tag is a role="button" Tab stop placed before the input; with a tag focused, Backspace or Delete closes it and the focus goes back to the input. When disabled, tags are static text and not focusable.
  • With showClear and some content, the clear button follows the input: Tab reaches it, Enter or Space clears, and the focus goes back to the input.
  • The active state follows the focus: it starts when the focus comes in from outside (click, Tab or focus()) and ends when it leaves the whole component. Moving the focus between the input, the tags and the clear button is not leaving, so it does not fire focus or blur and does not trigger addOnBlur. While active, tags folded by maxTagCount expand (unless expandRestTagsOnClick is false).
  • With draggable and the component active, each tag gets a drag handle that is its own Tab stop, placed before the tag it belongs to. Focus a handle, press Space to pick up, the arrow keys to move and Space to drop; screen readers announce the pick up, moves and drop.

Design Tokens

These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
Sass variableValueUsage
$transition_duration-tagInput-bgvar(--semi-transition_duration-none)标签输入框-背景色-动画持续时间
$transition_function-tagInput-bgvar(--semi-transition_function-easeIn)标签输入框-背景色-过渡曲线
$transition_delay-tagInput-bgvar(--semi-transition_delay-none)标签输入框-背景色-延迟时间
$transition_duration-tagInput-bordervar(--semi-transition_duration-none)标签输入框-边框-动画持续时间
$transition_function-tagInput-bordervar(--semi-transition_function-easeIn)标签输入框-边框-过渡曲线
$transition_delay-tagInput-bordervar(--semi-transition_delay-none)标签输入框-边框-延迟时间
$color-tagInput-icon-defaultvar(--semi-color-text-2)标签输入框图标颜色 - 默认
$color-tagInput-icon-hovervar(--semi-color-primary-hover)标签输入框图标颜色 - 悬浮
$color-tagInput_disabled-text-defaultvar(--semi-color-disabled-text)标签输入框禁用文字颜色
$color-tagInput_disabled-bgvar(--semi-color-disabled-fill)标签输入框禁用背景色
$color-tagInput_default-bg-focusvar(--semi-color-fill-0)标签输入框背景颜色 - 选中态
$color-tagInput-border-defaulttransparent标签输入框描边颜色 - 默认
$color-tagInput-border-hovertransparent标签输入框描边颜色 - 悬浮
$color-tagInput-border-focusvar(--semi-color-focus-border)标签输入框描边颜色 - 选中态
$color-tagInput_prefix-defaultvar(--semi-color-text-2)标签输入框 prefix 颜色
$color-tagInput_suffix-defaultvar(--semi-color-text-2)标签输入框 suffix 颜色
$color-tagInput_default-bg-defaultvar(--semi-color-fill-0)标签输入框背景颜色 - 默认
$color-tagInput_default-bg-hovervar(--semi-color-fill-1)标签输入框背景颜色 - 悬浮
$color-tagInput_warning-bg-defaultvar(--semi-color-warning-light-default)警告标签输入框背景颜色 - 默认
$color-tagInput_warning-border-defaultvar(--semi-color-warning-light-default)警告标签输入框描边颜色 - 默认
$color-tagInput_warning-bg-hovervar(--semi-color-warning-light-hover)警告标签输入框背景颜色 - 悬浮
$color-tagInput_warning-border-hovervar(--semi-color-warning-light-hover)警告标签输入框描边颜色 - 悬浮
$color-tagInput_warning-bg-focusvar(--semi-color-warning-light-default)警告标签输入框背景颜色 - 选中
$color-tagInput_warning-border-focusvar(--semi-color-warning)警告标签输入框描边颜色 - 选中
$color-tagInput_danger-bg-defaultvar(--semi-color-danger-light-default)危险标签输入框背景颜色 - 默认
$color-tagInput_danger-border-defaultvar(--semi-color-danger-light-default)危险标签输入框描边颜色 - 默认
$color-tagInput_danger-bg-hovervar(--semi-color-danger-light-hover)危险标签输入框背景颜色 - 悬浮
$color-tagInput_danger-border-hovervar(--semi-color-danger-light-hover)危险标签输入框描边颜色 - 悬浮
$color-tagInput_danger-bg-focusvar(--semi-color-danger-light-default)危险标签输入框背景颜色 - 选中
$color-tagInput_danger-border-focusvar(--semi-color-danger)危险标签输入框描边颜色 - 选中
$color-tagInput_sortable_item_over-bgvar(--semi-color-primary)拖拽经过的元素前竖线背景色
$color-tagInput_handler-icon-defaultvar(--semi-color-text-2)可拖拽的标签拖拽按钮颜色
$spacing-tagInput_small-Y1px小尺寸标签输入框标签顶部外边距
$spacing-tagInput_default-Y$spacing-super-tight默认尺寸标签输入框标签顶部外边距
$spacing-tagInput_large-Y3px大尺寸标签输入框标签顶部外边距
$spacing-tagInput_wrapper_n_paddingX$spacing-tight标签输入框标签容器水平内边距
$spacing-tagInput_drag_handler-marginRight4px拖拽handler icon的右外边距
$spacing-tagInput_tag_icon_paddingLeft4pxtag中有handler icon时tag的左内边距
$spacing-tagInput_prefix_suffix-marginX$spacing-base-tight标签输入框 prefix/suffix 水平外边距
$height-tagInput-large$height-control-large大尺寸标签输入框高度
$height-tagInput-default$height-control-default默认尺寸标签输入框高度
$height-tagInput-small$height-control-small小尺寸标签输入框高度
$height-tagInput_input_small20px小尺寸标签输入框Input框高度
$height-tagInput_input_default24px默认尺寸标签输入框Input框高度
$height-tagInput_input_large24px大尺寸标签输入框Input框高度
$width-tagInput-clear-medium$width-icon-medium * 2标签输入框清空按钮宽度
$width-tagInput-border-default$border-thickness-control标签输入框描边描边宽度 - 默认
$width-tagInput-border-hover$width-tagInput-border-default标签输入框描边描边宽度 - 悬浮
$width-tagInput-border-focus$border-thickness-control-focus标签输入框描边宽度 - 选中态
$width-tagInput_sortable_item_over2px拖拽经过的元素前竖线宽度
$radius-tagInputvar(--semi-border-radius-small)标签输入框圆角
$font-tagInput_prefix_suffix-fontWeight$font-weight-bold标签输入框 prefix/suffix 文字字重