Files
2nd/10_Wiki/Topics/Architecture/Downshift.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

222 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 `<select>` 의 styling 의 unable.
- 매 custom dropdown 의 매 keyboard / screen reader / focus management 의 hard.
- 매 ARIA roles (`combobox`, `listbox`, `option`) 의 correctly wire.
### 매 API 종류
- `useCombobox` — 매 typeahead 의 search input + dropdown.
- `useSelect` — 매 native select replacement (no input).
- `useMultipleSelection` — 매 tag / chip selection.
- Render-prop `<Downshift>` — 매 legacy, 매 hooks 권장.
### 매 핵심 state
- `isOpen` — dropdown open?
- `highlightedIndex` — 매 keyboard hover.
- `selectedItem` — 매 chosen value.
- `inputValue` — 매 typed text.
### 매 응용
1. Autocomplete / type-ahead search.
2. Country / timezone picker.
3. Multi-select tag input (with `useMultipleSelection`).
4. Async-loading dropdowns.
## 💻 패턴
### Pattern 1: Basic useCombobox
```tsx
import { useCombobox } from "downshift";
import { useState } from "react";
const ALL = ["apple","banana","cherry","date","elderberry"];
export function FruitCombo() {
const [items, setItems] = useState(ALL);
const cb = useCombobox({
items,
onInputValueChange: ({ inputValue }) => {
setItems(ALL.filter(f => f.includes(inputValue?.toLowerCase() ?? "")));
},
});
return (
<div>
<label {...cb.getLabelProps()}>Fruit</label>
<input {...cb.getInputProps()} />
<ul {...cb.getMenuProps()}>
{cb.isOpen && items.map((item, i) => (
<li
key={item}
{...cb.getItemProps({ item, index: i })}
style={{ background: cb.highlightedIndex === i ? "#def" : "white" }}
>{item}</li>
))}
</ul>
</div>
);
}
```
### 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<User[]>([]);
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<string>({});
const remaining = all.filter(t => !ms.selectedItems.includes(t));
const cb = useCombobox({
items: remaining,
onSelectedItemChange: ({ selectedItem }) => {
if (selectedItem) ms.addSelectedItem(selectedItem);
},
selectedItem: null,
});
return (
<div>
{ms.selectedItems.map((t, i) => (
<span key={t} {...ms.getSelectedItemProps({ selectedItem: t, index: i })}>
{t} <button onClick={() => ms.removeSelectedItem(t)}>×</button>
</span>
))}
<input {...cb.getInputProps(ms.getDropdownProps())} />
{/* menu... */}
</div>
);
}
```
### Pattern 4: useSelect (no typing)
```tsx
import { useSelect } from "downshift";
function SizeSelect() {
const items = ["S","M","L","XL"];
const sel = useSelect({ items });
return (
<>
<button {...sel.getToggleButtonProps()}>
{sel.selectedItem ?? "Pick size"}
</button>
<ul {...sel.getMenuProps()}>
{sel.isOpen && items.map((s, i) => (
<li key={s} {...sel.getItemProps({ item: s, index: i })}>{s}</li>
))}
</ul>
</>
);
}
```
### 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 |