adaptive-layout
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
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-adaptive-layout
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
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/adaptive-layout
What's inside
- Skill name
adaptive-layout- Description
- Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids. Use when making screens responsive or for tablets, iPad, foldables, rotation, split screen, orientation lock. Not for tab bars (navigation-and-routing).
- License
- MIT
- Instructions
- 213 lines in SKILL.md · view on GitHub
Before installing
- Ships executable scripts
Files (45)
- SKILL.md
- shared.json · assets/
- task-board.tsx · examples/
- tasks-list-detail.md · examples/
- list-detail-and-navigation.md · references/
- platforms-orientation-and-resizing.md · references/
- testing-and-checker.md · references/
- window-classes-and-panes.md · references/
- check-adaptive-layout.mjs · scripts/
- check-lib.mjs · scripts/
- source-scan.mjs · scripts/lib/
- selftest.mjs · scripts/
- adaptive-layout.json · templates/contracts/
- adaptive-layout.schema.json · templates/contracts/
- use-window-class.test.ts · templates/src/hooks/
- use-window-class.ts · templates/src/hooks/
- list-detail-layout.test.tsx · templates/src/ui/adaptive/
- list-detail-layout.tsx · templates/src/ui/adaptive/
- pane-layout.test.ts · templates/src/ui/adaptive/
- pane-layout.ts · templates/src/ui/adaptive/
- use-list-detail-layout.test.tsx · templates/src/ui/adaptive/
- use-list-detail-layout.ts · templates/src/ui/adaptive/
- use-measured-width.test.ts · templates/src/ui/adaptive/
- use-measured-width.ts · templates/src/ui/adaptive/
- app.config.ts · tests/bases/app/
- adaptive-layout.json · tests/bases/app/contracts/
- package.json · tests/bases/app/
- with-task-widgets.js · tests/bases/app/plugins/
- _layout.tsx · tests/bases/app/src/app/(app)/
- [id].tsx · tests/bases/app/src/app/(app)/tasks/
- index.tsx · tests/bases/app/src/app/(app)/tasks/
- capture-screen.tsx · tests/bases/app/src/features/attachments/
- settings-screen.tsx · tests/bases/app/src/features/settings/
- task-board.tsx · tests/bases/app/src/features/tasks/components/
- task-detail.tsx · tests/bases/app/src/features/tasks/components/
- task-row.tsx · tests/bases/app/src/features/tasks/components/
- task-list.ts · tests/bases/app/src/features/tasks/
- tasks-screen.tsx · tests/bases/app/src/features/tasks/
- use-tasks-model.ts · tests/bases/app/src/features/tasks/
- tokens.ts · tests/bases/app/src/theme/
- screen-frame.tsx · tests/bases/app/src/ui/
- tsconfig.json · tests/bases/app/
- CASES.json · tests/fixtures/check-adaptive-layout/
- SUITE.json · tests/fixtures/check-adaptive-layout/
- EXPECT.txt · tests/fixtures/check-adaptive-layout/good/
SKILL.md (first lines)
# Adaptive layout
Every screen lays out for the window the app has now, on iOS and Android: one window classifier,
breakpoints in one module, a readable column on wide windows, a list beside its detail when both
fit in the box the panes get, grids sized from their own box, no orientation lock or resizing
opt-out the platforms ignore anyway, and a selection that lives in the route so rotation, resizing
and links keep it. A zero-dependency checker proves what a reviewer would otherwise check by eye.
## Rules that must hold
1. **Layout decides by the window, never the device.** No `Platform.isPad`, `interfaceIdiom`,
expo-device `DeviceType`, `isTablet()` or `isPhone` helper picks a layout, and no
`getOrientationAsync` or orientation listener shapes one: read `useWindowClass()` (and its
`isLandscape`, the window's shape). Why: iPad Split View, Slide Over and Stage Manager, Android
multi-window and freeform windows, and a foldable opening all resize the app while it runs; an
iPad in a third of the screen is a compact, portrait-shaped window. A hardware capability in
`src/services/` (no haptic engine on iPad) is not a layout decision; the rule reads UI code only.
(`device-branch`, `device-orientation`)
2. **One window classifier, never forked.** `src/hooks/use-window-class.ts` (react-components-and-hooks
owns it) gives `{ width: 'compact' | 'regular' (600), isLandscape, isLargeText (1.25) }`; no second
`useBreakpoint`, `useIsTablet` or `REGULAR_WIDTH_MIN` elsewhere. Why: two classifiers disagree at the
boundary and screens stop agreeing on what "regular" means. (`window-class-fork`)
3. **Breakpoints beyond the class live in `src/ui/adaptive/pane-layout.ts`, as content rules.** A
width or height compared with a number of 300 or more anywhere else fails; a new rule is a pure
function ("two panes when the list at 320 and the detail at 400 fit") tested at the window table.
Why: a number in a screen drifts from every other screen's, and a content rule explains itself.
(`raw-breakpoint`)
4. **Every screen caps its reading width at `CONTENT_MAX_WIDTH`.** Through `ScreenFrame`,
`ListDetailLayout`, or a list's `contentContainerStyle` (`width: '100%', maxWidth:
CONTENT_MAX_WIDTH, alignSelf: 'center'`); a canvas, map, camera or video screen is listed in
`contracts/adaptive-layout.json` `fullBleedScreens` with its reason; no hand-picked `maxWidth: 720`.
A screen in a `contracts/architecture.json` `testOnlyFolders` folder (a capture harness, a test
build's screen) is not judged: it never ships in a store build.
Why: on a 13-inch iPad in landscape an uncapped line runs 1,300 pt wide. (`readable-width`,
`readable-width-token`)
5. **No fixed width wider than the narrowest window allows (288).** Regions size by `flex`,
percentages, the pane layout's `listWidth` or a measured container. Why: a 320 pt Split View pane
or small phone minus two 16 pt gutters leaves 288; anything wider scrolls sideways or clips.
(`fixed-width`)
6. **A list and its detail share one screen through `ListDetailLayout`, sized by its own box.** The
pane count comes from `useListDetailLayout()`: one pane on a compact window; on a regular one, two
panes only when the list, a hairline and the detail minimum fit in the width `ListDetailLayout`
measures inside the screen's `SafeAreaView` (721 pt, 801 pt with large text; before the first
layout, the window minus the side insets). Each screen in `listDetailScreens` renders it with
`panes={model.panes}`. Why: a regular window is not always wide enough (a half-width iPad Split
View is 678 pt; an iPad mini beside an 80 pt side rail leaves 664 pt; a form sheet is narrower
still), and the notch side is not usable space. (`list-detail-screens`)
7. **The selection is the route's, and each route owns its param.** The list route carries
`?selected=<id>`, the detail route is `[id]`, both render the same screen and each reads only its
own param; selecting calls `router.push` on one pane, and on two panes `router.setParams` with the
param the current route owns (`{ selected }` on the list route, `{ id }` on the detail route);
nothing navigates when the window changes; no `useState` selection. Why: a rotation, a resize, a
restored route and a link all land on the same item, back never walks through every tapped row,
and `/tasks/t2?selected=t3` would leave the detail pane on `t2`. (`list-detail-selection`)
8. **Grid columns come from the grid's own box, and a new count remounts the list.**
`useMeasuredWidth()` + `gridColumnsOf({ width, minCellWidth, gap, maxColumns })`, and
``key={`grid-${columns}`}`` on the `FlatList`. Why: inside a pane the window is far wider than the
grid, and React Native throws "Changing numColumns on the fly is not supported" on the first
rotation. (`grid-columns-source`, `grid-columns-key`)
9. **Orientation is not locked, except where the contract says why.** The app config sets
`orientation: 'portrait'` (a phone-only lock in practice) or `'default'`; `'landscape'` needs
`appOrientation: 'landscape'` in the contract. A per-screen lock (`lockAsync`, a Stack screen's
`orientation` option) sits only in an `orientationLocks` file with its reason, and every
`lockAsync` has its `unlockAsync`; expo-screen-orientation's `initialOrientation` lock needs the
matching `appOrientation`. Why: iPads with multitasking and Android displays 600 dp and wider (apps
targeting API 36) ignore locks, so the layout must handle both shapes anyway.
(`orientation-lock`, `orientation-unlock`, `config-orientation`)
10. **The app never opts out of resizing, and supports tablets.** No `requireFullScreen: true` or
`UIRequiresFullScreen` (unless `UIRequiresFullScreenIgnoredStartingWithVersion` of 27 or lower,
usually `'26'`, limits it to older iOS), no `resizeableActivity`, aspect-ratio limits or
`PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY` in the config or a plugin, not even set to the
default (TN3192 asks apps to drop the key; the checker calls that redundant); `ios.supportsTablet:
true` unless the contract says `tablets: false` with a reason. Why: Apple deprecated full screen in
iPadOS 26 and ignores it for apps built with the iOS 27 SDK; Android 16 ignores the opt-outs on
large displays and the last one stops working at API 37; a phone-only app runs in a scaled frame
on iPad. (`full-screen-opt-out`, `tablet-support`)
11. **Insets are physical and change with the window.** Rotation moves the notch or cutout to a side;
take both sides (or `SafeAreaView` edges), never `paddingStart: insets.left`, and never count the
notch side as pane width (measure inside the edges; subtract the side insets from a window).
Why: the cutout does not mirror in RTL, and a detail pane under the notch is unusable.
(react-components-and-hooks' `inset-direction`; the pane tests here)
12. **Every layout decision is a pure function tested at the window table, at `fontScale` 1 and 2.**
320 × 568, 360 × 800, 402 × 874, 874 × 402 (62 pt side insets), 678 × 1032, 744 × 1133,
800 × 1280, 1376 × 1032; screenshots on named devices at the same sizes. Why: Jest lays nothing
out, and adaptive bugs live at the size nobody opened.
## Workflow
1. Read [references/window-classes-and-panes.md](references/window-classes-and-panes.md): window
versus device, the classifier, the window table, the pane rule, readable widths, grids, fixed
widths, large text, and the decisions with their reasons.
2. **First time in an app.** Make sure `src/hooks/use-window-class.ts` exists (react-components-and-hooks
ships it; this skill's synced copy is the same file). Copy `templates/src/ui/adaptive/` to
`src/ui/adaptive/` with every test, and `templates/contracts/adaptive-layout.json` plus its schema
to `contracts/`. The kit imports design-system-structure's `@/theme/*`; the tests use
unit-and-component-tests' `@test/render-with-providers`.
3. **The app config.** Read [references/platforms-orientation-and-resizing.md](references/platforms-orientation-and-resizing.md)
sections 1-3: keep `orientation: 'portrait'` or `'default'`, set `ios.supportsTablet: true`, remove
every resizing opt-out, and record a deliberate profile (landscape app, no iPad layout yet) in the
contract (section 8).
4. **A screen.** Put it in `ScreenFrame` (or cap its list's `contentContainerStyle`); a full-bleed
screen goes into `fullBleedScreens` with its reason. Decide variations from `useWindowClass()`;
if a decision needs a width, add a pure function to `pane-layout.ts` with window-table tests.
5. **A list and its detail.** Read [references/list-detail-and-navigation.md](references/list-detail-and-navigation.md)
and imitate [examples/tasks-list-detail.md](examples/tasks-list-detail.md): two thin routes over one
screen, the model hook reads the route's own param from `useLocalSearchParams()` and the panes from
`useListDetailLayout()`, `onSelect` sets the current route's param on two panes, the screen renders
`<ListDetailLayout panes={model.panes}>` in a `SafeAreaView` with the named edges. Add the screen
to `listDetailScreens`, and the example's route tests (both routes, a measured box).
6. **A grid.** Imitate [examples/task-board.tsx](examples/task-board.tsx): measure the container,
`gridColumnsOf` and `gridCellWidthOf`, render nothing until the first layout, key the list by the
column count.
7. **An orientation lock** (camera, full-screen video): platforms reference section 5. Lock in an
effect whose cleanup unlocks, list the file in `orientationLocks` with its reason, and still lay the
screen out for both shapes.
8. **Tests first.** Follow [references/testing-and-checker.md](references/testing-and-checker.md)
sections 1-2: window-table tables at `fontScale` 1 and 2, the `useWindowDimensions` mock (Jest's
default `fontScale` is 2), `SafeAreaProvider` with `initialMetrics`, a fired layout event, the
crossing test. Route tests use the router helper as in the example.From 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
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
ci-pipeline-and-gates
zakariaf
Builds GitHub Actions CI for Expo apps - verify on every PR, SHA-pinned actions, pinned runners and Node, device lanes, manual EAS releases.
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
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
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
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
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
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
Cite this page
adaptive-layout skill. Agent Mart. https://agentmart.dev/skills/zakariaf-adaptive-layout (last updated 2026-10-24)