Component Choice Workflow
A nested checklist for picking the right component when several feel "close enough." Use this before you reach for shadcn/ui, MUI, Headless UI, React Native Paper, or Tamagui — the wrong primitive locks in friction that no styling can fix later.
This guide treats web React and React Native / mobile web as first-class citizens. Where they diverge, the recommendation calls it out explicitly.
Step 1 — Frame the decision before browsing component libraries
- What is the user actually doing? (selecting, navigating, disclosing, confirming, inputting, monitoring)
- How many options / states / steps are involved? (1, 2, 3–5, 6–15, 16+, unbounded)
- Is the choice mutually exclusive or multi-select?
- Is the result reversible or destructive? (destructive needs more friction)
- What is the primary surface — desktop, mobile web, native iOS, native Android, or all four?
- What's the input modality? (mouse, touch, keyboard-only, screen reader, voice, stylus)
- Is the component on the critical path (checkout, signup) or ambient (settings, filters)?
- Are options known at build time, fetched once, or streamed/paginated?
- Does the user need to compare options side-by-side, or scan sequentially?
If you can't answer these in one sentence each, stop and clarify with the requester. The component decisions below are downstream of this framing.
Step 2 — Pick the family
Match the user's intent to a family, then drill into the specific component in Step 3.
- Selection — user picks 1+ from a known set → Step 3A
- Binary state — user toggles on/off → Step 3B
- Disclosure — user reveals/hides content in place → Step 3C
- Overlay / transient — content floats above the page → Step 3D
- Navigation — user moves between views → Step 3E
- Sequential content — user pages through a series → Step 3F
- Numeric / range input → Step 3G
- Notification / status → Step 3H
Step 3A — Selection: radio, checkbox, select, combobox, segmented, chips
The single most ambiguous family. Decide by count, exclusivity, and screen real estate.
Single-select (mutually exclusive)
- 2 options that fit on one line? → Segmented control / Toggle Group (mobile-friendly, instant, no hidden state). Web:
ToggleGroup. Native: segmented control /SegmentedButtons(Paper). - 2–5 options, all important to see at once? → Radio group. Best for forms where the choice itself is informative (shipping speed, plan tier).
- 6–15 options? → Select / Dropdown. On mobile, prefer the native
<select>on web and the native picker (Picker/ActionSheetIOS/BottomSheet) on React Native — they get the OS-level wheel/sheet UI for free. - 16+ options or fetched dynamically? → Combobox / Autocomplete with type-to-filter. Required once scrolling becomes painful.
- Hierarchical or grouped options (country → state)? → Cascading select or Combobox with grouped sections, not nested radios.
- Visual options (colors, avatars, templates)? → Radio cards or chip group, not a text dropdown.
Mobile gotcha: a custom dropdown that worked beautifully on desktop usually feels broken on a phone (small hit target, no momentum scroll, no haptics). Default to native pickers on touch surfaces unless you have a strong reason.
Multi-select
- 2–7 options, all visible? → Checkbox group.
- 8+ options or filterable? → Multi-select combobox with chips for selected values.
- Tag / category style with free-text entry? → Tag input / chip input.
- Mutually exclusive within categories (filter facets)? → Faceted filter panel (checkbox groups inside accordions or popovers).
Step 3B — Binary state: switch, checkbox, toggle button
These three are not interchangeable. Pick by when the change takes effect and what kind of value it represents.
- Setting that takes effect immediately, no submit needed? → Switch (e.g. "Dark mode," "Notifications"). On native, use the platform
Switch— iOS and Android render it differently and users expect that. - One of many options in a form, applied on submit? → Checkbox (e.g. "I agree to terms," "Subscribe to newsletter").
- A press that changes view state, like bold/italic in an editor? → Toggle button (icon button with
aria-pressed). - A truly transient action that does something now and isn't a state? → Button, not a toggle.
Accessibility: switches need
role="switch"(web) and the rightaccessibilityRole(switch, native). A checkbox styled like a switch fails screen readers.
Step 3C — Disclosure: accordion vs collapsible vs tabs vs details
All four hide content until requested. Differ on how many panels can be open and whether the items are peers.
- Many independent peer items, any number can be open at once (FAQ)? → Accordion (multi-open mode).
- Many peer items, only one open at a time (specs / shipping / reviews)? → Accordion (single-open mode) or Tabs — pick Tabs if the panels are roughly equal in importance and length, Accordion if they're long-form prose.
- A single show/hide region — "Show more," advanced settings? → Collapsible (one disclosure, no group).
- Tiny inline expansion, native-feeling, no JS state needed? →
<details>/<summary>on web. Browser-native, free a11y, free SEO, free Ctrl+F discoverability. - Mobile context, panels are full-width and content is heavy? → Accordion (Tabs become cramped under ~360px).
- Mobile context, content is short and side-by-side comparison helps? → Tabs with horizontal scroll for overflow.
Anti-pattern: tabs with 7+ items on mobile. Either switch to a select / segmented scroll, or rethink the IA.
Step 3D — Overlays: modal vs drawer vs popover vs tooltip vs toast
Pick by focus stealing, dismissibility, and content size.
- Blocks the whole task until handled, may have a form? → Modal / Dialog. Trap focus, return focus on close.
- Side panel of related content, page underneath stays meaningful? → Drawer / Sheet. On mobile, prefer bottom sheet (thumb-reachable); on desktop, side drawer.
- Small contextual menu or info anchored to a trigger? → Popover.
- One-line, hover-only, supplementary label? → Tooltip. ⚠️ Tooltips don't exist on touch — never put critical info there. Mobile users will miss it.
- Confirming a destructive action? → Alert dialog (a more constrained modal with explicit confirm/cancel).
- Time-limited feedback after an action ("Saved")? → Toast / Snackbar. Don't put required actions in a toast — it auto-dismisses.
- Persistent in-page status that the user must acknowledge? → Alert / Banner (stays until dismissed or fixed).
Mobile rule of thumb: if you'd reach for a popover or tooltip on desktop, reach for a bottom sheet on mobile. Anchored overlays misbehave when the keyboard opens.
Step 3E — Navigation: tabs vs sidebar vs segmented vs breadcrumb vs bottom nav
- 2–5 peer views inside one page? → Tabs.
- 5–15 destinations across the whole app, desktop-first? → Sidebar nav.
- 3–5 top-level destinations on mobile? → Bottom tab bar (native pattern on iOS/Android; on mobile web, fixed bottom nav).
- Showing depth / location in a hierarchy? → Breadcrumb.
- 2–4 view modes (list / grid / map)? → Segmented control in the header, not tabs.
- Many destinations with overflow? → Sidebar with collapsible sections (web) or drawer triggered by hamburger (mobile).
Step 3F — Sequential / paginated content: carousel, pagination, infinite scroll, stepper
Horizontal vs vertical carousel
- Items the user is browsing (products, photos, recommendations)? → Horizontal carousel (pairs with thumb-swipe on mobile, mouse drag on desktop).
- Items the user is consuming sequentially (stories, reels, feed)? → Vertical carousel / vertical pager (TikTok / Stories model). Right when each item is full-screen and immersive.
- Both axes feel plausible (a gallery)? → Horizontal, except on TV / large landscape screens where vertical reads as a list and horizontal reads as featured.
- More than ~10 items? → Don't carousel — switch to a scrollable rail (no auto-advance, no pagination dots), a grid, or pagination.
- Hero / promotional carousel? → Strongly consider not building one. Auto-advancing hero carousels have well-documented engagement drops; a static hero or two stacked cards usually outperform.
Pagination vs infinite scroll vs load-more
- User needs deep linkability and back-button predictability (search results)? → Pagination.
- Mobile feed where engagement matters more than navigation? → Infinite scroll, but with a footer that's still reachable (use a "Load more" button at the end of the practical first batch, or expose a footer in a drawer).
- User needs to know how much is left? → Load-more button with a count, not infinite.
- Multi-step form / wizard? → Stepper, with a visible progress indicator.
Step 3G — Numeric & range input: slider, stepper, input, segmented
- Imprecise value where feel matters (volume, brightness)? → Slider.
- Precise value the user knows (age, quantity)? → Number input with
inputMode="numeric"on mobile. - Small integer range, ±1 actions feel natural (cart quantity)? → Stepper / NumberInput with ± buttons. Touch-friendly hit targets are essential here (44pt minimum).
- Range with two endpoints (min price–max price)? → Range slider.
- 3–5 discrete buckets? → Segmented control ("S / M / L / XL"), not a slider.
- Date or time? → Native picker on mobile (
<input type="date">web,DatePickerIOS/DatePickerAndroid/ community date picker on RN). Custom calendars are right only when range, multi-select, or business rules require it.
Step 3H — Notification / status: toast, alert, banner, badge, skeleton
- Action just completed, no acknowledgement needed? → Toast (3–5s).
- Persistent system message ("You're offline")? → Banner at top.
- Inline form/field problem? → Inline alert under the field.
- Page-level error or warning? → Alert component at the top of the content area.
- Numeric or boolean indicator on another element ("3 unread")? → Badge.
- Loading content the user is about to see? → Skeleton matching the final layout (not a spinner) for perceived-performance wins.
- Indeterminate background work? → Spinner / progress indicator.
Step 4 — Cross-cutting checks before you commit
Run this list against the component you've chosen.
- Touch targets ≥ 44pt (iOS) / 48dp (Android)? Especially for steppers, switches, close buttons.
- Keyboard navigable? Tab order makes sense, Enter/Space activate, Escape closes overlays.
- Screen reader announces state changes?
aria-expanded,aria-pressed,aria-selected,role="switch",aria-livefor toasts. - Works without JS / on slow networks? Native
<select>,<details>,<form>degrade gracefully; custom replacements often don't. - Respects
prefers-reduced-motion? Carousels, accordions, sheets should skip or shorten animation. - Right-to-left safe? Carousels, breadcrumbs, sliders need RTL-aware direction.
- Keyboard-only mobile users (Bluetooth keyboards on iPad)? Same focus rules as desktop.
- Dark mode tokens applied? Not just the surface — borders, focus rings, disabled states.
- Component scales to the longest realistic content? Translated strings can be 30–60% longer.
- Component has a sensible empty state? Empty selects, empty lists, no-results.
Step 5 — Web ↔ Mobile parity quick map
Same intent, different primitive. Pick the platform-native one when shipping React Native, and the closest web equivalent when shipping web.
| Intent | Web (React) | React Native |
|---|---|---|
| Single select, many options | <select> / Combobox | Picker / native action sheet / bottom sheet |
| Multi select, many options | Multi-combobox + chips | Bottom sheet w/ checkbox list |
| On/off setting | Switch (shadcn / Radix) | Switch (RN core) |
| Quick disclosure | <details> / Collapsible | Pressable + LayoutAnimation / Reanimated |
| Side panel / sheet | Drawer / Sheet | Bottom sheet (@gorhom/bottom-sheet) |
| Inline overlay | Popover | Bottom sheet (popovers misbehave w/ keyboard) |
| Tooltip | Tooltip (hover) | Skip — use inline help text or info icon → sheet |
| Toast | Toast (sonner, Radix) | Snackbar (Paper) / native banner |
| Pagination | Pagination control | Replace with infinite scroll + pull-to-refresh |
| Tabs | Tabs (Radix / shadcn) | Top tabs (@react-navigation/material-top-tabs) |
| Bottom nav | Optional fixed bottom bar | @react-navigation/bottom-tabs |
| Carousel | Embla / Swiper | FlatList horizontal / react-native-reanimated-carousel |
| Date picker | <input type="date"> | @react-native-community/datetimepicker |
Step 6 — Defaults when you're stuck
If you've gone through the checklist and two options still feel equally valid, use these defaults — they're the lowest-risk pick most of the time:
- Selection of unknown size → Combobox.
- Toggle in a settings screen → Switch.
- Toggle in a form submitted later → Checkbox.
- One-of-three view modes → Segmented control.
- Confirm a destructive action → Alert dialog with the destructive button styled red and labelled with the verb ("Delete account"), not "OK."
- Mobile overlay → Bottom sheet.
- Mobile single-select dropdown → Native picker.
- List of 50+ items → Virtualized list (
react-windowweb,FlatList/FlashListnative), never a plain.map().
Cross-references
- Accordion, Tabs — disclosure decisions in Step 3C
- Radio Group, Checkbox, Select, Dropdown, Toggle Group — Step 3A
- Switch — Step 3B
- Modal, Popover, Tooltip, Toast, Alert — Step 3D
- Sidebar, Breadcrumb, Pagination — Step 3E and 3F
- Slider — Step 3G
- Badge, Skeleton — Step 3H