A reusable quick-filter flow for React Native & Expo — a horizontal chip / pill bar, chip dropdowns, and a slide-in filter panel with sort, faceted counts, a live result counter, i18n, and light / dark theming. Ships a headless core + a themed, ready-to-use UI. Distilled from the filter UX of Trendyol, Amazon, Flo and MediaMarkt.
| Chip bar + product list | Dropdown (deferred apply) | Master-detail panel (Amazon-style) | Sheet, dark (MediaMarkt-style) |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Every e-commerce / catalog / search screen re-implements the same filtering UX:
a scrollable chip bar, a way to pick values, an Apply step, a live result count,
and active filters reflected back on the bar. react-native-quick-filter
captures that as one domain-agnostic abstraction you can drop into any app.
- Headless core + themed UI — zero-config out of the box, every part overridable.
- Three detailed-filter presentations sharing one state: drilldown, master-detail (Amazon), and sheet (MediaMarkt).
- Instant (toggle) and deferred (Apply/CTA) apply modes side by side.
- Live result counter (
resolveResultCount, sync or async) + faceted per-option counts. - Strict TypeScript, ships CJS + ESM +
.d.ts. - i18n (English default, Turkish included) and light / dark theming.
- Reanimated slide + swipe-to-dismiss (gesture-handler), reduce-motion aware.
- Expo-friendly — JS-only, no native module; works in Expo Go and dev builds.
npm install react-native-quick-filter
# or: yarn add react-native-quick-filter / pnpm add react-native-quick-filterThen install the peer dependencies (all in the Expo SDK):
npx expo install react-native-reanimated react-native-gesture-handler react-native-safe-area-context| peer dependency | version |
|---|---|
react |
>=18 |
react-native |
>=0.74 |
react-native-reanimated |
>=3.6 (v3 & v4) |
react-native-gesture-handler |
>=2.14 |
react-native-safe-area-context |
>=4.8 |
Wrap your app root once, and make sure the Reanimated Babel plugin is enabled.
// App.tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>{/* your app */}</SafeAreaProvider>
</GestureHandlerRootView>
);
}// babel.config.js (bare RN). The Reanimated plugin MUST be last.
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: ['react-native-reanimated/plugin'], // v4: 'react-native-worklets/plugin'
};With Expo, babel-preset-expo adds the Reanimated/worklets plugin automatically.
import { useState } from 'react';
import {
FilterProvider, QuickFilterBar, FilterDropdown, FilterSidePanel,
type Facet, type ChipSpec, type Selection,
} from 'react-native-quick-filter';
const facets: Facet[] = [
{ id: 'sort', label: 'Sort', kind: 'single', options: [
{ id: 'recommended', label: 'Recommended' },
{ id: 'priceAsc', label: 'Price: Low to High' },
] },
{ id: 'category', label: 'Category', kind: 'multi', searchable: true, selectAll: true,
options: [{ id: 'shoes', label: 'Shoes' }, { id: 'bags', label: 'Bags' }] },
{ id: 'brand', label: 'Brand', kind: 'multi', searchable: true, columns: 2,
options: [{ id: 'nike', label: 'Nike' }, { id: 'adidas', label: 'Adidas' }] },
{ id: 'price', label: 'Price', kind: 'range', min: 0, max: 5000,
presets: [{ id: 'p1', label: '$0 – $300', min: 0, max: 300 }] },
{ id: 'express', label: 'Express Delivery', kind: 'toggle',
options: [{ id: 'on', label: 'Express Delivery' }] },
];
const chips: ChipSpec[] = [
{ facetId: 'filter', kind: 'action', label: 'Filter', opens: 'sidePanel' },
{ facetId: 'sort', kind: 'dropdown', label: 'Sort' },
{ facetId: 'category', kind: 'dropdown' },
{ facetId: 'brand', kind: 'dropdown' },
{ facetId: 'price', kind: 'dropdown' },
{ facetId: 'express', kind: 'toggle', removable: true },
];
function Screen() {
const [selection, setSelection] = useState<Selection>();
return (
<FilterProvider
facets={facets}
chips={chips}
onSelectionChange={setSelection}
resolveResultCount={(sel) => fetchCount(sel)} // sync number or Promise<number>
>
<QuickFilterBar />
{/* your product list, filtered by `selection` */}
<FilterDropdown />
<FilterSidePanel variant="masterDetail" side="left" />
</FilterProvider>
);
}All three share the same state — whichever you render is what opens when the Filter chip is triggered.
// 1) drilldown — single panel, group list → tap → detail (with back). (Trendyol)
<FilterSidePanel variant="drilldown" side="right" />
// 2) masterDetail — left rail + right content, both visible at once. (Amazon)
<FilterSidePanel variant="masterDetail" side="left" animated={false} />
// 3) sheet — all facets as collapsible sections, pops over the page. (MediaMarkt)
// presentation="fullScreen" for full screen; long sections scroll independently.
<FilterSheet presentation="sheet" sectionMaxHeight={280} />- Facet — a filter dimension (
single | multi | range | toggle), independent of presentation. - ChipSpec — a chip in the bar (
action | dropdown | toggle | segment). - Selection — the single source of truth:
{ options: Record<id, string[]>, ranges: Record<id, { min?, max? }> }. - applyMode —
instant(applies immediately) ordeferred(draft → commit via Apply/CTA), per facet or provider-wide.
import { ThemeProvider, StringsProvider } from 'react-native-quick-filter';
<ThemeProvider scheme="dark" override={{ colors: { accent: '#F27A1A' } }}>
<StringsProvider strings={{ apply: 'Apply', resultCount: (n) => `${n} items` }}>
{/* ... */}
</StringsProvider>
</ThemeProvider>Without providers, the light theme + English dictionary defaults apply (zero-config).
A Turkish dictionary ships built-in (import { tr } from 'react-native-quick-filter').
Pass a selection prop for controlled mode; omit it and use defaultSelection
for uncontrolled. onSelectionChange(next) fires on every commit.
Build your own UI with only hooks — no UI components:
const { selection, activeCount, clearAll } = useFilter();
const brand = useFacet('brand'); // toggleOption, visibleOptions, commit, searchQuery, ...| Category | Exports |
|---|---|
| Provider | FilterProvider |
| Hooks | useFilter, useFacet, useResultCount, useDropdown, usePanel, useChip |
| Bar / Chips | QuickFilterBar, QuickFilterChip, Chip |
| Dropdown | FilterDropdown |
| Detailed filter | FilterSidePanel (variant: drilldown | masterDetail), FilterSheet (presentation: sheet | fullScreen), SidePanelMasterList, SidePanelRail |
| Bodies | FacetBody, CheckboxListBody, RadioListBody, RangeBody |
| Atoms | ResultCountButton, FilterFooter, Button, Checkbox, Radio, Badge, SearchInput, Chevron |
| Theme | ThemeProvider, useTheme, defaultLightTheme, defaultDarkTheme, mergeTheme |
| i18n | StringsProvider, useStrings, tr, en, formatNumber |
| Types | Facet, FacetOption, ChipSpec, Selection, RangeValue, QuickFilterTheme, QuickFilterStrings, … |
Full reference: docs/API.md · usage guide: docs/USAGE.md · consuming in another project: docs/CONSUMING.md.
For LLM tools (Claude Code, Cursor, GPT, etc.), a condensed machine-readable surface lives in llms.txt.
| React Native | >=0.74 (incl. New Architecture — JS-only, no native module) |
| Expo | SDK 50+ · Expo Go & dev builds |
| Reanimated | v3 (>=3.6) and v4 |
| Platforms | iOS · Android · (web via react-native-web, untested) |
| Types | Bundled .d.ts (strict) |
npm run typecheck # tsc --noEmit (strict)
npm test # jest + @testing-library/react-native (36 tests)
npm run build # react-native-builder-bob → lib/{module,commonjs,typescript}Three demos under example/: Store (light, master-detail panel),
Electronics (dark, sheet, faceted counts), Headless (hooks only).
cd example
./scripts/sync-lib.sh # packs & installs the library (re-run after source changes)
npx expo run:ios --device "iPhone 17 Pro Max" # or: npx expo startThe example consumes the library as an
npm packtarball because it lives inside the package, which breaks Metro's symlink/out-of-tree resolution. A separate consumer app does not hit this — a normal install just works.
React Native filter, Expo filter, quick filter, filter bar, filter chips, filter pills, faceted search, facets, sort menu, bottom-sheet filter, side panel / drawer filter, filter dropdown, e-commerce product filter, Trendyol/Amazon/MediaMarkt-style filter UI, headless filter, Reanimated filter.
Same headless model, native UI on each platform:
quick-filter-swift— iOS (UIKit + SwiftUI)quick-filter-kotlin— Android (Views + Jetpack Compose)
If this library saves you time, you can support its development:
Thank you! 🙏
Issues and PRs welcome. Run npm run typecheck && npm test before opening a PR.
See CHANGELOG.md for release history.
Design inspiration & analysis: references/00-GENEL-ANALIZ.md
and per-app references/*/ANALYSIS.md.
MIT © enes ozturk



