@bsky.app/sift
v0.3.4
Published
A little React Native library for building autocompletes.
Readme
@bsky.app/sift
A headless autocomplete UI library for React Native (including web). Handles popover positioning, keyboard navigation, and accessibility.
Installation
pnpm add @bsky.app/siftPeer dependencies: react, react-native, expo.
Basic example
import {useState} from 'react'
import {View, Text, TextInput} from 'react-native'
import {useSift, Sift, SiftItem} from '@bsky.app/sift'
const items = [
{key: '1', label: 'Alice'},
{key: '2', label: 'Bob'},
{key: '3', label: 'Charlie'},
]
function Autocomplete() {
const [query, setQuery] = useState('')
const sift = useSift({offset: 4})
const filtered = items.filter(item =>
item.label.toLowerCase().includes(query.toLowerCase()),
)
return (
<View>
<TextInput
{...sift.targetProps}
value={query}
onChangeText={setQuery}
placeholder="Search..."
/>
{query.length > 0 && (
<Sift
sift={sift}
data={filtered}
onSelect={item => setQuery(item.label)}
onDismiss={() => setQuery('')}
render={({active, props, item}) => (
<SiftItem {...props} style={active && {backgroundColor: '#eee'}}>
<Text>{item.label}</Text>
</SiftItem>
)}
/>
)}
</View>
)
}Items must have a key: string property, used internally for list rendering.
useSift options
offset
Gap in pixels between the anchor element and the popover. Defaults to 0.
const sift = useSift({offset: 8})placement
Controls where the popover appears relative to the anchor. Accepts 'top',
'top-start', 'top-end', 'bottom', 'bottom-start', or 'bottom-end'.
Defaults to 'bottom'.
const sift = useSift({placement: 'top'})dynamicWidth
When true (default), the popover width follows the anchor. When false, the
popover snaps to the input's left edge and is constrained to the input's width
via maxWidth.
const sift = useSift({dynamicWidth: false})useSift return value
| Property | Description |
| ------------------ | ------------------------------------------------------------ |
| targetProps | Spread onto the input (ref, ARIA attributes) |
| refs.setAnchor | Ref callback for the anchor element (optional) |
| refs.setPopover | Ref callback for the popover (set automatically by <Sift>) |
| popoverStyles | Computed position styles applied to the popover |
| updatePosition | Re-measure and update popover position |
| elements.input | The input element (for keyboard handling) |
| elements.popover | The popover element |
| isActive() | Returns true when the popover is mounted |
If refs.setAnchor is not called, the input element is used as the anchor.
<Sift> props
render
Render function for each item. Receives active (keyboard-highlighted),
props (accessibility attributes + onPress), and item.
Use <SiftItem> to wrap your item content — it's a Pressable that applies
the accessibility props correctly.
<Sift
render={({active, props, item}) => (
<SiftItem {...props} style={active && {backgroundColor: '#eee'}}>
<Text>{item.label}</Text>
</SiftItem>
)}
/>onSelect
Called when the user selects an item, either by pressing it or by pressing Enter/Tab while it's highlighted via keyboard.
onDismiss
Called when the user presses the Escape key (web only).
inverted
Renders the list bottom-to-top and reverses keyboard navigation to match. Useful when the popover opens above the input.
style
Style applied to the popover wrapper View.
<SiftItem>
A Pressable wrapper for autocomplete items. Accepts all Pressable props
plus a style that can be a function receiving {pressed, hovered} state.
<SiftItem
{...props}
style={s => [
{padding: 8},
(active || s.hovered) && {backgroundColor: '#eee'},
]}>
<Text>{item.label}</Text>
</SiftItem>Keyboard navigation (web)
| Key | Action |
| ------------------- | --------------------------------------------- |
| ArrowDown / ArrowUp | Move through items (reversed when inverted) |
| Home / End | Jump to first / last item |
| Enter / Tab | Select the active item |
| Escape | Calls onDismiss |
