--- id: wiki-2026-0508-downshift title: Downshift category: 10_Wiki/Topics status: verified canonical_id: self aliases: [downshift-js, useCombobox, useSelect] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [react, ui, accessibility, combobox, hooks] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: react --- # Downshift ## 매 한 줄 > **"매 headless UI 의 accessibility-first combobox primitive"**. Kent C. Dodds 의 2017 release, 매 render-prop / hook API 의 ARIA 1.2 combobox spec 의 implement. 매 2026 의 매 alternative 의 많음 (Radix, Headless UI, Ariakit, React Aria) 이지만 매 downshift 의 매 mature / battle-tested / tiny (~5KB). ## 매 핵심 ### 매 무엇 의 solve - 매 native ` ); } ``` ### Pattern 2: Async items (debounced) ```tsx import { useCombobox } from "downshift"; import { useState, useEffect } from "react"; import { useDebouncedValue } from "./hooks"; export function UserPicker() { const [query, setQuery] = useState(""); const debounced = useDebouncedValue(query, 250); const [items, setItems] = useState([]); useEffect(() => { if (!debounced) return; fetch(`/api/users?q=${debounced}`).then(r => r.json()).then(setItems); }, [debounced]); const cb = useCombobox({ items, itemToString: u => u?.name ?? "", onInputValueChange: ({ inputValue }) => setQuery(inputValue ?? ""), }); // 매 render UI 동일 패턴. } ``` ### Pattern 3: Multi-select with chips ```tsx import { useCombobox, useMultipleSelection } from "downshift"; function TagInput({ all }: { all: string[] }) { const ms = useMultipleSelection({}); const remaining = all.filter(t => !ms.selectedItems.includes(t)); const cb = useCombobox({ items: remaining, onSelectedItemChange: ({ selectedItem }) => { if (selectedItem) ms.addSelectedItem(selectedItem); }, selectedItem: null, }); return (
{ms.selectedItems.map((t, i) => ( {t} ))} {/* menu... */}
); } ``` ### Pattern 4: useSelect (no typing) ```tsx import { useSelect } from "downshift"; function SizeSelect() { const items = ["S","M","L","XL"]; const sel = useSelect({ items }); return ( <> ); } ``` ### Pattern 5: Controlled state (Redux / Zustand) ```tsx const cb = useCombobox({ items, selectedItem: store.selected, onSelectedItemChange: ({ selectedItem }) => store.set(selectedItem), inputValue: store.input, onInputValueChange: ({ inputValue }) => store.setInput(inputValue ?? ""), }); ``` ### Pattern 6: stateReducer for custom keyboard ```tsx import { useCombobox } from "downshift"; const stateReducer = (state, { type, changes }) => { switch (type) { case useCombobox.stateChangeTypes.InputKeyDownEnter: case useCombobox.stateChangeTypes.ItemClick: // 매 Enter 시 dropdown 의 close 하지 X return { ...changes, isOpen: state.isOpen, highlightedIndex: state.highlightedIndex }; default: return changes; } }; ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | React + ARIA combobox | downshift | | Full design system (Modal, Tabs, etc.) | Radix UI / Ariakit | | Adobe quality + i18n | React Aria | | Tailwind + simple primitives | Headless UI | | Vue / Svelte | downshift X — find native | **기본값**: 매 React combobox 만 필요 시 downshift, 매 design system 전체 시 Radix. ## 🔗 Graph - 부모: [[React]] · [[Headless UI]] · [[Accessibility (A11y)|Accessibility]] - 변형: [[Radix UI]] · [[Ariakit]] - 응용: [[Autocomplete]] - Adjacent: [[Render_Props|Render Props]] ## 🤖 LLM 활용 **언제**: 매 React typeahead / select 의 build, 매 a11y 의 mandatory. **언제 X**: 매 native select 의 충분, 매 React 외 framework. ## ❌ 안티패턴 - **Custom keyboard 의 reinvent**: 매 stateReducer 로 override 의 의도된 path. - **State sync mistakes**: 매 controlled / uncontrolled 의 mix — 매 하나의 choose. - **getXxxProps 의 spread 의 omit**: 매 ARIA attrs 의 lost — 매 a11y 의 break. ## 🧪 검증 / 중복 - Verified (downshift v9 docs, ARIA 1.2 combobox pattern, GitHub 11k+ stars). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — useCombobox + useSelect + multi-select |