About this animation
A searchable action palette with sliding selection and configurable commands, placeholder, fonts, and accent color.
Why this one
Desktop command palettes usually become flat search lists on mobile. This one lets you drag the selection across commands, keeps matching letters connected to the search, and makes the selected action visibly execute. The panel contracts toward its launcher while a confirmation appears. The Nimbus demo changes the actual workspace theme, adds tasks and projects, switches destinations, prepares an invitation, previews CSV data, and archives completed tasks.
How it works
onLayoutmeasures the available container. The panel uses 92% of its width, capped at 520 pt, and starts at 14% of its height, with a 72 pt minimum clearance for viewer controls. Long lists scroll instead of shrinking touch targets.- One opening shared value drives panel opacity, drop, scale, backdrop depth, and blur intensity. Android blur uses a shared
BlurTargetViewwith the SDK 31+ method. - A subsequence matcher scores word starts, adjacent letters, and short gaps. Results sort by relevance while spring-driven row positions follow their new layout. Optional keywords participate with a lower score.
- The selected row position is a shared value. Dragging updates it on the UI thread; tapping a command executes through Gesture Handler. Search updates React only when text changes.
- Matching letters draw their accent underline. A result digit rolls in a fixed, measured window; collections of ten or more use a plain result count.
- Executing lights the selected row, calls the host once, contracts the palette, and announces its confirmation. Reopening or closing cancels pending execution. All timers are cleared on unmount.
Install
Copy this folder without demo/ into your project.
npx expo install expo-blur expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsWrap your app in GestureHandlerRootView. Configure Reanimated/worklets according to your Expo SDK. No images, network requests, or Skia initialization are required. Icons: Lucide (ISC), copied from the Native Animation kit and rendered as local SVG data with expo-image.
Usage
import { CommandPalette, type PaletteCommand } from './command-palette';
const commands: PaletteCommand[] = [
{ id: 'new-task', title: 'New task', group: 'Suggested', icon: 'plus', description: 'Capture something to do' },
{ id: 'inbox', title: 'Go to Inbox', group: 'Navigation', icon: 'inbox', description: 'See what needs your attention' },
];
export function Workspace() {
return (
<CommandPalette
commands={commands}
onExecute={(command) => {
// Apply the action to your own workspace here.
return `${command.title} complete`;
}}
>
<YourWorkspace />
</CommandPalette>
);
}const palette = useRef<CommandPaletteRef>(null);
<CommandPalette
ref={palette}
commands={commands}
accentColor="#38BDF8"
blurIntensity={40}
springDamping={30}
colorScheme="dark"
fontFamily="YourInterfaceFont"
monoFontFamily="YourMonoFont"
launcherLabel="Quick actions"
onOpenChange={(open) => trackSearchVisibility(open)}
/>
// External controls:
palette.current?.open();
palette.current?.search('task');
palette.current?.select('new-task');
palette.current?.execute();The ref exports open(), close(), search(query), select(id), and execute(). Opening resets search. Methods never open the software keyboard automatically; tap the input to type. Pass unique command IDs and keep grouped commands contiguous for the unfiltered view. Keep mobile titles and descriptions concise. Description defaults to the command group when omitted. Keyboard hints are hidden, including from screen-reader row labels, unless showShortcuts={true} is explicitly supplied. Hosts with a hardware keyboard may continue passing optional shortcut values. Import ref types from the same folder.
Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
commands | readonly PaletteCommand[] | Required | ID, title, group, kit icon, optional description, shortcut, keywords, confirmation |
children | ReactNode | — | Workspace rendered behind the palette |
onExecute | (command) => string | void | — | Apply an action and optionally return confirmation copy |
onOpenChange | (open: boolean) => void | — | Observe visibility changes |
onInteraction | () => void | — | Stop host autoplay on touch, drag, or typing |
accentColor | string | #38BDF8 | Match underlines, icons, selection border |
colorScheme | 'light' | 'dark' | 'auto' | 'auto' | Surface treatment; auto follows the device |
fontFamily | string | System | Interface font |
monoFontFamily | string | System | Section headers and shortcuts |
showShortcuts | boolean | false | Opt in to row, launcher, and close keyboard hints |
placeholder | string | Search actions… | Search hint |
launcherLabel | string | Search actions | Launcher text and accessible label |
blurIntensity | number | 40 | Glass intensity, clamped to 0–100 |
springDamping | number | 28 | Row/panel damping, minimum 28 to avoid excess bounce |
style | StyleProp<ViewStyle> | — | Root container overrides |
testID | string | — | Test selector |
PaletteCommand.icon accepts the kit names in icons.ts. The component imports no project theme; the demo supplies Geist and Geist Mono from the catalog's shared typography.
Compatibility
Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, and Gesture Handler 3.3.0. Supports iOS, Android, and web APIs. Android 12+ uses native blur; older Android versions fall back to translucent material.
Accessibility
Interactive controls have button roles and labels. Rows support screen-reader activation as well as tap gestures; opening also has a launcher alternative to the downward pull. The input accepts Return to execute, Escape to close, and arrow keys to select. Optional shortcut chips require showShortcuts={true}; register global shortcuts in the host app. Visibility, result changes, and confirmations are announced. Background accessibility is hidden while the modal is open, and hidden palette views do not receive input. The search field remains a native accessible input; no forced keyboard focus interrupts autoplay.
Reduced motion removes blur, drop, scale, contraction, shaking, and digit rolling. Opening/closing, underline changes, rows, and toast use 200 ms fades. Touch targets are at least 48 pt; command rows are 64 pt with separate title and description lines. Dragging only selects, so releasing does not execute an accidental command; tap a command or submit the input to execute.
Demo timeline
The demo opens at 260 ms; types d, da, and dar at 910, 1160, and 1410 ms; selects dark mode at 1810 ms; and executes at 2210 ms. The selected row lights, the workspace changes theme, and the panel contracts toward the launcher before 3 seconds. The loop repeats every 4.7 seconds, alternating the actual theme. Any physical touch or typing permanently stops that mounted demo's autoplay. There is no autoplay logic in the reusable component.
Performance and limitations
Position, opening, selection, pressure, underline drawing, and digit motion run on the UI thread. Search uses React state per query and a small linear subsequence scorer; use a prefiltered command collection for large datasets. The drag selects within the currently visible scroll viewport; swipe the list to reveal more results. onExecute is synchronous; perform async work in the host and provide your own pending/error feedback. The CSV demo displays local preview data rather than writing a file. The demo's invitation action prepares a draft rather than contacting another person.
Layout
The mobile polish preserves fuzzy filtering, animated row positions, match underlines, drag selection, and the execution transition. Demo commands contain descriptions instead of desktop shortcuts. The workspace scrolls in short containers, and the panel keeps 72 pt of top clearance with a keyboard-aware scroll viewport.




