ui-ux-consultant-cli 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/assets/ui-ux-consultant/SKILL.md +844 -0
  2. package/assets/ui-ux-consultant/references/accessibility.md +175 -0
  3. package/assets/ui-ux-consultant/references/alt-libraries.md +90 -0
  4. package/assets/ui-ux-consultant/references/animations.md +448 -0
  5. package/assets/ui-ux-consultant/references/catalog/colors.md +91 -0
  6. package/assets/ui-ux-consultant/references/catalog/fonts.md +363 -0
  7. package/assets/ui-ux-consultant/references/catalog/products.md +340 -0
  8. package/assets/ui-ux-consultant/references/catalog/styles.md +165 -0
  9. package/assets/ui-ux-consultant/references/components.md +1116 -0
  10. package/assets/ui-ux-consultant/references/patterns.md +600 -0
  11. package/assets/ui-ux-consultant/references/performance.md +198 -0
  12. package/assets/ui-ux-consultant/references/stacks/astro.md +382 -0
  13. package/assets/ui-ux-consultant/references/stacks/flutter.md +308 -0
  14. package/assets/ui-ux-consultant/references/stacks/html-tailwind.md +415 -0
  15. package/assets/ui-ux-consultant/references/stacks/jetpack-compose.md +333 -0
  16. package/assets/ui-ux-consultant/references/stacks/laravel.md +521 -0
  17. package/assets/ui-ux-consultant/references/stacks/nextjs.md +275 -0
  18. package/assets/ui-ux-consultant/references/stacks/nuxt-ui.md +384 -0
  19. package/assets/ui-ux-consultant/references/stacks/nuxtjs.md +264 -0
  20. package/assets/ui-ux-consultant/references/stacks/react-native.md +346 -0
  21. package/assets/ui-ux-consultant/references/stacks/react.md +268 -0
  22. package/assets/ui-ux-consultant/references/stacks/shadcn.md +485 -0
  23. package/assets/ui-ux-consultant/references/stacks/svelte.md +429 -0
  24. package/assets/ui-ux-consultant/references/stacks/swiftui.md +336 -0
  25. package/assets/ui-ux-consultant/references/stacks/threejs.md +366 -0
  26. package/assets/ui-ux-consultant/references/stacks/vue.md +272 -0
  27. package/assets/ui-ux-consultant/references/theming.md +701 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +130 -0
  30. package/package.json +51 -0
@@ -0,0 +1,264 @@
1
+ # Nuxt.js UI/UX Guidelines
2
+
3
+ ## When to read this
4
+ Read this file when building with Nuxt 3. Covers auto-imports, SSR-safe patterns, data fetching composables, server routes, state management, middleware, and performance for production Nuxt apps.
5
+
6
+ ## Recommended UI Libraries
7
+
8
+ | Library | Best for | Install |
9
+ |---|---|---|
10
+ | Nuxt UI | Ready-made components (Tailwind-based) | `npx nuxi module add ui` |
11
+ | @pinia/nuxt | State management | `npx nuxi module add pinia` |
12
+ | @nuxt/image | Optimized images | `npx nuxi module add image` |
13
+ | @nuxt/content | Content / MDX pages | `npx nuxi module add content` |
14
+ | VueUse | Composable utilities (Nuxt-aware) | `npm install @vueuse/nuxt` |
15
+ | @nuxtjs/color-mode | Dark/light mode | `npx nuxi module add color-mode` |
16
+
17
+ ## Style Recommendations by App Type
18
+
19
+ - **SaaS / product:** Nuxt UI + custom Tailwind color tokens
20
+ - **Marketing/content:** @nuxt/content + @nuxt/image + custom Tailwind
21
+ - **Dashboard:** Nuxt UI data table + sidebar layout + @pinia/nuxt
22
+ - **Enterprise:** Element Plus + custom Nuxt module for design tokens
23
+ - **E-commerce:** Custom Tailwind + @nuxt/image + Pinia cart store
24
+
25
+ ## Top UX Patterns
26
+
27
+ ### 1. useFetch for SSR-Aware Data Fetching
28
+ ```typescript
29
+ // Preferred for simple API calls — SSR-aware, handles hydration automatically
30
+ const { data, pending, error, refresh } = await useFetch('/api/users');
31
+
32
+ // With options
33
+ const { data: posts } = await useFetch('/api/posts', {
34
+ query: { page: 1, limit: 10 },
35
+ pick: ['id', 'title', 'slug'], // Only serialize needed fields
36
+ });
37
+ ```
38
+
39
+ ### 2. useAsyncData for Complex Fetching
40
+ ```typescript
41
+ // More control — custom key, transform, watch, lazy
42
+ const { data, pending } = await useAsyncData(
43
+ 'users', // Cache key — must be unique per page
44
+ () => $fetch('/api/users'),
45
+ {
46
+ watch: [page], // Re-fetch when page changes
47
+ transform: data => data.users, // Transform before caching
48
+ lazy: true, // Don't block navigation
49
+ default: () => [], // Default value before fetch completes
50
+ }
51
+ );
52
+ ```
53
+
54
+ ### 3. Server API Route
55
+ ```typescript
56
+ // server/api/users.get.ts
57
+ export default defineEventHandler(async (event) => {
58
+ const query = getQuery(event);
59
+ const { page = 1, limit = 20 } = query;
60
+
61
+ const users = await db.user.findMany({
62
+ skip: (Number(page) - 1) * Number(limit),
63
+ take: Number(limit),
64
+ });
65
+
66
+ return { users, page, limit };
67
+ });
68
+
69
+ // server/api/users.post.ts — POST handler
70
+ export default defineEventHandler(async (event) => {
71
+ const body = await readBody(event);
72
+ const user = await db.user.create({ data: body });
73
+ return user;
74
+ });
75
+ ```
76
+
77
+ ### 4. SSR-Safe Shared State with useState
78
+ ```typescript
79
+ // composables/useCounter.ts — shared across components, SSR-safe
80
+ export const useCounter = () => useState('counter', () => 0);
81
+
82
+ // Usage in any component — no hydration mismatch
83
+ const count = useCounter();
84
+ count.value++;
85
+ ```
86
+
87
+ ### 5. Pinia Store with Nuxt
88
+ ```typescript
89
+ // stores/cart.ts
90
+ export const useCartStore = defineStore('cart', () => {
91
+ const items = ref<CartItem[]>([]);
92
+ const total = computed(() => items.value.reduce((sum, i) => sum + i.price, 0));
93
+
94
+ function addItem(product: Product) {
95
+ const existing = items.value.find(i => i.id === product.id);
96
+ if (existing) existing.qty++;
97
+ else items.value.push({ ...product, qty: 1 });
98
+ }
99
+
100
+ function removeItem(id: string) {
101
+ items.value = items.value.filter(i => i.id !== id);
102
+ }
103
+
104
+ return { items, total, addItem, removeItem };
105
+ });
106
+ ```
107
+
108
+ ### 6. Route Middleware for Auth
109
+ ```typescript
110
+ // middleware/auth.ts
111
+ export default defineNuxtRouteMiddleware((to) => {
112
+ const user = useUser(); // useState-based composable
113
+ if (!user.value) {
114
+ return navigateTo('/login', { redirectCode: 302 });
115
+ }
116
+ });
117
+
118
+ // pages/dashboard.vue — apply middleware
119
+ definePageMeta({
120
+ middleware: 'auth',
121
+ });
122
+ ```
123
+
124
+ ### 7. Page with SEO Metadata
125
+ ```vue
126
+ <script setup lang="ts">
127
+ const route = useRoute();
128
+ const { data: post } = await useFetch(`/api/posts/${route.params.slug}`);
129
+
130
+ useSeoMeta({
131
+ title: post.value?.title,
132
+ description: post.value?.excerpt,
133
+ ogTitle: post.value?.title,
134
+ ogImage: post.value?.coverImage,
135
+ twitterCard: 'summary_large_image',
136
+ });
137
+ </script>
138
+ ```
139
+
140
+ ### 8. Client-Only Component (avoids SSR)
141
+ ```vue
142
+ <!-- Wrap browser-only content -->
143
+ <ClientOnly>
144
+ <MapComponent />
145
+ <template #fallback>
146
+ <MapSkeleton />
147
+ </template>
148
+ </ClientOnly>
149
+ ```
150
+
151
+ ### 9. Plugin for Global Setup
152
+ ```typescript
153
+ // plugins/toast.client.ts — client-only plugin
154
+ export default defineNuxtPlugin(() => {
155
+ return {
156
+ provide: {
157
+ toast: (message: string) => {
158
+ // Initialize toast library here
159
+ window.__toast?.show(message);
160
+ },
161
+ },
162
+ };
163
+ });
164
+
165
+ // Usage in component
166
+ const { $toast } = useNuxtApp();
167
+ $toast('Saved successfully!');
168
+ ```
169
+
170
+ ### 10. Error Handling with createError
171
+ ```typescript
172
+ // server/api/posts/[id].get.ts
173
+ export default defineEventHandler(async (event) => {
174
+ const id = getRouterParam(event, 'id');
175
+ const post = await db.post.findUnique({ where: { id } });
176
+
177
+ if (!post) {
178
+ throw createError({ statusCode: 404, statusMessage: 'Post not found' });
179
+ }
180
+
181
+ return post;
182
+ });
183
+
184
+ // pages/posts/[id].vue — handle errors from useFetch
185
+ const { data: post, error } = await useFetch(`/api/posts/${route.params.id}`);
186
+ if (error.value) throw createError({ fatal: true, statusCode: error.value.statusCode });
187
+ ```
188
+
189
+ ## Best Practices by Category
190
+
191
+ ### Auto-Imports
192
+ - Components in `components/` — auto-imported with PascalCase name
193
+ - Composables in `composables/` — auto-imported, use `use` prefix
194
+ - Utilities in `utils/` — auto-imported
195
+ - Pinia stores in `stores/` — auto-imported with `@pinia/nuxt`
196
+ - Do not import Vue primitives manually (`ref`, `computed`) — auto-imported
197
+
198
+ ### Data Fetching
199
+ - `useFetch` for simple, declarative API calls in components and pages
200
+ - `useAsyncData` when you need a custom cache key, transform, or `watch` deps
201
+ - `$fetch` only in event handlers (click, submit) — not in setup() for initial data
202
+ - Always provide a unique cache key to `useAsyncData` — avoid collisions between pages
203
+ - `lazy: true` for non-critical data that should not block navigation
204
+
205
+ ### SSR Safety
206
+ - Never access `window`, `document`, or `localStorage` in composables or setup — wrap in `if (import.meta.client)`
207
+ - Use `useLocalStorage` from VueUse (`@vueuse/nuxt`) — handles SSR safely
208
+ - `useState` for shared state that must survive SSR hydration
209
+ - `<ClientOnly>` for components that only work in the browser
210
+
211
+ ### State Management
212
+ - `useState` for simple cross-component state — SSR-safe, no extra package
213
+ - Pinia (`@pinia/nuxt`) for complex state with actions and getters
214
+ - `storeToRefs()` when destructuring Pinia stores — preserves reactivity
215
+
216
+ ### Routing
217
+ - `definePageMeta` for page-level config: middleware, layout, keepalive, head
218
+ - `useRouter()` / `useRoute()` for navigation — not `$router`
219
+ - Named middleware files in `middleware/` — applied via `definePageMeta`
220
+ - `navigateTo()` for programmatic navigation — handles SSR and client
221
+
222
+ ### Performance
223
+ - `lazy: true` on `useAsyncData` for data that does not affect initial render
224
+ - `@nuxt/image` on every image — automatic optimization and lazy loading
225
+ - `defineAsyncComponent` for heavy client-side components
226
+ - Server-side rendering for all public pages — avoid `ssr: false` unless absolutely needed
227
+
228
+ ### Forms
229
+ - Server API routes as form targets — no separate API service needed
230
+ - Vee-Validate + Zod for schema validation
231
+ - `useFormData` pattern with server-side validation in API route
232
+ - Return structured errors from API routes: `{ error: { field: 'message' } }`
233
+
234
+ ### Accessibility
235
+ - Use Nuxt UI components — built on Headless UI with ARIA baked in
236
+ - `useSeoMeta` on every public page — title, description, OG tags
237
+ - Consistent `<NuxtLink>` for internal navigation — renders as `<a>` with prefetch
238
+ - ARIA live regions for async content updates
239
+
240
+ ## Common Anti-Patterns
241
+
242
+ 1. `localStorage` in `setup()` — crashes SSR; use `useLocalStorage` from VueUse
243
+ 2. `window` or `document` in composables without `if (import.meta.client)` guard
244
+ 3. `$fetch` in component `setup()` without `useAsyncData` — no SSR, no caching, no deduplication
245
+ 4. Manual `<head>` tags via `useHead` when `useSeoMeta` is simpler and safer
246
+ 5. Heavy components without `<ClientOnly>` — SSR attempts to render browser-only APIs
247
+ 6. Sequential `await useFetch` calls — use `Promise.all` with `useAsyncData`
248
+ 7. Duplicate `useAsyncData` keys across pages — causes cache collisions and stale data
249
+ 8. `ssr: false` on entire app — loses SEO and initial load performance
250
+ 9. Missing `lazy: true` on non-critical data — blocks navigation unnecessarily
251
+ 10. Not using `definePageMeta` for middleware — results in unprotected routes
252
+
253
+ ## Performance Checklist
254
+
255
+ - [ ] `useAsyncData` with `lazy: true` for non-critical, below-fold data
256
+ - [ ] `@nuxt/image` module installed and used on all `<img>` tags
257
+ - [ ] Route-level `definePageMeta({ middleware: 'auth' })` for protected routes
258
+ - [ ] SSR data fetching via `useFetch` / `useAsyncData` — avoids client waterfalls
259
+ - [ ] `useState` for cross-component shared state (SSR-safe, no hydration mismatch)
260
+ - [ ] `<ClientOnly>` wrapping all browser-only components
261
+ - [ ] `useSeoMeta` on every public-facing page
262
+ - [ ] Server API routes return only the fields needed (use `pick` option)
263
+ - [ ] Pinia store persisted with `pinia-plugin-persistedstate` if needed across reloads
264
+ - [ ] `nitro.compressPublicAssets: true` in `nuxt.config.ts` for production
@@ -0,0 +1,346 @@
1
+ # React Native Reference
2
+
3
+ ## When to Read
4
+ Read this file when building React Native apps with Expo — components, styling, navigation, lists, keyboard handling, accessibility, or performance.
5
+
6
+ ---
7
+
8
+ ## Recommended Libraries
9
+
10
+ | Library | Purpose | Install |
11
+ |---|---|---|
12
+ | React Navigation | Routing | `npm install @react-navigation/native` |
13
+ | NativeWind | Tailwind for RN | `npm install nativewind` |
14
+ | React Native Paper | Material components | `npm install react-native-paper` |
15
+ | MMKV | Fast local storage | `npm install react-native-mmkv` |
16
+ | Reanimated | 60fps animations (UI thread) | `npx expo install react-native-reanimated` |
17
+ | Zustand | Lightweight state management | `npm install zustand` |
18
+ | React Query / TanStack Query | Server state, caching | `npm install @tanstack/react-query` |
19
+ | Expo Image | Optimized image component | `npx expo install expo-image` |
20
+ | Zod | Schema validation | `npm install zod` |
21
+
22
+ ---
23
+
24
+ ## Style Recommendations
25
+
26
+ - `StyleSheet.create` for all styles — never inline objects in JSX
27
+ - Follow iOS 8pt grid / Android 8dp grid for spacing
28
+ - Minimum touch target: 44×44pt (iOS HIG) — use `minWidth`/`minHeight` or `hitSlop`
29
+ - System font scales: use `fontSize` values from a scale (12, 14, 16, 18, 24, 32)
30
+ - NativeWind for utility-first styling in Expo projects (Tailwind classes on RN components)
31
+ - Avoid hardcoded colors — use a theme object or `useTheme()` from React Navigation
32
+
33
+ ---
34
+
35
+ ## Expo vs Bare Workflow
36
+
37
+ Use **Expo managed workflow** for 95% of apps:
38
+ - Handles native builds, OTA updates (EAS Update), and device APIs
39
+ - `npx expo start` for instant dev iteration
40
+ - EAS Build for production `.ipa`/`.apk`
41
+
42
+ Go **bare** only when you need a custom native module not in Expo SDK.
43
+
44
+ ---
45
+
46
+ ## Top UX Patterns (with Code)
47
+
48
+ ### StyleSheet (always — never inline objects)
49
+ ```typescript
50
+ import { StyleSheet, View, Text } from 'react-native';
51
+
52
+ const styles = StyleSheet.create({
53
+ container: { flex: 1, backgroundColor: '#fff', padding: 16 },
54
+ title: { fontSize: 24, fontWeight: '700', color: '#111' },
55
+ card: {
56
+ borderRadius: 12,
57
+ padding: 16,
58
+ backgroundColor: '#f8fafc',
59
+ marginBottom: 12,
60
+ },
61
+ });
62
+
63
+ export function MyScreen() {
64
+ return (
65
+ <View style={styles.container}>
66
+ <Text style={styles.title}>Hello</Text>
67
+ </View>
68
+ );
69
+ }
70
+ ```
71
+
72
+ ### Responsive sizing
73
+ ```typescript
74
+ import { useWindowDimensions } from 'react-native';
75
+
76
+ function ResponsiveLayout() {
77
+ const { width, height } = useWindowDimensions(); // updates on rotation
78
+ const isTablet = width >= 768;
79
+
80
+ return isTablet ? <TabletLayout /> : <PhoneLayout />;
81
+ }
82
+ // Never use Dimensions.get() in render — it doesn't update on rotation
83
+ ```
84
+
85
+ ### FlatList (always for long lists)
86
+ ```tsx
87
+ <FlatList
88
+ data={items}
89
+ keyExtractor={(item) => item.id}
90
+ renderItem={({ item }) => <ItemCard item={item} />}
91
+ ItemSeparatorComponent={() => <View style={{ height: 8 }} />}
92
+ ListEmptyComponent={<EmptyState />}
93
+ ListHeaderComponent={<ListHeader />}
94
+ onEndReached={loadMore}
95
+ onEndReachedThreshold={0.3}
96
+ refreshControl={
97
+ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
98
+ }
99
+ initialNumToRender={10}
100
+ maxToRenderPerBatch={10}
101
+ windowSize={5}
102
+ />
103
+ ```
104
+
105
+ ### Navigation (React Navigation — Stack + Tab)
106
+ ```tsx
107
+ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
108
+ import { createNativeStackNavigator } from '@react-navigation/native-stack';
109
+
110
+ const Tab = createBottomTabNavigator();
111
+ const Stack = createNativeStackNavigator();
112
+
113
+ function HomeStack() {
114
+ return (
115
+ <Stack.Navigator screenOptions={{ headerShown: true }}>
116
+ <Stack.Screen name="Feed" component={FeedScreen} />
117
+ <Stack.Screen name="Post" component={PostScreen} />
118
+ </Stack.Navigator>
119
+ );
120
+ }
121
+
122
+ function RootNavigator() {
123
+ return (
124
+ <Tab.Navigator>
125
+ <Tab.Screen name="Home" component={HomeStack} />
126
+ <Tab.Screen name="Profile" component={ProfileScreen} />
127
+ </Tab.Navigator>
128
+ );
129
+ }
130
+ ```
131
+
132
+ ### Minimum touch target (44×44pt)
133
+ ```tsx
134
+ import { TouchableOpacity, StyleSheet } from 'react-native';
135
+
136
+ <TouchableOpacity
137
+ style={styles.iconButton}
138
+ onPress={handlePress}
139
+ activeOpacity={0.7}
140
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
141
+ >
142
+ <Icon name="heart" size={24} />
143
+ </TouchableOpacity>
144
+
145
+ // styles:
146
+ iconButton: {
147
+ minWidth: 44,
148
+ minHeight: 44,
149
+ alignItems: 'center',
150
+ justifyContent: 'center',
151
+ },
152
+ ```
153
+
154
+ ### Keyboard handling
155
+ ```tsx
156
+ import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
157
+
158
+ <KeyboardAvoidingView
159
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
160
+ style={{ flex: 1 }}
161
+ >
162
+ <ScrollView keyboardShouldPersistTaps="handled">
163
+ <TextInput
164
+ placeholder="Email"
165
+ keyboardType="email-address"
166
+ autoCapitalize="none"
167
+ returnKeyType="next"
168
+ />
169
+ <TextInput
170
+ placeholder="Password"
171
+ secureTextEntry
172
+ returnKeyType="done"
173
+ onSubmitEditing={handleLogin}
174
+ />
175
+ </ScrollView>
176
+ </KeyboardAvoidingView>
177
+ ```
178
+
179
+ ### Accessible touch target
180
+ ```tsx
181
+ <TouchableOpacity
182
+ accessible={true}
183
+ accessibilityLabel="Delete item"
184
+ accessibilityRole="button"
185
+ accessibilityHint="Double tap to delete this item permanently"
186
+ onPress={handleDelete}
187
+ >
188
+ <Icon name="trash" size={20} />
189
+ </TouchableOpacity>
190
+ ```
191
+
192
+ ### Zustand store
193
+ ```typescript
194
+ import { create } from 'zustand';
195
+
196
+ interface AuthStore {
197
+ user: User | null;
198
+ setUser: (user: User | null) => void;
199
+ logout: () => void;
200
+ }
201
+
202
+ export const useAuthStore = create<AuthStore>((set) => ({
203
+ user: null,
204
+ setUser: (user) => set({ user }),
205
+ logout: () => set({ user: null }),
206
+ }));
207
+
208
+ // In component:
209
+ const user = useAuthStore((state) => state.user); // subscribe to slice only
210
+ ```
211
+
212
+ ### TanStack Query for server state
213
+ ```tsx
214
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
215
+
216
+ function UserList() {
217
+ const { data, isLoading, error } = useQuery({
218
+ queryKey: ['users'],
219
+ queryFn: () => api.getUsers(),
220
+ });
221
+
222
+ if (isLoading) return <ActivityIndicator />;
223
+ if (error) return <ErrorState message={error.message} />;
224
+
225
+ return (
226
+ <FlatList
227
+ data={data}
228
+ keyExtractor={(u) => u.id}
229
+ renderItem={({ item }) => <UserRow user={item} />}
230
+ />
231
+ );
232
+ }
233
+ ```
234
+
235
+ ### Reanimated animation (runs on UI thread)
236
+ ```tsx
237
+ import Animated, {
238
+ useSharedValue,
239
+ useAnimatedStyle,
240
+ withSpring,
241
+ } from 'react-native-reanimated';
242
+
243
+ function ScaleButton({ onPress }: { onPress: () => void }) {
244
+ const scale = useSharedValue(1);
245
+
246
+ const animatedStyle = useAnimatedStyle(() => ({
247
+ transform: [{ scale: scale.value }],
248
+ }));
249
+
250
+ return (
251
+ <Animated.View style={animatedStyle}>
252
+ <TouchableOpacity
253
+ onPressIn={() => { scale.value = withSpring(0.95); }}
254
+ onPressOut={() => { scale.value = withSpring(1); }}
255
+ onPress={onPress}
256
+ >
257
+ <Text>Press me</Text>
258
+ </TouchableOpacity>
259
+ </Animated.View>
260
+ );
261
+ }
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Best Practices by Category
267
+
268
+ ### Styling
269
+ - Always `StyleSheet.create` — objects are frozen and bridged once, not every render
270
+ - Avoid `style={[styles.base, condition && styles.variant]}` with long arrays — extract a helper
271
+ - Use `flex: 1` on container screens to fill SafeArea
272
+ - `Platform.select({ ios: ..., android: ... })` for platform-specific styles
273
+
274
+ ### Lists
275
+ - `FlatList` for everything with 20+ items — never `ScrollView` + `.map`
276
+ - `SectionList` for grouped data
277
+ - `keyExtractor` must return a stable unique string — not index
278
+ - `getItemLayout` for known-height rows (significant performance boost)
279
+ - `removeClippedSubviews={true}` on Android for very long lists
280
+
281
+ ### Navigation
282
+ - `@react-navigation/native-stack` (uses native UINavigationController) — not JS stack
283
+ - Pass typed params using TypeScript generic: `Stack.Screen<StackParamList, 'PostDetail'>`
284
+ - `useNavigation` hook inside screen components; avoid `navigation.navigate` from non-screen components
285
+ - Deep linking: configure `linking` prop on `NavigationContainer`
286
+
287
+ ### State Management
288
+ - Zustand for client state — minimal boilerplate, no Provider needed
289
+ - TanStack Query for server state — handles caching, refetching, background updates
290
+ - MMKV for persisted state — 10× faster than AsyncStorage
291
+ - Avoid prop drilling beyond 2 levels — use Zustand slice or Context
292
+
293
+ ### Keyboard & Input
294
+ - `KeyboardAvoidingView` wraps every screen with inputs
295
+ - `keyboardShouldPersistTaps="handled"` on `ScrollView` — taps dismiss keyboard only outside inputs
296
+ - `returnKeyType="next"` and `onSubmitEditing` to move focus between fields
297
+ - `textContentType` for iOS autofill; `autoComplete` for Android
298
+
299
+ ### Touch & Gestures
300
+ - `activeOpacity={0.7}` on all `TouchableOpacity` — default 0.2 looks broken
301
+ - `hitSlop` for small icons — increases tap area without changing layout
302
+ - `Pressable` for more complex press states (hover, focus on web)
303
+ - Minimum 44pt touch targets everywhere — enforce with `minWidth`/`minHeight`
304
+
305
+ ### Accessibility
306
+ - `accessibilityRole` on every interactive element
307
+ - `accessibilityLabel` describes what it is; `accessibilityHint` describes what happens
308
+ - `accessibilityState={{ disabled: true }}` for disabled controls
309
+ - Test with iOS VoiceOver and Android TalkBack on real devices
310
+
311
+ ### Performance
312
+ - `React.memo` on list item components — prevents re-render when parent updates
313
+ - `useCallback` on `renderItem` — stable reference prevents FlatList re-renders
314
+ - `useMemo` for expensive transforms on large datasets
315
+ - Avoid anonymous functions in JSX (`onPress={() => fn(item)}`) in hot render paths — use `useCallback`
316
+
317
+ ---
318
+
319
+ ## Common Anti-Patterns
320
+
321
+ 1. `ScrollView` with long lists — freezes UI thread rendering all items; use `FlatList`
322
+ 2. Inline style objects `style={{ padding: 16 }}` — creates new object every render; use `StyleSheet.create`
323
+ 3. Missing `keyExtractor` — React Native warns and degrades diff performance
324
+ 4. `Dimensions.get('window')` in render — doesn't update on rotation; use `useWindowDimensions`
325
+ 5. `activeOpacity` omitted — default `0.2` makes buttons feel unresponsive
326
+ 6. `onPress` without `hitSlop` on small icons — nearly impossible to tap accurately
327
+ 7. Storing server state in Zustand manually — use TanStack Query (handles stale/loading/error)
328
+ 8. `console.log` in production — remove or use a logger that strips in release builds
329
+ 9. No `accessibilityLabel` on icon buttons — VoiceOver announces nothing useful
330
+ 10. Expo Go for production testing — use EAS Build; Expo Go skips native build steps
331
+
332
+ ---
333
+
334
+ ## Performance Checklist
335
+
336
+ - [ ] `FlatList` for all lists (never `ScrollView` + `.map`)
337
+ - [ ] `StyleSheet.create` for all styles
338
+ - [ ] `keyExtractor` returning stable unique string (not index)
339
+ - [ ] `useWindowDimensions` not `Dimensions.get` in render
340
+ - [ ] `React.memo` on `renderItem` components
341
+ - [ ] `useCallback` wrapping `renderItem` and event handlers
342
+ - [ ] Hermes engine enabled (default in Expo SDK 48+)
343
+ - [ ] `react-native-reanimated` for smooth 60fps animations (runs on UI thread)
344
+ - [ ] EAS Build for production — not Expo Go
345
+ - [ ] `getItemLayout` for fixed-height list items
346
+ - [ ] MMKV for persisted state (not AsyncStorage)