When to use
Used to provide input suggestions to the input box and perform automatic completion operations
The difference with the searchable Select component:
- AutoComplete is essentially an enhanced Input component that provides input suggestions, while Select is a selector
- When you click to expand, Select will clear all the values in the input box, and AutoComplete will keep the last selected value
- Select's selected item rendering (
renderSelectedItem) can return any JSX element, while AutoComplete'srenderSelectedItemonly returns a string
Demos
How to import
import { AutoComplete } from '@fibrous/semi-ui-solid';
Basic usage
Monitor user input through onSearch, pass input suggestions through data, and keep it controlled with value and onChange, which fires when the input box changes or an input item is selected
Code
import { createSignal } from 'solid-js';
import { IconSearch } from '@fibrous/semi-icons-solid';
import { AutoComplete } from '@fibrous/semi-ui-solid';
const domains = ['gmail.com', '163.com', 'qq.com'];
export default function AutoCompleteDemo() {
const [stringData, setStringData] = createSignal<string[]>([]);
const [value, setValue] = createSignal<string | number>('');
const handleStringSearch = (input: string) => {
setStringData(input ? domains.map((domain) => `${input}@${domain}`) : []);
};
const handleChange = (next: string | number) => {
console.log('onChange', next);
setValue(next);
};
return <AutoComplete data={stringData()} value={value()} showClear prefix={<IconSearch />} placeholder="搜索... " onSearch={handleStringSearch} onChange={handleChange} style={{ width: '200px' }} />;
}Custom option rendering
When you need to customize the rendering of candidates, data can be passed in an array of objects (each Object must contain label and value keys; value is the value picked by the candidate, and label is the text the candidate displays). Render each candidate through renderItem, which returns JSX.
Code
import { createSignal } from 'solid-js';
import { IconSearch } from '@fibrous/semi-icons-solid';
import { AutoComplete, Avatar } from '@fibrous/semi-ui-solid';
interface ListItem {
name: string;
email: string;
abbr: string;
color: 'amber' | 'indigo' | 'blue' | 'cyan';
value?: string;
label?: string;
}
const list: ListItem[] = [
{ name: '夏可漫', email: 'xiakeman@example.com', abbr: 'XK', color: 'amber' },
{ name: '申悦', email: 'shenyue@example.com', abbr: 'SY', color: 'indigo' },
{ name: '曲晨一', email: 'quchenyi@example.com', abbr: 'CY', color: 'blue' },
{ name: '文嘉茂', email: 'wenjiamao@example.com', abbr: 'JM', color: 'cyan' },
];
export default function AutoCompleteDemo() {
const [data, setData] = createSignal<ListItem[]>([]);
const handleStringSearch = (input: string) => {
setData(input ? list.map((item) => ({ ...item, value: item.name, label: item.email })) : []);
};
const renderOption = (item: ListItem) => (
<>
<Avatar color={item.color} size="small">
{item.abbr}
</Avatar>
<div style={{ 'margin-left': '4px' }}>
<div style={{ 'font-size': '14px', 'margin-left': '4px' }}>{item.name}</div>
<div style={{ 'margin-left': '4px' }}>{item.email}</div>
</div>
</>
);
return <AutoComplete aria-label="搜索邮箱" data={data()} showClear prefix={<IconSearch />} onSearch={handleStringSearch} renderItem={renderOption} renderSelectedItem={(option) => option.email} style={{ width: '280px' }} />;
}Remote search
Get the user input value from onSearch and update data dynamically
Code
import { createSignal } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import { debounce } from 'es-toolkit';
import { IconSearch } from '@fibrous/semi-icons-solid';
import { IconButton, IconForm, IconInput, IconSelect, IconTable } from '@fibrous/semi-icons-solid/lab';
import { AutoComplete, type AutoCompleteDataItem } from '@fibrous/semi-ui-solid';
// Icons are stored as components so each row mounts its own instance
const initList = [
{ value: 'select', label: '选择器', icon: IconSelect },
{ value: 'input', label: '输入框', icon: IconInput },
{ value: 'form', label: '表单', icon: IconForm },
{ value: 'button', label: '按钮', icon: IconButton },
{ value: 'table', label: '表格', icon: IconTable },
];
type ListItem = (typeof initList)[number];
export default function AutoCompleteDemo() {
const [loading, setLoading] = createSignal(false);
const [list, setList] = createSignal<ListItem[]>(initList);
const handleSearch = (inputValue: string) => {
setLoading(true);
const newList = inputValue ? initList.filter((item) => item.value.includes(inputValue)) : initList;
setTimeout(() => {
setList(newList);
setLoading(false);
}, 1000);
};
const search = debounce(handleSearch, 200);
const handleSelect = (value: string | number | AutoCompleteDataItem) => {
console.log(value);
};
const renderItem = (item: ListItem) => (
<div style={{ display: 'flex', 'align-items': 'center' }}>
<div style={{ 'font-size': '32px' }}>
<Dynamic component={item.icon} />
</div>
<div style={{ 'margin-left': '12px' }}>
<p>{item.value}</p>
<p>{item.label}</p>
</div>
</div>
);
// Unlike Select, this can return only a string, not a JSX element
const renderSelectedItem = (item: ListItem) => item.value;
return <AutoComplete aria-label="搜索" data={list()} style={{ width: '250px' }} prefix={<IconSearch />} onSearch={search} loading={loading()} renderItem={renderItem} renderSelectedItem={renderSelectedItem} onSelect={handleSelect} />;
}Size
The size of the input box can be set by setting size, optional small, default (default), large
Code
import { AutoComplete } from '@fibrous/semi-ui-solid';
const data = [1, 2, 3, 4];
export default function AutoCompleteDemo() {
return (
<div>
<AutoComplete data={data} size="small" placeholder="small" style={{ width: '200px' }} />
<br />
<br />
<AutoComplete data={data} size="default" placeholder="default" style={{ width: '200px' }} />
<br />
<br />
<AutoComplete data={data} size="large" placeholder="large" style={{ width: '200px' }} />
</div>
);
}The position of the drop-down menu
The position of the drop-down menu can be set by setting position, and the optional values refer to Tooltip position
Code
import { createSignal } from 'solid-js';
import { AutoComplete } from '@fibrous/semi-ui-solid';
const domains = ['gmail.com', '163.com', 'qq.com'];
export default function AutoCompleteDemo() {
const [data, setData] = createSignal<string[]>([]);
const change = (input: string) => {
setData(input ? domains.map((domain) => `${input}@${domain}`) : []);
};
return (
<div>
<AutoComplete data={data()} position="top" onSearch={change} placeholder="选项菜单在上方显示" style={{ width: '200px', margin: '10px' }} />
<AutoComplete data={data()} position="rightTop" onSearch={change} placeholder="选项菜单在右侧显示" style={{ width: '200px', margin: '10px' }} />
</div>
);
}Disabled
Code
import { AutoComplete } from '@fibrous/semi-ui-solid';
const data = [1, 2, 3, 4];
export default function AutoCompleteDemo() {
return <AutoComplete data={data} placeholder="禁用下拉菜单" disabled style={{ width: '200px' }} />;
}Validate status
Different verification states can be set to show different styles
Code
import { AutoComplete } from '@fibrous/semi-ui-solid';
export default function AutoCompleteDemo() {
return (
<>
<AutoComplete aria-label="Warning status" defaultValue="ies" validateStatus="warning" />
<br />
<br />
<AutoComplete aria-label="Error status" defaultValue="ies" validateStatus="error" />
<br />
<br />
<AutoComplete aria-label="Default status" defaultValue="ies" />
</>
);
}Custom empty content
Can set up custom display empty content
Code
import { createSignal } from 'solid-js';
import { IllustrationNoContent } from '@fibrous/semi-illustrations-solid';
import { AutoComplete, Empty } from '@fibrous/semi-ui-solid';
export default function AutoCompleteDemo() {
const [data, setData] = createSignal<number[]>([]);
const [loading, setLoading] = createSignal(false);
const fetchData = (input: string) => {
setLoading(true);
setTimeout(() => {
setData(input ? Array.from({ length: 5 }, () => Math.random()) : []);
setLoading(false);
}, 1000);
};
return <AutoComplete aria-label="搜索" loading={loading()} data={data()} emptyContent={<Empty style={{ padding: '12px', width: '300px' }} image={<IllustrationNoContent style={{ width: '150px', height: '150px' }} />} description="暂无内容" />} onSearch={fetchData} />;
}API reference
Props
| Property | Description | Type | Default |
|---|---|---|---|
| autoAdjustOverflow | Whether the dropdown flips automatically when it would be clipped. | boolean | - |
| autofocus | Whether the input is focused after mount. | boolean | false |
| clearIcon | Custom clear icon, effective when showClear is true. | JSX.Element | - |
| data | Data source of the candidates: an array of strings, numbers or objects. An object item's value is the picked value and its label is the row text (provide it; markup goes through renderItem). Other fields are kept as they are and reach the renderItem argument. Pass a new array to update the list. | T[] | [] |
| defaultActiveFirstOption | Whether the first candidate is highlighted when the panel opens, so that Enter picks it directly. | boolean | false |
| defaultOpen | Whether the dropdown panel is open on mount. | boolean | - |
| defaultValue | Initial value of the input when uncontrolled, read on mount only. | string | number | - |
| disabled | Whether the component is disabled. | boolean | - |
| dropdownClassName | Class name of the dropdown list, applied to the role="listbox" element. | string | - |
| dropdownMatchSelectWidth | Whether the minimum width of the dropdown equals the width of the input. | boolean | true |
| dropdownStyle | Inline style of the dropdown list, with kebab-case keys for an object, applied to the role="listbox" element. | JSX.CSSProperties | string | - |
| emptyContent | Content shown in the dropdown when data is empty. While loading, the spinner is shown instead. | JSX.Element | null |
| id | The id of the input, for <label for>; with triggerRender it lands on the root element. | string | - |
| insetLabel | Inset label of the input, rendered in the prefix position. It is not rendered, and does not name the component, when prefix or triggerRender is set. | JSX.Element | - |
| insetLabelId | The id of the insetLabel element, which names the combobox and the listbox. When omitted, the component generates one while an insetLabel is rendered. | string | - |
| loading | Whether the dropdown shows a loading spinner in place of the candidates and the empty content. | boolean | false |
| maxHeight | Maximum height of the dropdown list; a number is in px. | string | number | 300 |
| motion | Whether the dropdown animates when it shows and hides. | boolean | true |
| onBlur | Callback when the focus leaves the input area, with the FocusEvent. Moving the focus inside the area, for example to the clear button, does not fire it. | Type(e: FocusEvent) => void | - |
| onChange | Callback when the input text changes or a candidate is picked, with the current value. | Type(value: string | number) => void | - |
| onChangeWithObject | Has no effect; the component does not read it. | boolean | - |
| onClear | Callback after the clear button empties the input. | Type() => void | - |
| onDropdownVisibleChange | Callback when the dropdown shows or hides, with its visibility. | Type(visible: boolean) => void | - |
| onFocus | Callback when the focus enters the input area, with the FocusEvent. Moving the focus inside the area, for example to the clear button, does not fire it. | Type(e: FocusEvent) => void | - |
| onSearch | Callback as the user types, with the typed text. | Type(inputValue: string) => void | - |
| onSelect | Callback when a candidate is picked, with its value, or with the item without internal fields when onSelectWithObject is set. | Type(value: string | number | BaseDataItem) => void | - |
| onSelectWithObject | Boolean prop. When true, onSelect receives the data item without internal fields instead of its value. | boolean | false |
| placeholder | Placeholder of the input. | string | - |
| position | Position of the dropdown; the values are those of the Tooltip position. Unset, it is bottomLeft, or bottomRight when the ConfigProvider direction is rtl. | Position | - |
| prefix | Prefix content of the input. When it is set, insetLabel is not rendered. | JSX.Element | - |
| ref | The root div that wraps the input or the output of triggerRender. Its focus() and blur() act on the input. | TypeRef<HTMLDivElement> | - |
| renderItem | Renders a candidate row. The argument is the data item itself, and the return value is JSX. Without it the row shows label. | Type(option: T) => JSX.Element | - |
| renderSelectedItem | The text the input shows for a picked item; it must return a string. A string or number item receives { value, label }, an object item the item itself. | Type(option: T extends string | number ? BaseDataItem : T) => string | - |
| showClear | Whether to show the clear button when there is content. | boolean | false |
| size | Size of the input: small, default or large. | Size | "default" |
| stopPropagation | Whether click, mousedown and focus in/out events on the dropdown stop propagating. | TooltipProps['stopPropagation'] | true |
| suffix | Suffix content of the input. | JSX.Element | - |
| triggerRender | Custom trigger, which replaces the input. Its parameters include value (the array of selected items), inputValue, placeholder, disabled, autofocus, size, validateStatus, showClear, prefix, suffix, clearIcon, the aria-label, aria-labelledby, aria-describedby, aria-errormessage, aria-invalid and aria-required attributes (other aria-* attributes do not reach the custom trigger), onChange (updates the input text, calls onSearch and onChange and opens the list), onClear, onFocus, onBlur and componentProps. With it, id lands on the root element. | Type(props: AutoCompleteTriggerRenderProps<T>) => JSX.Element | - |
| validateStatus | Validation status: default, error or warning. It only affects the style. | ValidateStatus | "default" |
| value | Current value. Passing it makes the component controlled; undefined leaves it uncontrolled. | string | number | - |
class, style, ref, data-* and other undeclared attributes and events (for example onClick) land on the root div, whose tabindex is fixed to -1. id, name and aria-* land on the input (role="combobox"), so a <label for> finds it. With triggerRender there is no input: id lands on the root div, name lands nowhere, and aria-label, aria-labelledby, aria-describedby, aria-errormessage, aria-invalid and aria-required reach the focusable element of the custom trigger through its parameters; other aria-* attributes do not reach the trigger.
Object items of data:
| Field | Description | Type |
|---|---|---|
| value | The value after picking | string | number |
| label | The row text; must be provided, markup goes through renderItem | string | number |
| disabled | Whether the candidate is disabled | boolean |
| class | Class of the candidate row | string |
| style | Style of the candidate row | JSX.CSSProperties | string |
Other fields are kept as they are and show up in the renderItem argument; with onSelectWithObject set, the object that onSelect receives keeps them too. Fields starting with data- land on the candidate row. A string or number item reaches renderSelectedItem as { value, label }.
Accessibility
Keyboard and Focus
- AutoComplete's input box has
role="combobox"and is linked to the options panel througharia-expanded,aria-controls,aria-autocomplete="list"andaria-activedescendant - The combobox takes its name from, in order:
aria-labelledby,aria-label, the rendered insetLabel (the combobox'saria-labelledbypoints at it, with an id the component generates wheninsetLabelIdis not given; with a prefix the insetLabel is not rendered and does not name it). The listbox uses the same name source. AutoComplete has no default name: with none of these, name the input witharia-labeloraria-labelledby; a<label for>names only the input and leaves the listbox unnamed - AutoComplete's input box can be focused, and once focused, keyboard users can use
Up ArroworDown Arrowto open the options panel; when data is empty the panel shows emptyContent (nothing when it is not provided), and a loading spinner while loading - AutoComplete also supports opening and closing panels via
Enterkey - If the user sets the defaultActiveFirstOption property to true, the first option is highlighted by default when the options panel is opened
- If the drop-down menu is open:
- Use
Escto close the menu - Use
Tabto close the menu; the focus moves on to the next focusable element as usual - Use
Up ArroworDown Arrowto toggle options; the highlighted option (the onearia-activedescendantpoints at) hasaria-selected="true" - The focused option can be selected with the
Enterkey and the panel will be collapsed - After picking an option with the mouse, the focus stays in the input
- Use
- With
showClearand a value, the clear button is the same as Input's: a nativebuttonnamed "Clear input" from the locale; from the input,Tabmoves to it, andEnterorSpaceclears and puts the focus back in the input
Content Guidelines
- Content needs to be presented clearly so that users can clearly perceive the options available
- Limit the number of options displayed at one time
Design Token
| Sass variable | Value | Usage |
|---|---|---|
| $transition_duration-autoComplete_option-bg | var(--semi-transition_duration-none) | 选择器-选项-动画持续时间 |
| $transition_function-autoComplete_option-bg | var(--semi-transition_function-easeIn) | 选择器-选项-过渡曲线 |
| $transition_delay-autoComplete_option-bg | var(--semi-transition_delay-none) | 选择器-选项-延迟时间 |
| $spacing-autoComplete_loading_wrapper-paddingTop | $spacing-tight | 加载搜索结果时的顶部内边距 |
| $spacing-autoComplete_loading_wrapper-paddingBottom | $spacing-tight | 加载搜索结果时的底部内边距 |
| $color-autoComplete_option-icon-default | transparent | 自动完成菜单选项图标颜色 - 默认态 |
| $color-autoComplete_option-bg-hover | var(--semi-color-fill-0) | 自动完成菜单选项背景颜色 - 悬停态 |
| $color-autoComplete_option-bg-active | var(--semi-color-fill-1) | 自动完成菜单选项背景颜色 - 按下态 |
| $color-autoComplete_option_disabled-text | var(--semi-color-disabled-text) | 禁用自动完成菜单选项文字颜色 |
| $width-autoComplete_option_tick | $width-icon-small | 自动完成菜单项选中对勾图标大小 |
| $spacing-autoComplete_option_tick-marginRight | $spacing-tight | 自动完成菜单选中对勾右侧外边距 |
| $spacing-autoComplete_option-paddingLeft | $spacing-base-tight | 自动完成菜单项左侧内边距 |
| $spacing-autoComplete_option-paddingRight | $spacing-base-tight | 自动完成菜单项右侧内边距 |
| $spacing-autoComplete_option-paddingTop | $spacing-tight | 自动完成菜单项顶部内边距 |
| $spacing-autoComplete_option-paddingBottom | $spacing-tight | 自动完成菜单项底部内边距 |
| $spacing-autoComplete_option_first-marginTop | $spacing-extra-tight | 自动完成第一个菜单项顶部外边距 |
| $spacing-autoComplete_option_last-marginBottom | $spacing-extra-tight | 自动完成最后一个菜单项顶部外边距 |
| $radius-autoComplete_option | 0px | 自动完成待选项圆角 |
| $color-autoComplete_option_main-text | var(--semi-color-text-0) | 自动完成菜单选项文本颜色 |