navigation-and-routing
Builds and checks Expo Router routes and layouts against a contract: typed hrefs, params, protected flows, modals, back guards.
Skill by zakariaf
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/zakariaf-navigation-and-routing
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: documents
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
- Runs as
- Node.js package
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/navigation-and-routing
What's inside
- Skill name
navigation-and-routing- Description
- Builds and checks Expo Router routes and layouts against a contract: typed hrefs, params, protected flows, modals, back guards. Use when adding a route, link or guard, or Back acts up. Not for deep links (deep-links-and-universal-links).
- License
- MIT
- Instructions
- 204 lines in SKILL.md · view on GitHub
Before installing
- Ships executable scripts
Files (67)
- SKILL.md
- shared.json · assets/
- placeholder-screen.tsx · examples/
- debug-route.tsx · examples/react-navigation/
- navigation-root.tsx · examples/react-navigation/
- navigation.json · examples/react-navigation/
- react-navigation.d.ts · examples/react-navigation/
- root-stack.tsx · examples/react-navigation/
- route-guards.ts · examples/react-navigation/
- route-params.ts · examples/react-navigation/
- use-task-editor-model.ts · examples/react-navigation/
- contract-and-checker.md · references/
- expo-router-in-practice.md · references/
- react-navigation-static.md · references/
- check-lib.mjs · scripts/
- check-navigation.mjs · scripts/
- object-literal.mjs · scripts/lib/
- source-scan.mjs · scripts/lib/
- selftest.mjs · scripts/
- navigation.json · templates/contracts/
- navigation.schema.json · templates/contracts/
- +not-found.tsx · templates/src/app/
- not-found-navigation.test.tsx · templates/src/features/not-found/
- not-found-screen.test.tsx · templates/src/features/not-found/
- not-found-screen.tsx · templates/src/features/not-found/
- use-not-found-model.test.tsx · templates/src/features/not-found/
- use-not-found-model.ts · templates/src/features/not-found/
- protected-routes.test.tsx · templates/src/features/root-navigator/
- task-detail-params.test.ts · templates/src/features/task-detail/
- task-detail-params.ts · templates/src/features/task-detail/
- use-discard-guard.android.test.tsx · templates/src/features/task-editor/
- use-discard-guard.test.tsx · templates/src/features/task-editor/
- use-discard-guard.ts · templates/src/features/task-editor/
- ar.json · templates/src/i18n/catalogs/
- en.json · templates/src/i18n/catalogs/
- app.config.ts · tests/bases/app/
- navigation.json · tests/bases/app/contracts/
- package.json · tests/bases/app/
- _layout.tsx · tests/bases/app/src/app/(app)/
- compose.tsx · tests/bases/app/src/app/(app)/
- index.tsx · tests/bases/app/src/app/(app)/
- [taskId].tsx · tests/bases/app/src/app/(app)/tasks/
- edit.tsx · tests/bases/app/src/app/(app)/tasks/[taskId]/
- _layout.tsx · tests/bases/app/src/app/
- debug.tsx · tests/bases/app/src/app/
- sign-in.tsx · tests/bases/app/src/app/
- test-build.ts · tests/bases/app/src/features/debug/
- app-root.tsx · tests/bases/app/src/features/root-navigator/
- app-stack.tsx · tests/bases/app/src/features/root-navigator/
- root-stack.tsx · tests/bases/app/src/features/root-navigator/
- use-task-editor-model.ts · tests/bases/app/src/features/task-editor/
- use-tasks-model.ts · tests/bases/app/src/features/tasks/
- session-selectors.ts · tests/bases/app/src/stores/session/
- task-row.tsx · tests/bases/app/src/ui/
- tsconfig.json · tests/bases/app/
- package.json · tests/bases/react-navigation/
- use-home-model.ts · tests/bases/react-navigation/src/features/home/
- task-row.tsx · tests/bases/react-navigation/src/ui/
- tsconfig.json · tests/bases/react-navigation/
- CASES.json · tests/fixtures/expo-router/
- …and 7 more
SKILL.md (first lines)
# Navigation and routing
An Expo app has one navigation tree, built by Expo Router from the files in `src/app/`, whose routes
equal a repo-owned route contract (`contracts/navigation.json`): flows switch by guards, hrefs and
params are checked, screens with unsaved work cannot be lost by a stray Back, and every URL lands
somewhere. React Navigation 7's static API is the fenced alternative. A script proves it.
## Rules that must hold
1. **One router, one tree: Expo Router's.** The root `src/app/_layout.tsx` default-exports a layout
that renders the root navigator, directly or through the components it renders (app-startup-and-bootstrap's
root skeleton: `AppRoot` > `RootStack` > `AppStack`). No `NavigationContainer`,
`createStaticNavigation` or `@react-navigation/*` import beside it: expo-router 57 carries its own
React Navigation, and a second copy has its own contexts and its own back stack. (`root-layout`,
`one-navigator`)
2. **The contract is the route table.** Every route file (route id = its path under `src/app` without
the extension) is in `contracts/navigation.json` `routes`, and every contract route has a file;
`home` names the first screen. A route change starts in the contract, so a stray file (Expo Router
bundles everything in `src/app`) or a forgotten screen is caught. (`contract`, `route-table`,
`home-route`)
3. **Flows switch by guards, never by navigating.** Signed out / signed in, onboarding / main and
test-build-only screens are `Stack.Protected` groups whose `guard` reads a store selector, each in
the contract (`protected`). No `<Redirect>` in a layout and no `router.*` call from a layout's
effect: they render the wrong screen first and leave it in the history, while a guard turning false
removes its screens from the history. Guards hide screens; the server still checks data.
(`protected-guard`, `flow-by-guard`)
4. **Hrefs are typed.** `experiments.typedRoutes` is on (CI generates the types with
`npx expo customize tsconfig.json` before `tsc`), and app code never writes `as Href`: a renamed
route then fails `tsc` wherever it is linked. (`typed-routes`, `href-cast`)
5. **Params are parsed at the edge.** Any URL can open a dynamic route with any text, and
`useLocalSearchParams<T>()` is a cast. Each dynamic route has a valibot parser returning a `Result`
(`paramParsers`), the model shows the screen's not-found state on failure, and params stay ids and
short plain values. (`params-parsed`)
6. **A screen with unsaved work blocks its removal.** Its model hook calls `usePreventRemove` (the
template's `useDiscardGuard`), which sees the header back button, the iOS swipe, a sheet's swipe
down, Android's back button and predictive back gesture, and `router.back()`; Back asks before
discarding. The contract lists it in `guarded`. Turn the iOS swipe off (`gestureEnabled` and
`fullScreenGestureEnabled` false, `"gesture": "off"`) only where starting it already harms. The
guard cannot see a kill: save drafts as they change. (`guarded-prevent-remove`,
`guarded-gesture-off`)
7. **Going back never stacks a second home.** `router.navigate` and `push` (and `<Link>`) push, so the
home route is reached with `router.dismissTo(home)` or `router.back()` (`<Link dismissTo>`).
(`go-back`)
8. **A destination is a route; a layer over a live screen is not.** Modal destinations are routes with
a modal `presentation`, exactly the contract's `modals` (on Android `modal` opens like a push,
`formSheet` is a bottom sheet). Confirmations, the discard dialog and a pause layer render inside
the screen or the dialog host, because a route change would cover or unmount what they sit on.
(`modal-presentation`)
9. **Every URL lands somewhere.** `src/app/+not-found.tsx` renders for an unknown URL (without it the
router shows its development page); its one action is `dismissTo('/')`, so Back cannot return to
the dead link, and it never prints the URL. (`not-found`)
10. **The native stack.** `Stack` from `expo-router` (react-native-screens): the platform draws the
transitions, swipe and header. The JavaScript stack only with `"jsStack": true` and a reason.
Every `<Stack>` takes its animation from motion-and-animation's `useScreenAnimation()`.
(`native-stack`)
11. **Navigation lives in screens' model hooks.** `src/ui`, `src/stores`, `src/services`, `src/data`,
`src/api` and `src/core` never import a router: components take `onPress`, stores change state.
The one exception is a root kit (`src/services/<concern>/<concern>-root-kit.tsx`, registered in
app-startup-and-bootstrap's `createRootKits`), whose effects route notification taps beside the
root Stack. Navigation state is never persisted; an open editor is reopened from its saved draft.
(`nav-in-screens`)
12. **Debug routes are gated and guarded.** A test-build screen renders only through `TEST_ONLY`
(debug-menu-and-test-hooks' gate, so production bundles drop it) and sits in a protected group,
so a production deep link cannot open it. (`debug-gated`)
13. **Placeholders are listed and never ship.** While screens are built one by one, an unbuilt route
renders the placeholder screen, so the route table, its typed hrefs and every model hook's
navigation exist from day one (no no-op handlers); the contract lists them and `--release` fails
while any is left. (`placeholder-route`, `placeholder-release`)
14. **With React Navigation 7 instead (appendix):** one static config whose groups (`if` hooks) equal
the contract's `groups`, one `createStaticNavigation` container (never `<NavigationContainer>`),
`RootParamList` from `StaticParamList`, plain-data params, `popTo` to go back, no `reset()` to
switch flows, and the stack wrapped with `.with()` so pushes cross-fade under reduce motion.
(`group-if`, `static-container`, `typed-routes`, `params-serialisable`, `flow-by-guard`,
`motion-transition`)
## Workflow
1. **Read what the task needs** in [references/expo-router-in-practice.md](references/expo-router-in-practice.md):
| Task | Section |
|---|---|
| first setup, the root layout, reopening work after a kill | "The root layout and the startup order" |
| a new screen, a tab, a header | "File routes", "Layouts: one navigator tree" |
| a link or a param | "Typed routes and hrefs", "Params: parse them at the edge" |
| sign-in, onboarding, test-only screens | "Flows: Stack.Protected, never a redirect", "Debug routes and placeholders" |
| a modal, a sheet, a dialog | "Modals and what is not a route" |
| Back, the swipe, Android back, unsaved changes | "Back handling (iOS and Android)", "Going back to a screen already in the stack" |
| a route test | "Testing navigation" |
With React Navigation instead of Expo Router, read [references/react-navigation-static.md](references/react-navigation-static.md)
and imitate `examples/react-navigation/`.
2. **Set up once.** Copy `templates/contracts/navigation.json` (and its schema) and list the app's
routes; copy `templates/src/app/+not-found.tsx` with `templates/src/features/not-found/` and add the
`not-found.*` keys of `templates/src/i18n/catalogs/` to every catalog (i18n-strings-and-catalogs).
Turn on `experiments.typedRoutes` in `app.config.ts`. The root layout and `AppStack` come from
app-startup-and-bootstrap's root skeleton; add the root `Stack.Protected` groups inside `AppStack`.
3. **Add a route:** add its id to the contract, create the thin route file rendering the feature's
screen (architecture-and-boundaries), add its `Stack.Screen` in the layout only for options (a
title, `presentation`), and put its navigation calls in the feature's model hook.
4. **A dynamic route:** write `src/features/<feature>/<feature>-params.ts` after
`templates/src/features/task-detail/task-detail-params.ts` (with its test), map it in
`paramParsers`, and render the not-found state on a parse failure.
5. **A flow:** wrap its screens in `<Stack.Protected guard={...}>` fed by a store selector, add the
group to `protected`, and test it the way `templates/src/features/root-navigator/protected-routes.test.tsx`
does (copy that test when the app adds its first flow; it also pins the router facts).
6. **A screen with unsaved work:** copy `templates/src/features/task-editor/use-discard-guard.ts` with
both tests, call it from the model hook with the "unsaved" boolean, render the dialog while
`isPromptOpen`, leave after a save through `leave()`, and list the route in `guarded`.
7. **Run the tests**, paths first (`--selectProjects` takes every following word as a project):
`npx jest src/features/not-found src/features/root-navigator src/features/task-editor src/features/task-detail --selectProjects unit android`,
then `npm run typecheck` and lint as quality-gates requires.
8. **Run the checker:** `node ${CLAUDE_SKILL_DIR}/scripts/check-navigation.mjs .`; fix every `FAIL`
line (file, rule, fix) and rerun until `RESULT: PASS`. Rules and contract keys:
[references/contract-and-checker.md](references/contract-and-checker.md). Before a release, run it
with `--release`.
## Definition of done
- [ ] `contracts/navigation.json` lists exactly the app's routes, `home`, every protected group, everyFrom zakariaf/ReactNative-Skills, MIT.
More from zakariaf/ReactNative-Skills
- 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
architecture-and-boundaries
zakariaf
Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
async-safety
zakariaf
Prevents silent async bugs: effect races, stale results, lost promises, leaked listeners and timers, AbortSignal gaps.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
audio-and-haptics-feedback
zakariaf
Builds SFX and haptics behind ports.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
auth-and-secure-storage
zakariaf
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
background-tasks
zakariaf
Builds expo-background-task jobs: idempotent, budgeted runs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9
Signals
- Stars
- 11
- Forks
- 2
- Open issues
- 0
- Last commit (this folder)
- Oct 17, 2026 (11 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/zakariaf/ReactNative-Skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from zakariaf
- 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9
Cite this page
navigation-and-routing skill. Agent Mart. https://agentmart.dev/skills/zakariaf-navigation-and-routing (last updated 2026-10-24)