多选
Mantine Core 的独立组件,请不要替换为 JGuang UI/Base UI 的同名 API。
安装
npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1
入口引入 @mantine/core/styles.css,并在根组件使用 MantineProvider;通过 forceColorScheme 同步深浅色主题。
使用与验证
在左侧选择组件,在预览中操作它的状态;使用“查看代码”检查真实源码。安装后执行类型检查和构建,再验证键盘焦点、手机布局与深浅色主题。
来源
https://github.com/mantinedev/mantine/tree/f0e2bc6d28b0f0b8aaefb5b859937bf77f539df7 MIT 许可证;不包括付费内容。下方提供上游英文参考内容,中文使用指南已适配;上游详细参考暂保留英文。
版本与兼容性
固定的 Mantine Core 9.7.1 要求 React 和 React DOM 满足 ^19.2.0,并配套 @mantine/hooks@9.7.1。此条件来自实际 npm 包的 peerDependencies。React 18 或其他不兼容版本不能强行安装;先向项目负责人说明升级影响并确认方案,不使用 --force/--legacy-peer-deps 掩盖冲突。入口的原生 Provider 开启 theme.respectReducedMotion,并用 forceColorScheme 控制深浅色。
适用场景
从建议选项中选择多个值。
本地示例使用的原生参数
以下参数由本地、经过类型检查的 JSX 示例提取,按所属组件区分。不是全部属性清单;完整签名和继承关系见下方固定源码参考。
| 组件参数 | 说明 |
|---|---|
MultiSelect.disabled | 禁用用户输入或操作,不等同于只读。 |
MultiSelect.error | 本地错误展示,本身不执行校验。 |
MultiSelect.readOnly | 阻止编辑并保留只读交互。 |
MultiSelect.label | 可见标签或当前组件的标签内容。 |
MultiSelect.data | 本地选项或节点,保持 value 唯一并遵循原生结构。 |
MultiSelect.defaultValue | 非受控初始值,不是持续更新的受控值。 |
交互与状态检查
当前场景:默认、禁用、校验错误、只读。切换支持的禁用、错误或只读场景后检查真实行为;无此原生状态的组件不会被强行添加统一属性。验证键盘焦点和退出方式,修改值后按原生回调签名连接业务状态。
业务接入边界
选中值是数组,应与搜索文本分开维护。示例使用本地选项,不提供远程搜索服务。
宿主配置
先安装固定版本 @mantine/core 和 @mantine/hooks,在入口引入 @mantine/core/styles.css,并使用 MantineProvider。示例的 theme 和 variant 是 JGuang 编写的宿主参数,不是所有原生组件都接受的统一 API;通过 forceColorScheme 同步应用主题。源码面板中的原生实现仅供阅读,使用 npm 导出,不要单独复制内部实现。此批只覆盖 Core,不包括日期、图表和表单扩展包。上游详细说明保留英文,不宣称全文中文化。
可运行用法
以下代码使用资源原生 API;不是把其他设计系统的同名组件替换进来。
import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';
const fruit = ["Apple", "Banana", "Orange"];
function ComponentPreview({ variant = 'Default' }: { variant?: string }) {
const disabled = variant === "Disabled";
const invalid = variant === "Invalid";
const readOnly = variant === "Read only";
return <M.MultiSelect disabled={disabled} error={invalid ? "Choose an option / 请选择" : undefined} readOnly={readOnly} label="Technologies" data={fruit} defaultValue={["Apple"]} />;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}
可用状态与示例
默认、禁用、校验错误、只读
示例遵循 Mantine 原生 API。支持多状态的输入组件可将示例中的 variant 改为 Disabled、Invalid 或 Read only;这些只是示例标签,不是新增的组件属性。改造时保留组件要求的数据、受控值、store 和复合子组件结构。
Usage
MultiSelect provides a way to enter multiple values.
MultiSelect is similar to TagsInput, but it does not allow entering custom values.
Loading state
Set loading prop to display a loading indicator. By default, the loader is displayed on the right side of the input.
You can change the position with the loadingPosition prop to 'left' or 'right'. This is useful for async operations like API calls, searches, or validations:
Controlled
The MultiSelect value must be an array of strings; other types are not supported.
The onChange function is called with an array of strings as a single argument.
import { useState } from 'react';
import { MultiSelect } from '@mantine/core';
function Demo() {
const [value, setValue] = useState<string[]>([]);
return <MultiSelect data={[]} value={value} onChange={setValue} />;
}
Clearable
Set the clearable prop to display the clear button in the right section. The button is not displayed
when:
- The component does not have a value
- The component is disabled
- The component is read only
Searchable
Set the searchable prop to allow filtering options by user input:
Controlled search value
You can control the search value with the searchValue and onSearchChange props:
import { useState } from 'react';
import { MultiSelect } from '@mantine/core';
function Demo() {
const [searchValue, setSearchValue] = useState('');
return (
<MultiSelect
searchable
searchValue={searchValue}
onSearchChange={setSearchValue}
data={[]}
/>
);
}
Nothing found
Set the nothingFoundMessage prop to display a given message when no options match the search query
or there is no data available. If the nothingFoundMessage prop is not set, the MultiSelect dropdown will be hidden.
Checked option icon
Set checkIconPosition prop to left or right to control position of check icon in active option.
To remove the check icon, set withCheckIcon={false}. To align unchecked labels with checked ones, set withAlignedLabels prop.
Max selected values
You can limit the number of selected values with the maxValues prop. This will not allow adding more values
once the limit is reached.
Hide selected options
To remove selected options from the list of available options, set the hidePickedOptions prop:
Value type
MultiSelect supports primitive values (strings, numbers, booleans) as value type. MultiSelect automatically
infers the value type. If you want to set the value type explicitly, pass type argument:
import { MultiSelect } from '@mantine/core';
type MultiSelectValue = 'React' | 'Angular' | 'Svelte' | number;
function Demo() {
return <MultiSelect<MultiSelectValue> data={['React', 'Angular', 'Svelte', 100]} />;
}
Sort options
By default, options are sorted by their position in the data array. You can change this behavior
with the filter function:
Fuzzy search with fuse.js
You can implement fuzzy search using the fuse.js library to match options even with typos or partial matches:
renderOption
The renderOption callback allows you to customize option rendering. It is called with the option object and
checked state. The function must return a React node.
renderPill
The renderPill callback allows you to customize pill rendering. The function receives the option (that was passed to data), value, onRemove and disabled props. It must return a React node.
Reorder pills
Set the withPillsReorder prop to allow reordering pills. Dropping a pill before or after
another pill updates the component value accordingly. Reordering is automatically disabled
when disabled or readOnly is set.
You can reorder pills with a mouse (drag-and-drop) or keyboard:
- Pills are not part of the
Taborder. With focus on the input, pressArrowLeft(when the caret is at the start of the input) to move focus to the last pill. ArrowLeftandArrowRightmove focus between pills (RTL-aware). PressingArrowRighton the last pill returns focus to the input.Alt + ArrowLeftandAlt + ArrowRightreorder the focused pill (RTL-aware).
Focus follows the moved pill so multiple moves can be chained without re-focusing.
If you use a custom pill renderer with the renderPill prop, spread the reorderProps from
the render callback payload onto the focusable pill root element to keep reordering working.
reorderProps carries the tabIndex, data-mantine-pill-index attribute and the keyboard
handler that drive keyboard reorder, so it must land on the element the user can focus:
import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
data={['React', 'Angular', 'Vue']}
withPillsReorder
renderPill={({ value, onRemove, reorderProps }) => (
<div {...reorderProps}>
{value} <button onClick={onRemove}>×</button>
</div>
)}
/>
);
}
Scrollable dropdown
By default, the options list is wrapped with ScrollArea.Autosize.
You can control the dropdown max-height with the maxDropdownHeight prop if you do not change the default settings.
If you want to use native scrollbars, set withScrollArea={false}. Note that in this case,
you will need to change the dropdown styles with Styles API.
Fit dropdown to viewport height
Set floatingHeight="viewport" to make the dropdown grow to fill the available vertical
space in the viewport. The flip middleware is disabled in this mode – the dropdown
always opens in the configured direction and is constrained to the viewport edges instead
of flipping to the other side. Useful when working with large option lists:
Group options
Disabled options
When an option is disabled, it cannot be selected and is ignored in keyboard navigation.
Note that the user can still enter a disabled option as a value. If you want to prohibit certain values,
use a controlled component and filter them out in the onChange function.
Inside Popover
To use MultiSelect inside popover, you need to set withinPortal: false:
Control dropdown opened state
You can control the dropdown opened state with the dropdownOpened prop. Additionally,
you can use onDropdownClose and onDropdownOpen to listen to dropdown opened state changes.
Dropdown position
By default, the dropdown is displayed below the input if there is enough space; otherwise it is displayed above the input.
You can change this behavior by setting the position and middlewares props, which are passed down to the
underlying Popover component.
Example of a dropdown that is always displayed above the input:
Dropdown width
To change the dropdown width, set the width prop in comboboxProps. By default,
the dropdown width is equal to the input width.
Dropdown offset
To change the dropdown offset, set the offset prop in comboboxProps:
Dropdown animation
By default, dropdown animations are disabled. To enable them, you can set transitionProps,
which will be passed down to the underlying Transition component.
Dropdown padding
Dropdown shadow
Input props
Read only
Set readOnly to make the input read-only. When readOnly is set,
MultiSelect will not show suggestions and will not call the onChange function.
Disabled
Set disabled to disable the input. When disabled is set,
the user cannot interact with the input and MultiSelect will not show suggestions.
Error state
Success state
To set aria-label on the clear button, use clearButtonProps. Note that it is required
only when clearable is set.
import { MultiSelect } from '@mantine/core';
function Demo() {
return (
<MultiSelect
data={[]}
clearable
clearButtonProps={{
'aria-label': 'Clear input',
}}
/>
);
}
Backspace key
When the search input is empty and the user presses the Backspace key, the last selected item is removed. This behavior is built-in and cannot be disabled.
源码参数参考
以下实现签名和类型从本地固定版本源码生成。保留可选属性、原生继承类型、联合分支和 never 限制;不会将其他设计系统的 API 混入。forwardRef 实现中的 ref 属于 React 引用,不是新增业务属性。此区域是原始类型参考,不是上游说明的全文翻译。
MultiSelect(_props)
export type MultiSelectStylesNames =
| __InputStylesNames
| ComboboxLikeStylesNames
| 'pill'
| 'pillsList'
| 'inputField';
export interface MultiSelectProps<Value extends Primitive = string>
extends
BoxProps,
__BaseInputProps,
ComboboxLikeProps<Value>,
StylesApiProps<MultiSelectFactory>,
ElementProps<'input', 'size' | 'value' | 'defaultValue' | 'onChange'> {
/** Controlled component value */
value?: Value[];
/** Uncontrolled component default value */
defaultValue?: Value[];
/** Called when value changes */
onChange?: (value: Value[]) => void;
/** Called with `value` of the removed item */
onRemove?: (value: Value) => void;
/** Called when the clear button is clicked */
onClear?: () => void;
/** Called when user attempts to select more values than allowed */
onMaxValues?: () => void;
/** Controlled search value */
searchValue?: string;
/** Default search value */
defaultSearchValue?: string;
/** Called when search changes */
onSearchChange?: (value: string) => void;
/** Maximum number of values, no limit if not set */
maxValues?: number;
/** Allows searching through options by user input @default false */
searchable?: boolean;
/** Message displayed when no options match the search query (when searchable is enabled) or when the data array is empty. If not set, the dropdown will be hidden instead. */
nothingFoundMessage?: React.ReactNode;
/** If set, the check icon is displayed near the selected option label @default true */
withCheckIcon?: boolean;
/** If set, unchecked labels are aligned with checked ones @default false */
withAlignedLabels?: boolean;
/** Position of the checkmark icon shown next to selected options in the dropdown @default 'left' */
checkIconPosition?: 'left' | 'right';
/** When enabled, selected options are hidden from the dropdown list @default false */
hidePickedOptions?: boolean;
/** When enabled, displays a clear button to remove all selected values (hidden when component is empty, disabled, or read-only) @default false */
clearable?: boolean;
/** Determines how the clear button and rightSection are rendered @default 'both' */
clearSectionMode?: ClearSectionMode;
/** Props passed down to the clear button */
clearButtonProps?: InputClearButtonProps;
/** Props passed down to the hidden input */
hiddenInputProps?: Omit<React.ComponentProps<'input'>, 'value'>;
/** Divider used to separate values in the hidden input `value` attribute @default ',' */
hiddenInputValuesDivider?: string;
/** A function to render content of the option, replaces the default content of the option */
renderOption?: (item: ComboboxLikeRenderOptionInput<ComboboxItem<Value>>) => React.ReactNode;
/** A function to render content of the pill */
renderPill?: (props: ComboboxRenderPillInput<Value>) => React.ReactNode;
/** Props passed down to the underlying `ScrollArea` component in the dropdown */
scrollAreaProps?: ScrollAreaProps;
/** Controls color of the default chevron */
chevronColor?: MantineColor;
/** Clear search value when item is selected @default true */
clearSearchOnChange?: boolean;
/** Controls whether dropdown opens when the input receives focus @default true */
openOnFocus?: boolean;
/** If set, selected values can be reordered by dragging pills. Disabled when `disabled` or `readOnly` is set. @default false */
withPillsReorder?: boolean;
}
export type MultiSelectFactory = Factory<{
props: MultiSelectProps;
ref: HTMLInputElement;
stylesNames: MultiSelectStylesNames;
signature: <Value extends Primitive = string>(
props: MultiSelectProps<Value>
) => React.JSX.Element;
}>;
export type Mod = Record<string, any> | string;
export type BoxMod = Mod | Mod[] | BoxMod[];
export interface BoxProps extends MantineStyleProps {
/** Class added to the root element, if applicable */
className?: string | undefined;
/** Inline style added to root component element, can subscribe to theme defined on MantineProvider */
style?: MantineStyleProp;
/** CSS variables defined on root component element */
__vars?: CssVarsProp;
/** `size` property passed down the HTML element */
__size?: string;
/** Breakpoint above which the component is hidden with `display: none` */
hiddenFrom?: MantineBreakpoint;
/** Breakpoint below which the component is hidden with `display: none` */
visibleFrom?: MantineBreakpoint;
/** Determines whether component should be hidden in light color scheme with `display: none` */
lightHidden?: boolean;
/** Determines whether component should be hidden in dark color scheme with `display: none` */
darkHidden?: boolean;
/** Element modifiers transformed into `data-` attributes, for example, `{ 'data-size': 'xl' }`, falsy values are removed */
mod?: BoxMod;
}
export type ElementProps<
ElementType extends React.ElementType,
PropsToOmit extends string = never,
> = Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>;
export interface CSSProperties extends React.CSSProperties {
[key: string]: any;
}
type MantineStyle = CSSProperties | ((theme: MantineTheme) => CSSProperties);
export type MantineStyleProp = MantineStyle | MantineStyle[] | MantineStyleProp[] | undefined;
export type CssVariable = `--${string}`;
export type CssVariables<Variable extends string = CssVariable> = Partial<Record<Variable, string>>;
export type CssVars<Variable extends string = CssVariable> =
| CssVariables<Variable>
| ((theme: MantineTheme) => CssVariables<Variable>)
| CssVars<Variable>[];
export type CssVarsProp<Variable extends string = CssVariable> =
| CssVars<Variable>
| CssVars<Variable>[];
type WrapperProps = React.ComponentProps<'div'> & DataAttributes;
export interface __BaseInputProps extends __InputWrapperProps, __InputProps {
/** Props passed down to the root element */
wrapperProps?: WrapperProps;
}
export type __InputStylesNames = InputStylesNames | InputWrapperStylesNames;
export type InputStylesNames = 'input' | 'wrapper' | 'section' | 'bottomSection';
export interface __InputProps {
/** Content section displayed on the left side of the input */
leftSection?: React.ReactNode;
/** Left section width, used to set `width` of the section and input `padding-left`, by default equals to the input height */
leftSectionWidth?: React.CSSProperties['width'];
/** Props passed down to the `leftSection` element */
leftSectionProps?: React.ComponentProps<'div'>;
/** Sets `pointer-events` styles on the `leftSection` element. Use `'all'` when section contains interactive elements (buttons, links). @default 'none' */
leftSectionPointerEvents?: React.CSSProperties['pointerEvents'];
/** Content section displayed on the right side of the input */
rightSection?: React.ReactNode;
/** Right section width, used to set `width` of the section and input `padding-right`, by default equals to the input height */
rightSectionWidth?: React.CSSProperties['width'];
/** Props passed down to the `rightSection` element */
rightSectionProps?: React.ComponentProps<'div'>;
/** Sets `pointer-events` styles on the `rightSection` element. Use `'all'` when section contains interactive elements (buttons, links). @default 'none' */
rightSectionPointerEvents?: React.CSSProperties['pointerEvents'];
/** Sets `required` attribute on the `input` element */
required?: boolean;
/** Key of `theme.radius` or any valid CSS value to set `border-radius`, numbers are converted to rem @default theme.defaultRadius */
radius?: MantineRadius;
/** Sets `disabled` attribute on the `input` element */
disabled?: boolean;
/** Controls input `height`, horizontal `padding`, and `font-size` @default 'sm' */
size?: MantineSize | (string & {});
/** Determines whether the input should have `cursor: pointer` style. Use when input acts as a button-like trigger (e.g., `component="button"` for Select/DatePicker). @default false */
pointer?: boolean;
/** Determines whether the input should have red border and red text color when the `error` prop is set @default true */
withErrorStyles?: boolean;
/** Determines whether the input should have green border when the `success` prop is set @default true */
withSuccessStyles?: boolean;
/** HTML `size` attribute for the input element (number of visible characters) */
inputSize?: string;
/** Section to be displayed when the input is `__clearable` and `rightSection` is not defined */
__clearSection?: React.ReactNode;
/** Determines whether the `__clearSection` should be displayed if it is passed to the component, has no effect if `rightSection` is defined */
__clearable?: boolean;
/** Determines how the clear button and rightSection are rendered @default 'both' */
__clearSectionMode?: ClearSectionMode;
/** Right section displayed when both `__clearSection` and `rightSection` are not defined */
__defaultRightSection?: React.ReactNode;
/** Displays loading indicator in the left or right section @default false */
loading?: boolean;
/** Position of the loading indicator @default 'right' */
loadingPosition?: 'left' | 'right';
/** Content section displayed at the bottom of the input, inside the border */
__bottomSection?: React.ReactNode;
/** Props passed down to the `__bottomSection` element */
__bottomSectionProps?: React.ComponentProps<'div'>;
}
export type InputWrapperStylesNames =
| 'root'
| InputLabelStylesNames
| InputDescriptionStylesNames
| InputErrorStylesNames
| InputSuccessStylesNames;
export interface __InputWrapperProps {
/** Contents of `Input.Label` component. If not set, label is not displayed. */
label?: React.ReactNode;
/** Contents of `Input.Description` component. If not set, description is not displayed. */
description?: React.ReactNode;
/** Contents of `Input.Error` component. If not set, error is not displayed. */
error?: React.ReactNode;
/** Contents of `Input.Success` component. If not set, success is not displayed. */
success?: React.ReactNode;
/** Adds required attribute to the input and a red asterisk on the right side of label @default false */
required?: boolean;
/** If set, the required asterisk is displayed next to the label. Overrides `required` prop. Does not add required attribute to the input. @default false */
withAsterisk?: boolean;
/** Props passed down to the `Input.Label` component */
labelProps?: InputLabelProps & DataAttributes & PlaceholderPolymorphicProps;
/** Props passed down to the `Input.Description` component */
descriptionProps?: InputDescriptionProps & DataAttributes & PlaceholderPolymorphicProps;
/** Props passed down to the `Input.Error` component */
errorProps?: InputErrorProps & DataAttributes & PlaceholderPolymorphicProps;
/** Props passed down to the `Input.Success` component */
successProps?: InputSuccessProps & DataAttributes & PlaceholderPolymorphicProps;
/** Render function to wrap the input element. Useful for adding tooltips, popovers, or other wrappers around the input. @default React.Fragment */
inputContainer?: (children: React.ReactNode) => React.ReactNode;
/** Controls order and visibility of wrapper elements. Only elements included in this array will be rendered. @default ['label', 'description', 'input', 'error'] */
inputWrapperOrder?: ('label' | 'input' | 'description' | 'error')[];
}
原生与外部类型
以下外部类型仍由 React 或资源自身依赖定义,没有手工展开为虚构属性。实际可用类型以安装版本的 TypeScript 检查为准。
Factory<{
props: MultiSelectProps;
ref: HTMLInputElement;
stylesNames: MultiSelectStylesNames;
signature: <Value extends Primitive = string>(
props: MultiSelectProps<Value>
) => React.JSX.Element;
}>
Primitive
MantineStyleProps
React.CSSProperties
MantineTheme
Partial<Record<Variable, string>>
Record<Variable, string>
Variable
MantineBreakpoint
Record<string, any>
React.ReactNode
InputLabelProps
DataAttributes
PlaceholderPolymorphicProps
InputDescriptionProps
InputErrorProps
InputSuccessProps
React.ComponentProps<'div'>
MantineRadius
MantineSize
ClearSectionMode
ComboboxLikeProps<Value>
Value
StylesApiProps<MultiSelectFactory>
React.ElementType
Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>
React.ComponentProps<ElementType>
ElementType
PropsToOmit
InputClearButtonProps
Omit<React.ComponentProps<'input'>, 'value'>
React.ComponentProps<'input'>
ComboboxLikeRenderOptionInput<ComboboxItem<Value>>
ComboboxItem<Value>
ComboboxRenderPillInput<Value>
ScrollAreaProps
MantineColor
HTMLInputElement
InputLabelStylesNames
InputDescriptionStylesNames
InputErrorStylesNames
InputSuccessStylesNames
ComboboxLikeStylesNames
React.JSX.Element