Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
175 lines
4.5 KiB
Markdown
175 lines
4.5 KiB
Markdown
---
|
|
title: Use Native Menus for Dropdowns and Context Menus
|
|
impact: HIGH
|
|
impactDescription: native accessibility, platform-consistent UX
|
|
tags: user-interface, menus, context-menus, zeego, accessibility
|
|
---
|
|
|
|
## Use Native Menus for Dropdowns and Context Menus
|
|
|
|
Use native platform menus instead of custom JS implementations. Native menus
|
|
provide built-in accessibility, consistent platform UX, and better performance.
|
|
Use [zeego](https://zeego.dev) for cross-platform native menus.
|
|
|
|
**Incorrect (custom JS menu):**
|
|
|
|
```tsx
|
|
import { useState } from 'react'
|
|
import { View, Pressable, Text } from 'react-native'
|
|
|
|
function MyMenu() {
|
|
const [open, setOpen] = useState(false)
|
|
|
|
return (
|
|
<View>
|
|
<Pressable onPress={() => setOpen(!open)}>
|
|
<Text>Open Menu</Text>
|
|
</Pressable>
|
|
{open && (
|
|
<View style={{ position: 'absolute', top: 40 }}>
|
|
<Pressable onPress={() => console.log('edit')}>
|
|
<Text>Edit</Text>
|
|
</Pressable>
|
|
<Pressable onPress={() => console.log('delete')}>
|
|
<Text>Delete</Text>
|
|
</Pressable>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Correct (native menu with zeego):**
|
|
|
|
```tsx
|
|
import * as DropdownMenu from 'zeego/dropdown-menu'
|
|
|
|
function MyMenu() {
|
|
return (
|
|
<DropdownMenu.Root>
|
|
<DropdownMenu.Trigger>
|
|
<Pressable>
|
|
<Text>Open Menu</Text>
|
|
</Pressable>
|
|
</DropdownMenu.Trigger>
|
|
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item key='edit' onSelect={() => console.log('edit')}>
|
|
<DropdownMenu.ItemTitle>Edit</DropdownMenu.ItemTitle>
|
|
</DropdownMenu.Item>
|
|
|
|
<DropdownMenu.Item
|
|
key='delete'
|
|
destructive
|
|
onSelect={() => console.log('delete')}
|
|
>
|
|
<DropdownMenu.ItemTitle>Delete</DropdownMenu.ItemTitle>
|
|
</DropdownMenu.Item>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu.Root>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Context menu (long-press):**
|
|
|
|
```tsx
|
|
import * as ContextMenu from 'zeego/context-menu'
|
|
|
|
function MyContextMenu() {
|
|
return (
|
|
<ContextMenu.Root>
|
|
<ContextMenu.Trigger>
|
|
<View style={{ padding: 20 }}>
|
|
<Text>Long press me</Text>
|
|
</View>
|
|
</ContextMenu.Trigger>
|
|
|
|
<ContextMenu.Content>
|
|
<ContextMenu.Item key='copy' onSelect={() => console.log('copy')}>
|
|
<ContextMenu.ItemTitle>Copy</ContextMenu.ItemTitle>
|
|
</ContextMenu.Item>
|
|
|
|
<ContextMenu.Item key='paste' onSelect={() => console.log('paste')}>
|
|
<ContextMenu.ItemTitle>Paste</ContextMenu.ItemTitle>
|
|
</ContextMenu.Item>
|
|
</ContextMenu.Content>
|
|
</ContextMenu.Root>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Checkbox items:**
|
|
|
|
```tsx
|
|
import * as DropdownMenu from 'zeego/dropdown-menu'
|
|
|
|
function SettingsMenu() {
|
|
const [notifications, setNotifications] = useState(true)
|
|
|
|
return (
|
|
<DropdownMenu.Root>
|
|
<DropdownMenu.Trigger>
|
|
<Pressable>
|
|
<Text>Settings</Text>
|
|
</Pressable>
|
|
</DropdownMenu.Trigger>
|
|
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.CheckboxItem
|
|
key='notifications'
|
|
value={notifications}
|
|
onValueChange={() => setNotifications((prev) => !prev)}
|
|
>
|
|
<DropdownMenu.ItemIndicator />
|
|
<DropdownMenu.ItemTitle>Notifications</DropdownMenu.ItemTitle>
|
|
</DropdownMenu.CheckboxItem>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu.Root>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Submenus:**
|
|
|
|
```tsx
|
|
import * as DropdownMenu from 'zeego/dropdown-menu'
|
|
|
|
function MenuWithSubmenu() {
|
|
return (
|
|
<DropdownMenu.Root>
|
|
<DropdownMenu.Trigger>
|
|
<Pressable>
|
|
<Text>Options</Text>
|
|
</Pressable>
|
|
</DropdownMenu.Trigger>
|
|
|
|
<DropdownMenu.Content>
|
|
<DropdownMenu.Item key='home' onSelect={() => console.log('home')}>
|
|
<DropdownMenu.ItemTitle>Home</DropdownMenu.ItemTitle>
|
|
</DropdownMenu.Item>
|
|
|
|
<DropdownMenu.Sub>
|
|
<DropdownMenu.SubTrigger key='more'>
|
|
<DropdownMenu.ItemTitle>More Options</DropdownMenu.ItemTitle>
|
|
</DropdownMenu.SubTrigger>
|
|
|
|
<DropdownMenu.SubContent>
|
|
<DropdownMenu.Item key='settings'>
|
|
<DropdownMenu.ItemTitle>Settings</DropdownMenu.ItemTitle>
|
|
</DropdownMenu.Item>
|
|
|
|
<DropdownMenu.Item key='help'>
|
|
<DropdownMenu.ItemTitle>Help</DropdownMenu.ItemTitle>
|
|
</DropdownMenu.Item>
|
|
</DropdownMenu.SubContent>
|
|
</DropdownMenu.Sub>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu.Root>
|
|
)
|
|
}
|
|
```
|
|
|
|
Reference: [Zeego Documentation](https://zeego.dev/components/dropdown-menu)
|