A context menu provides access to functionality that's directly related to an item, without cluttering the interface.
Pro component
Requires all-access to use the source code
npx expo install react-native-keyboard-controller
import { Stack } from 'expo-router';
import * as React from 'react';
import { Platform, Pressable, View } from 'react-native';
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
import { Text } from '~/components/nativewindui/Text';
import { AlertAnchor } from '~/components/nativewindui/Alert';
import { AlertRef } from '~/components/nativewindui/Alert/types';
import { Button } from '../~/components/nativewindui/Button';
import { ContextMenu } from '~/components/nativewindui/ContextMenu';
import { ContextMenuRef } from '~/components/nativewindui/ContextMenu/types';
import {
createContextItem,
createContextSubMenu,
} from '~/components/nativewindui/ContextMenu/utils';
import { useColorScheme } from '~/lib/useColorScheme';
export default function ContextMenuScreen() {
const [checked, setChecked] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(true);
const { colors } = useColorScheme();
const ref = React.useRef<ContextMenuRef>(null);
const [selectedEmoji, setSelectedEmoji] = React.useState('');
const alertRef = React.useRef<AlertRef>(null);
const dynamicItems = React.useMemo(() => {
return [
createContextSubMenu({ title: 'Sub Menu', iOSItemSize: 'small', loading: isLoading }, [
createContextSubMenu({ title: 'Submenu 2' }, [
{ actionKey: '1', title: 'Item 1' },
{ actionKey: '2', title: 'Item 2' },
]),
createContextItem({ actionKey: '43', title: 'Item 3' }),
]),
createContextItem({
actionKey: '4',
title: 'Checkbox Item',
state: { checked },
keepOpenOnPress: true,
icon: {
namingScheme: 'sfSymbol',
name: 'checkmark.seal',
color: colors.primary,
},
}),
createContextItem({
actionKey: '5',
title: 'Set to loading',
keepOpenOnPress: true,
disabled: isLoading,
}),
];
}, [checked, isLoading]);
function handleEmojiPress(emoji: string) {
return () => {
if (emoji === selectedEmoji) {
return;
}
setSelectedEmoji(emoji);
ref.current?.dismissMenu?.();
};
}
return (
<>
<Stack.Screen options={{ title: 'Context Menu' }} />
<View className="flex-1 justify-center gap-8 p-8">
<ContextMenu
className="rounded-md"
title="Dropdown Menu"
items={STATIC_ITEMS}
materialAlign="start"
auxiliaryPreviewPosition="center"
renderAuxiliaryPreview={() => {
return (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
className="flex-row items-center justify-center rounded-md bg-red-500 px-12 py-4">
<Text variant="footnote">Auxiliary Preview</Text>
</Animated.View>
);
}}
onItemPress={(item) => {
alertRef.current?.alert({
title: 'Item Pressed',
message: `Item ${item.actionKey} pressed`,
buttons: [{ text: 'OK' }],
materialWidth: 350,
});
}}>
<Pressable className="border-foreground bg-card h-32 items-center justify-center rounded-md border border-dashed">
<Text variant="footnote" className="text-muted-foreground font-bold">
Static
</Text>
<Text>Long Press Me</Text>
</Pressable>
</ContextMenu>
<ContextMenu
ref={ref}
className="rounded-md"
items={dynamicItems}
auxiliaryPreviewPosition="center"
renderAuxiliaryPreview={() => {
return (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
className="bg-card flex-row rounded-md p-2 shadow">
<Button
variant={selectedEmoji === '❤️' ? 'tonal' : 'plain'}
size="icon"
onPress={handleEmojiPress('❤️')}>
<Text variant="footnote">❤️</Text>
</Button>
<Button
variant={selectedEmoji === '😍' ? 'tonal' : 'plain'}
size="icon"
onPress={handleEmojiPress('😍')}>
<Text variant="footnote">😍</Text>
</Button>
<Button
variant={selectedEmoji === '🥰' ? 'tonal' : 'plain'}
size="icon"
onPress={handleEmojiPress('🥰')}>
<Text variant="footnote">🥰</Text>
</Button>
<Button
variant={selectedEmoji === '💘' ? 'tonal' : 'plain'}
size="icon"
onPress={handleEmojiPress('💘')}>
<Text variant="footnote">💘</Text>
</Button>
</Animated.View>
);
}}
onItemPress={(item) => {
if (item.actionKey === '4') {
setChecked((prev) => !prev);
return;
}
if (item.actionKey === '5') {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 1500);
return;
}
alertRef.current?.alert({
title: 'Item Pressed',
message: `Item ${item.actionKey} pressed`,
buttons: [{ text: 'OK' }],
materialWidth: 350,
});
}}>
<Pressable
onLongPress={() => {
if (isLoading) {
setTimeout(() => {
setIsLoading(false);
}, 1500);
}
}}
className="border-primary bg-card h-32 items-center justify-center rounded-md border border-dashed">
<Text variant="footnote" className="text-muted-foreground font-bold">
With State
</Text>
<Text variant="footnote">Checked: {checked ? 'true' : 'false'}</Text>
<Text variant="footnote">Emoji: {selectedEmoji}</Text>
</Pressable>
</ContextMenu>
{Platform.OS === 'ios' && (
<ContextMenu
className="rounded-md"
title="Dropdown Menu"
items={STATIC_ITEMS}
materialAlign="start"
auxiliaryPreviewPosition="center"
renderAuxiliaryPreview={() => {
return (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
className="bg-card flex-row items-center justify-center rounded-md px-12 py-4">
<Text variant="footnote">Auxiliary Preview</Text>
</Animated.View>
);
}}
iosRenderPreview={() => {
return (
<View className="aspect-square h-72 items-center justify-center rounded-md bg-red-500 px-12 py-4">
<Text variant="footnote">iOS Preview</Text>
</View>
);
}}
onItemPress={(item) => {
alertRef.current?.alert({
title: 'Item Pressed',
message: `Item ${item.actionKey} pressed`,
buttons: [{ text: 'OK' }],
materialWidth: 350,
});
}}>
<Pressable className="border-accent bg-card h-32 items-center justify-center rounded-md border border-dashed">
<Text variant="footnote" className="text-muted-foreground font-bold">
With Preview
</Text>
<Text>Long Press Me</Text>
</Pressable>
</ContextMenu>
)}
</View>
<AlertAnchor ref={alertRef} />
</>
);
}
const STATIC_ITEMS = [
createContextSubMenu({ title: 'Submenu 1', iOSItemSize: 'small', loading: false }, [
createContextSubMenu({ title: 'Sub', iOSItemSize: 'small' }, [
{ actionKey: '10', title: 'Select Me' },
{ actionKey: '20', title: 'No! Select Me!' },
]),
createContextItem({
actionKey: '430',
title: 'Item 430',
icon: { name: 'checkmark.seal', namingScheme: 'sfSymbol' },
}),
]),
createContextSubMenu({ title: 'Hello', iOSItemSize: 'small' }, [
createContextItem({
actionKey: '30',
icon: { name: 'checkmark.seal', namingScheme: 'sfSymbol' },
}),
createContextItem({
actionKey: '31',
icon: { name: 'checkmark.seal', namingScheme: 'sfSymbol' },
}),
createContextItem({
actionKey: '32',
icon: { name: 'checkmark.seal', namingScheme: 'sfSymbol' },
}),
createContextItem({
actionKey: '33',
icon: { name: 'checkmark.seal', namingScheme: 'sfSymbol' },
}),
]),
createContextSubMenu({ title: '', iOSType: 'inline', iOSItemSize: 'small' }, [
createContextItem({
actionKey: '130',
title: '💧',
}),
createContextItem({
actionKey: '131',
title: '💧',
}),
createContextItem({
actionKey: '132',
title: '💧',
}),
createContextItem({
actionKey: '133',
title: '💧',
}),
]),
createContextItem({
actionKey: '40',
title: 'Delete Computer',
destructive: true,
image: { url: 'https://picsum.photos/id/2/100', cornerRadius: 30 },
}),
];
Unlock All Pro Screens & Components
Elevate your app with powerful, native-feeling components and templates. Build faster with beautiful ready-to-use designs.
The Context-Menu is not compatible with the new architecture yet .
The child component needs to accept Pressable props to ensure compatibility on Android.
import { ContextMenu } from '~/components/nativewindui/ContextMenu';
import {
createContextItem,
createContextSubMenu,
} from '~/components/nativewindui/ContextMenu/utils';
<ContextMenu
className="rounded-md"
items={[
createContextItem({
actionKey: 'first',
title: 'Item 1',
}),
createContextSubMenu({ title: 'Submenu 1', iOSItemSize: 'small' }, [
createContextItem({
actionKey: 'sub-first',
title: 'Sub Item 1',
}),
createContextItem({
actionKey: 'sub-second',
title: 'Sub Item 2',
}),
]),
]}
onItemPress={(item) => {
console.log('Item Pressed', item);
}}>
<Pressable>
<Text>Long Press Me</Text>
</Pressable>
</ContextMenu>
ContextMenu
Inherits all the props from React Native's View component.
Prop | Type | Default | Description |
---|---|---|---|
title | string | The title of the context menu. | |
items | (ContextItem | ContextSubMenu)[] | The items or submenus to display in the context menu. | |
iOSItemSize | 'small' | 'medium' | 'large' | The preferred item size on iOS. | |
children | React.ReactNode | The child component to render inside the context menu. It needs to accept Pressable props to ensure compatibility on Android | |
onItemPress | (item: Omit<ContextItem, 'icon'>, isUsingActionSheetFallback?: boolean) => void | Callback function triggered when an item is pressed. | |
enabled | boolean | true | Whether the context menu is enabled. |
iosRenderPreview | () => React.ReactElement | Function to render a preview on iOS. | |
iosOnPressMenuPreview | () => void | Callback for pressing the menu preview on iOS. | |
renderAuxiliaryPreview | () => React.ReactElement | Function to render an auxiliary preview. | |
auxiliaryPreviewPosition | 'start' | 'center' | 'end' | Position for the auxiliary preview. | |
materialPortalHost | string | The host for the Android portal. | |
materialSideOffset | number | 2 | Side offset for Android context menu. |
materialAlignOffset | number | Alignment offset for Android context menu. | |
materialAlign | 'start' | 'center' | 'end' | Alignment for Android context menu. | |
materialWidth | number | Width for the Android context menu. | |
materialMinWidth | number | Minimum width for the Android context menu. | |
materialLoadingText | string | Text to display while the context menu is loading. | |
materialSubMenuTitlePlaceholder | string | Placeholder title for the Android submenu. | |
materialOverlayClassName | string | Class name for the Android overlay. |
createContextSubMenu
: A utility function to create a context sub-menu.
createContextItem
: A utility function to create a context item.
import { createContextSubMenu, createContextItem } from '~/components/nativewindui/ContextMenu/utils';
const MENU = [
createContextSubMenu({ title: 'Submenu' }, [
createContextItem({ actionKey: '1', title: 'Item 1' }),
createContextItem({ actionKey: '2', title: 'Item 2' }),
]),
];