react-dropdown-select、react-select、react-selectize は、React アプリケーションでドロップダウンメニューや選択機能を実装するためのライブラリです。これらは、ネイティブの <select> タグでは実現困難なカスタマイズ性の高い UI、検索機能、マルチ選択、およびアクセシビリティ機能を提供します。react-select は業界標準として広く採用されており、高い拡張性を持ちます。react-dropdown-select はより軽量でシンプルな実装を重視します。react-selectize は古くから存在するライブラリですが、メンテナンス状況に注意が必要です。
フロントエンド開発において、ドロップダウン選択機能は単なる UI パーツではなく、ユーザーエクスペリエンスとデータ入力の質を左右する重要な要素です。react-dropdown-select、react-select、react-selectize は、それぞれ異なる哲学に基づいて設計されています。本稿では、実務的な観点からこれらを比較し、アーキテクチャ選定の指針を示します。
コンポーネントの内部構造をどこまで制御できるかは、プロジェクトの規模に直結します。
react-select は、コンポーネントの各部分(コントロール、メニュー、オプションなど)を完全に置き換えることができる「コンポーネント注入」アプローチを採用しています。
// react-select: カスタムコンポーネントの注入
import Select from 'react-select';
const CustomOption = (props) => (
<div {...props.innerProps} style={{ background: 'pink' }}>
{props.children}
</div>
);
<Select components={{ Option: CustomOption }} options={options} />
react-dropdown-select は、よりシンプルなプロップベースのカスタマイズを提供します。内部構造を完全に置き換えるというよりは、既存の構造に対してコンテンツを注入する形が一般的です。
// react-dropdown-select: カスタムレンダリング
import Select from 'react-dropdown-select';
<Select
options={options}
renderItem={({ item, props }) => (
<div {...props} style={{ color: 'blue' }}>
{item.label}
</div>
)}
/>
react-selectize は、古典的なアプローチを取り、プレースホルダーやオプションのレンダリングを関数プロップで制御します。ただし、構造自体の変更は他 2 つに比べて制限されることが多いです。
// react-selectize: レンダリングプロップの使用
import { Selectize } from 'react-selectize';
<Selectize
options={options}
optionComponent={(option) => <div>{option.label}</div>}
/>
現代の Web 開発において、アクセシビリティ(a11y)は必須要件です。ライブラリが ARIA 属性を適切に管理しているかが重要です。
react-select は、WAI-ARIA 仕様への準拠に非常に積極的です。スクリーンリーダーへの対応や、キーボードによるナビゲーション(矢印キー、Enter、Escape)が最初から強化されています。
// react-select: 組み込みのアクセシビリティ機能
// 追加設定なしで ARIA 属性が自動的に付与される
<Select
options={options}
aria-label="ユーザー選択"
isClearable
/>
react-dropdown-select も基本的なキーボード操作をサポートしていますが、複雑なユースケースでは追加の設定や確認が必要になる場合があります。標準的な実装であれば問題なく動作します。
// react-dropdown-select: 基本的なアクセシビリティ設定
<Select
options={options}
accessibilityLabel="ユーザー選択"
tabIndex={0}
/>
react-selectize は、作成された時期が古く、最新の ARIA 基準に完全には準拠していない可能性があります。キーボード操作の実装はありますが、現代的なスクリーンリーダーとの相性は検証が必要です。
// react-selectize: 手動での属性管理が必要な場合あり
<Selectize
options={options}
// ARIA 属性を明示的に渡す必要があるかもしれない
aria-label="ユーザー選択"
/>
React の状態管理(Controlled vs Uncontrolled)との統合方法も選定基準になります。
react-select は、完全に制御された(Controlled)コンポーネントとして設計されています。value と onChange を明示的に管理することで、親コンポーネントとのデータフローが明確になります。
// react-select: 制御されたコンポーネント
const [value, setValue] = useState(null);
<Select
value={value}
onChange={(newValue) => setValue(newValue)}
options={options}
/>
react-dropdown-select も同様に制御されたコンポーネントとして動作しますが、内部状態をある程度保持するハイブリッドな挙動を示すこともあり、プロップの渡し方に注意が必要です。
// react-dropdown-select: 値の管理
const [values, setValues] = useState([]);
<Select
values={values}
onChange={(items) => setValues(items)}
options={options}
/>
react-selectize は、従来のパターンに従っており、状態管理は可能ですが、React の最新のフックパターンとの相性についてはドキュメントを慎重に確認する必要があります。
// react-selectize: 状態のバインディング
const [value, setValue] = useState(null);
<Selectize
value={value}
onValueChange={(val) => setValue(val)}
options={options}
/>
ライブラリの選定において、メンテナンス状況はバグ修正やセキュリティパッチの適用に直結するため、最も重要な要素の一つです。
react-select は、Jed Watson によって作成され、活発なコミュニティによって維持されています。定期的なリリースがあり、React の新しいバージョン(例えば React 18)への対応も迅速です。長期的な視点で見た場合、最もリスクが低いです。
react-dropdown-select は、特定の開発者によってメンテナンスされており、安定した動作を提供しています。react-select ほど頻繁な更新ではありませんが、主要な機能は安定しており、シンプルな要件であれば十分な信頼性があります。
react-selectize は、最後に更新されてから長い時間が経過している可能性が高く、事実上メンテナンスが停止している(Abandonware)リスクがあります。npm のページや GitHub リポジトリでアーカイブ扱いになっていないか確認が必要です。新規プロジェクトで採用することは、技術的負債を抱えることを意味します。
開発チームの生産性に影響する要素です。
| 特徴 | react-select | react-dropdown-select | react-selectize |
|---|---|---|---|
| 設定の柔軟性 | 非常に高い(コンポーネント置換) | 中程度(レンダリング関数) | 低〜中程度 |
| スタイル調整 | CSS-in-JS 構造(オーバーライド必要) | クラス名ベース(比較的容易) | 古典的 CSS |
| ドキュメント | 充実している | 必要十分 | 限定的 |
| ** TypeScript 対応** | 公式で強力なサポート | サポートあり | 型定義が古いか不明 |
react-select を選ぶべき時:
プロジェクトが長期にわたること、アクセシビリティが重要であること、複雑なカスタマイズ(例:オプション内にコンポーネントを埋め込む、非同期検索など)が必要な場合は、迷わず react-select を選定すべきです。初期の学習コストは高いですが、将来的な拡張性を考慮すると最も費用対効果が高くなります。
react-dropdown-select を選ぶべき時:
「とにかく早く実装したい」「react-select だとスタイルの書き換えが面倒すぎる」という場合、react-dropdown-select は優れた代替案です。特に、デザインシステムが既に確立されており、それを簡単に適用したい場合に有効です。
react-selectize についての結論:
残念ながら、現代のフロントエンド開発において react-selectize を新規採用する理由はほとんど見当たりません。メンテナンスのリスク、アクセシビリティの懸念、そしてより優れた代替品の存在を考慮すると、既存システムの保守以外では避けるべきライブラリです。
最終的には、**「将来の保守性」と「ユーザーへのアクセシビリティ」**を最優先し、react-select を基準として検討し、特定の軽量要件がある場合にのみ react-dropdown-select を評価するのが、プロフェッショナルなアーキテクチャ判断と言えます。
シンプルで軽量な実装を優先し、react-select の複雑なスタイルオーバーライドに悩まされたくない場合に選択します。小規模なプロジェクトや、カスタマイズ要件が少なく、素早く実装したいシーンに適しています。
大規模なエンタープライズアプリケーションや、複雑な機能(非同期ローディング、グループ化、高度なカスタマイズ)が必要な場合に選択します。コミュニティが活発で、長期的なメンテナンスが保証されているため、新規プロジェクトのデファクトスタンダードとして推奨されます。
既存のレガシーシステムの保守以外では、新規プロジェクトでの使用は推奨しません。メンテナンスが停止している可能性が高く、最新の React 機能やアクセシビリティ基準に対応していないリスクがあります。代替として react-select の検討を強く推奨します。
Customisable dropdown select for react
propsdocument.body
npm install --save react-dropdown-select
react-select is very nice, but sometimes project requirements are beyond it's abilities
import:
import Select from "react-dropdown-select";
and use as:
const options = [
{
value: 1,
label: 'Leanne Graham'
},
{
value: 2,
label: 'Ervin Howell'
}
];
<Select options={options} onChange={(values) => this.setValues(values)} />;
If your options don't have value and label fields, include labelField and valueField in the props:
const options = [
{
id: 1,
name: 'Leanne Graham'
},
{
id: 2,
name: 'Ervin Howell'
}
];
<Select
options={options}
labelField="name"
valueField="id"
onChange={(values) => this.setValues(values)}
/>;
options and onChange are the minimum required props
| Prop | Type | Default | Description |
|---|---|---|---|
| values | array | [] | Selected values |
| options | array | [] | Available options, (option with key disabled: true will be disabled) |
| keepOpen | bool | false | If true, dropdown will always stay open (good for debugging) |
| defaultMenuIsOpen | bool | false | If true, dropdown will be open by default |
| autoFocus | bool | false | If true, and searchable, dropdown will auto focus |
| clearOnBlur | bool | true | If true, and searchable, search value will be cleared on blur |
| clearOnSelect | bool | true | If true, and searchable, search value will be cleared upon value select/de-select |
| name | string | null | If set, input type hidden would be added in the component with the value of the name prop as name and select's values as value |
| required | bool | false | If set, input type hidden would be added in the component with required prop as true/false |
| pattern | string | null | If set, input type hidden would be added in the component with pattern prop as regex |
| dropdownGap | number | 5 | Gap between select element and dropdown |
| multi | bool | false | If true - will act as multi-select, if false - only one option will be selected at the time |
| placeholder | string | "Select..." | Placeholder shown where there are no selected values |
| addPlaceholder | string | "" | Secondary placeholder on search field if any value selected |
| disabled | bool | false | Disable select and all interactions |
| style | object | {} | Style object to pass to select |
| className | string | CSS class attribute to pass to select | |
| loading | bool | false | Loading indicator |
| clearable | bool | false | Clear all indicator |
| searchable | bool | true | If true, select will have search input text |
| separator | bool | false | Separator line between close all and dropdown handle |
| dropdownHandle | bool | true | Dropdown handle to open/close dropdown |
| dropdownHeight | string | "300px" | Minimum height of a dropdown |
| direction | string | "ltr" | direction of a dropdown "ltr", "rtl" or "auto" |
| searchBy | string | label | Search by object property in values |
| sortBy | string | null | Sort by object property in values |
| labelField | string | "label" | Field in data to use for label |
| valueField | string | "value" | Field in data to use for value |
| color | string | "#0074D9" | Base color to use in component, also can be overwritten via CSS |
| closeOnScroll | bool | false | If true, scrolling the page will close the dropdown |
| closeOnSelect | bool | false | If true, selecting option will close the dropdown |
| closeOnClickInput | bool | false | If true, clicking input will close the dropdown if you are not searching. |
| dropdownPosition | string | "bottom" | Available options are "auto", "top" and "bottom" defaults to "bottom". Auto will adjust itself according Select's position on the page |
| keepSelectedInList | bool | true | If false, selected item will not appear in a list |
| portal | DOM element | false | If valid dom element specified - dropdown will break out to render inside the specified element |
| create | bool | false | If true, select will create value from search string and fire onCreateNew callback prop |
| backspaceDelete | bool | true | If true, backspace key will delete last value |
| createNewLabel | string | "add {search}" | If create set to true, this will be the label of the "add new" component. {search} will be replaced by search value |
| disabledLabel | string | "disabled" | Label shown on disabled field (after) the text |
| selectAll | bool | false | Allow to select all |
| selectAllLabel | string | "Select all" | Label for "Select all" |
| clearAllLabel | string | "Clear all" | Label for "Clear all" |
| additionalProps | object | null | Additional props to pass to Select |
by using renderer props to override components some of the functionality will have to be handled manually with a help of internal props, states and methods exposed
| Prop | Type | Default | Description |
|---|---|---|---|
| onChange | func | On values change (user and internally triggered) callback, returns array of values objects | |
| onSelect | func | On values change (user triggered) callback, returns array of values objects | |
| onDeselect | func | On values change (user triggered) callback, returns array of values objects | |
| onDropdownClose | func | Fires upon dropdown close | |
| onDropdownOpen | func | Fires upon dropdown open | |
| onCreateNew | func | Fires upon creation of new item if create prop set to true | |
| onClearAll | func | Fires upon clearing all values (via custom renderers) | |
| onSelectAll | func | Fires upon selecting all values (via custom renderers) | |
| onDropdownCloseRequest | func | undefined | Fires upon dropdown closing state, stops the closing and provides own method close() |
| contentRenderer | func | Overrides internal content component (the contents of the select component) | |
| itemRenderer | func | Overrides internal item in a dropdown | |
| noDataRenderer | func | Overrides internal "no data" (shown where search has no results) | |
| optionRenderer | func | Overrides internal option (the pillow with an "x") on the select content | |
| inputRenderer | func | Overrides internal input text | |
| loadingRenderer | func | Overrides internal loading | |
| clearRenderer | func | Overrides internal clear button | |
| separatorRenderer | func | Overrides internal separator | |
| dropdownRenderer | func | Overrides internal dropdown component | |
| dropdownHandleRenderer | func | Overrides internal dropdown handle | |
| searchFn | func | undefined | Overrides internal search function |
| handleKeyDownFn | func | undefined | Overrides internal keyDown function |