# React Native CodePad
> A native code editor for React Native, with its own Kotlin and Swift engines, tree-sitter highlighting and a Nitro binding. No WebView.
This file contains all documentation content in a single document following the llmstxt.org standard.
## AI coding agents
CodePad ships an agent skill and a plain text copy of these docs, so a coding agent can add an editor to your app without guessing the API.
### Agent skill
The skill shows an agent how to install CodePad and set up `CodeEditor`, and lists the mistakes that come from treating the editor as a `TextInput`. Install it in your project with the `skills` command-line tool:
```sh
npx skills add SectionTN/react-native-codepad
```
The command prompts for the agents to set it up for. Agents that read the Agent Skills format, such as Claude Code, Codex and Cursor, load it when a task mentions a code editor, `CodeEditor` or a language package.
The skill covers installation, the props, ref methods and events, themes, search, the keyboard toolbar and an editor nested in a `ScrollView`. For anything else it sends the agent to `node_modules/react-native-codepad/src/types.ts`, which ships with the package and always matches the installed version.
Run `npx skills update` to get the latest version of the skill.
### Plain text docs
The site publishes the docs as plain text for agents and chat tools that read a URL:
- [`llms.txt`](https://reactnativecodepad.pages.dev/llms.txt) lists every page with a one-line description.
- [`llms-full.txt`](https://reactnativecodepad.pages.dev/llms-full.txt) contains every page in one file.
Give an agent the second URL when you want it to answer from the whole documentation.
---
## Hardware keyboard
CodePad handles the keys below from any hardware keyboard: Bluetooth or USB, a Chromebook, an iPad keyboard, or the Mac keyboard on the simulator. Every other key goes to the platform, and printable keys type through the normal path, with auto close and typing coalescing.
| Action | Android | iOS |
| --- | --- | --- |
| Move by one grapheme cluster | Left, Right | Left, Right |
| Move by one word | Ctrl+Left, Ctrl+Right | Option+Left, Option+Right |
| Move by one visual row, keeping the goal column | Up, Down | Up, Down |
| Move by one viewport height | Page Up, Page Down | Page Up, Page Down |
| Line start, line end | Home, End | Home, End, Cmd+Left, Cmd+Right |
| Document start, document end | Ctrl+Home, Ctrl+End | Cmd+Home, Cmd+End, Cmd+Up, Cmd+Down |
| Extend the selection | Shift with any of the above | Shift with any of the above |
| Newline through the indenter | Enter, numpad Enter | Enter |
| Indent the caret or the selected lines | Tab | Tab |
| Outdent the selected lines | Shift+Tab | Shift+Tab |
| Delete backward (an empty auto closed pair goes in one go) | Backspace | Backspace |
| Delete forward | Delete | Forward Delete (Fn+Delete on a Mac keyboard) |
| Undo | Ctrl+Z | Cmd+Z |
| Redo | Ctrl+Shift+Z, Ctrl+Y | Shift+Cmd+Z |
| Select all | Ctrl+A | Cmd+A |
| Copy, cut, paste | Ctrl+C, Ctrl+X, Ctrl+V, the dedicated keys | Cmd+C, Cmd+X, Cmd+V |
| Collapse the selection to the caret | Escape | Escape |
Under `readOnly`, movement, selection, select all, copy and Escape still work, and the editing keys do nothing. Word movement uses runs of `[A-Za-z0-9_]` and falls back to the platform's word breaks. A paste reports origin `input` in `onChange`, and undo and redo report `undo` and `redo`.
Android leaves Alt and Meta combinations and Ctrl+Tab to the platform. iOS leaves Control combinations, and Option with any key other than Left and Right, to UIKit. The editor's commands win over UIKit's own text navigation.
### The undo stack on iOS
The editor's history is the responder chain's `undoManager`, so Cmd+Z, shake to undo and the three finger gestures run the same undo groups as `undo()` and `redo()`. On Android the history is reached through the key table and the ref.
### The same table from JavaScript
`moveCaret(direction, { select })` drives the movement rows, so a toolbar can match the keyboard:
| Direction | Android | iOS |
| --- | --- | --- |
| `left`, `right` | Left, Right | Left, Right |
| `wordLeft`, `wordRight` | Ctrl+Left, Ctrl+Right | Option+Left, Option+Right |
| `lineStart`, `lineEnd` | Home, End | Home, End |
| `documentStart`, `documentEnd` | Ctrl+Home, Ctrl+End | Cmd+Home, Cmd+End |
| `up`, `down` | Up, Down | Up, Down |
| `pageUp`, `pageDown` | Page Up, Page Down | Page Up, Page Down |
`typeText('\n')`, `indent()`, `outdent()`, `deleteBackward()` and `deleteForward()` cover Enter, Tab, Shift+Tab, Backspace and Delete. The [keyboard guide](/guides/keyboard/) has the toolbar recipe.
### Testing without a keyboard
On an Android emulator with `hw.keyboard=yes` the host keyboard types into the editor, and `adb shell input keyevent` sends keys from a script. On the iOS simulator the Mac keyboard types into the editor, and with Connect Hardware Keyboard on, Cmd and Option chords reach the table.
---
## Keyboard
Three things keep the caret visible while the soft keyboard is up: `avoidKeyboard`, `contentInsets` for anything you draw over the editor, and a toolbar pinned to the keyboard. The example app's key row uses all three.
### `avoidKeyboard`
```tsx
```
The editor measures how much the keyboard overlaps it on every frame of the keyboard animation and adds that to its bottom inset, so the content slides up with the keyboard and back down when it closes. A keyboard opened for another field insets the editor too.
On Android the prop does nothing in two window modes, on purpose:
- `adjustPan`: the framework pans the window, and an inset would move the content twice.
- `adjustResize` without edge to edge: the window already shrank. React Native is edge to edge since 0.81.
`onContentSizeChange` never includes the keyboard overlap. The prop is off by default on both platforms.
### `contentInsets`
Style padding on the view is ignored. `contentInsets` is the padding instead: inside the scrollable area, in dp on Android and points on iOS. A bottom inset keeps the caret above a toolbar or a floating button.
```tsx
// Hoisted: object props are compared by identity.
const INSETS = { bottom: 56 };
;
```
With `avoidKeyboard` on, the keyboard overlap is added on top of `contentInsets.bottom`. You pass the toolbar height, the editor adds the keyboard.
### A toolbar on the keyboard
For a toolbar that rides the keyboard, use [react-native-keyboard-controller](https://kirillzyusko.github.io/react-native-keyboard-controller/). Its `KeyboardStickyView` keeps the bar on top of the keyboard through the whole animation. The example app pins its key row and status bar that way: the editor takes the bar's height as its bottom inset, and while the keyboard is closed the bar is above the home indicator, so the inset covers that band too.
```tsx
import { useMemo, useRef, useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { CodeEditor, type CodeEditorRef } from 'react-native-codepad';
import { KeyboardStickyView, useKeyboardState } from 'react-native-keyboard-controller';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { EditorToolbar } from './EditorToolbar';
export function ToolbarScreen() {
const editor = useRef(null);
const [toolbarHeight, setToolbarHeight] = useState(0);
const keyboardVisible = useKeyboardState((s) => s.isVisible);
const safe = useSafeAreaInsets();
// The editor adds the keyboard overlap itself; the home indicator only matters while the keyboard is closed.
const lift = keyboardVisible ? 0 : safe.bottom;
const insets = useMemo(() => ({ bottom: toolbarHeight + lift }), [toolbarHeight, lift]);
const offset = useMemo(() => ({ closed: -safe.bottom, opened: 0 }), [safe.bottom]);
return (
setToolbarHeight(e.nativeEvent.layout.height)}
/>
);
}
const styles = StyleSheet.create({
screen: { flex: 1 },
editor: { flex: 1 },
sticky: { position: 'absolute', left: 0, right: 0, bottom: 0 },
});
```
If you build the key row with a React Native `ScrollView`, set `keyboardShouldPersistTaps="always"` so tapping a key does not dismiss the keyboard. Each key calls the ref:
| Key | Call |
| --- | --- |
| Arrows, Home, End | `moveCaret(direction, { select })` |
| Tab, Untab | `indent()`, `outdent()` |
| Enter | `typeText('\n')` |
| Symbols such as `{` or `"` | `typeText(symbol)` |
| Backspace | `deleteBackward()` |
| Undo, Redo | `undo()`, `redo()`, enabled from `canUndo()` and `canRedo()` read in `onChange` |
| Hide | `blur()` |
`typeText()` and `deleteBackward()` take the keyboard path: auto close, the indenter, typing coalesced in the undo history, origin `input`, and no-ops under `readOnly`. `insertText()` is the raw path. The example's key row is in the repository under `example/src/components/AccessoryBar.tsx`, and `BottomBar.tsx` next to it pins it to the keyboard.
### Suggestions and focus
`suggestions` controls what the keyboard offers: `off` (the default) asks for no suggestions, `forceOff` also kills composition on most Android keyboards, and `on` allows autocorrect. Code wants `off`. A Markdown editor might want `on`. On iOS `forceOff` is the same as `off`. Composing keyboards (Japanese, Chinese) work in every mode.
`autoFocus` focuses the editor once the view is attached. `focus()` and `blur()` do it on demand, and `focus()` doesn't open the keyboard under `readOnly`.
---
## Languages
Each language is a compiled tree-sitter grammar plus the nvim-treesitter queries, shipped as its own npm package. The core has no language built in, so without a package every document is plain text.
### Packages and ids
| `language` | Package |
| --- | --- |
| `c` | `react-native-codepad-lang-c` |
| `css` | `react-native-codepad-lang-css` |
| `go` | `react-native-codepad-lang-go` |
| `html` | `react-native-codepad-lang-html` |
| `java` | `react-native-codepad-lang-java` |
| `javascript` | `react-native-codepad-lang-javascript` |
| `json` | `react-native-codepad-lang-json` |
| `kotlin` | `react-native-codepad-lang-kotlin` |
| `markdown` | `react-native-codepad-lang-markdown` |
| `python` | `react-native-codepad-lang-python` |
| `rust` | `react-native-codepad-lang-rust` |
| `swift` | `react-native-codepad-lang-swift` |
| `tsx` | `react-native-codepad-lang-tsx` |
| `typescript` | `react-native-codepad-lang-typescript` |
| `yaml` | `react-native-codepad-lang-yaml` |
Pass the id as `language`. The [language packages reference](/reference/languages/) lists each grammar's revision and capture names.
Use `markdown`, not `markdown_inline`: the inline grammar is reached through injection.
`Codepad.availableLanguages()` lists the installed packages:
```ts
import { Codepad } from 'react-native-codepad';
const installed = Codepad.availableLanguages();
// ['javascript', 'markdown', 'markdown_inline', ...]
```
### Plain text fallback
A `language` with no installed package renders as plain text in `foreground` and logs one warning. It doesn't throw. Switching `language` on a live editor reparses the document.
### Injections
A grammar's injection query hands ranges to another language: fenced blocks in Markdown, `