Elements
Input
<Input> is a native text field: a UITextField (single line) or UITextView (multiline) on iOS and Mac Catalyst, an
EditText on Android. Typing, the caret, selection, autocorrection and autofill are the platform’s own.
import { Input, mount, signal, Text, View, type YNode } from "nsh";
function App() {
const name = signal("");
const password = signal("");
const notes = signal("");
const submitted = signal("");
let passwordInput!: YNode;
const field = {
fontSize: 16,
color: "#1f2328",
backgroundColor: "#ffffff",
borderWidth: 1,
borderColor: "#d0d7de",
borderRadius: 10,
paddingHorizontal: 12,
paddingVertical: 10,
} as const;
return (
<View style={{ flex: 1, backgroundColor: "#f6f8fa", padding: 20, paddingTop: 24, gap: 10 }}>
<Text style={{ fontSize: 22, fontWeight: "700", color: "#1f2328" }}>input</Text>
<Text style={{ color: "#57606a" }}>Name (controlled, maxLength 20)</Text>
<Input
style={field}
value={name()}
onChangeText={(text) => name.set(text)}
placeholder="Ada Lovelace"
placeholderTextColor="#8c959f"
autoCapitalize="words"
maxLength={20}
returnKeyType="next"
onSubmitEditing={() => passwordInput.focus()}
/>
<Text style={{ fontSize: 12, color: "#6e7781" }}>{name().length} / 20</Text>
<Text style={{ color: "#57606a" }}>Password</Text>
<Input
ref={passwordInput}
style={field}
value={password()}
onChangeText={(text) => password.set(text)}
secureTextEntry
autoComplete="current-password"
returnKeyType="done"
onSubmitEditing={() => submitted.set(`Signed in as ${name() || "nobody"}`)}
/>
<Text style={{ color: "#57606a" }}>Notes (multiline)</Text>
<Input
style={{ ...field, height: 100 }}
multiline
value={notes()}
onChangeText={(text) => notes.set(text)}
placeholder="Anything else?"
placeholderTextColor="#8c959f"
/>
<View
onPress={() => submitted.set(`Signed in as ${name() || "nobody"}`)}
activeOpacity={0.7}
disabled={password().length === 0}
class="disabled:opacity-40"
style={{ backgroundColor: "#1f883d", borderRadius: 10, padding: 14, alignItems: "center", marginTop: 6 }}
>
<Text style={{ color: "#ffffff", fontSize: 16, fontWeight: "600" }}>Sign in</Text>
</View>
<Text style={{ color: "#1a7f37" }}>{submitted()}</Text>
</View>
);
}
mount(App);import { Input, signal } from "nsh";
function NameField() {
const name = signal("");
return (
<Input
value={name()}
onChangeText={(text) => name.set(text)}
placeholder="Your name"
autoCapitalize="words"
returnKeyType="done"
style={{ fontSize: 16, padding: 12, borderWidth: 1, borderColor: "#d0d7de", borderRadius: 8 }}
/>
);
}
#Controlled and uncontrolled
- Controlled: pass
valueand update it inonChangeText. JS can rewrite what the user typed (trim it, format it): the text shows the edit at once and is corrected when the newvaluearrives. Each native edit carries a count, and avaluefrom JS is applied only if it is based on the latest edit, so a slow JS turn never overwrites newer typing. - Uncontrolled: pass
defaultValue(or nothing) and read the text fromonChangeTextoronSubmitEditing.
Rules that must hold while the user types are props, answered natively without asking JS: maxLength limits the length
and blurOnSubmit decides what return does. See forms and the keyboard.
#Props
Besides the common props (with style typed as TextStyle: font, color, alignment and the
view styles apply):
#Text
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | The text, controlled by JS. | |
defaultValue | string | The initial text. | |
placeholder | string | Shown while the input is empty. | |
placeholderTextColor | ColorValue | The placeholder’s color. | |
multiline | boolean | false | Several lines; return inserts a line break. |
maxLength | number | The most characters the user can enter. | |
editable | boolean | true | The user can change the text. |
secureTextEntry | boolean | false | The text is hidden (passwords). |
allowFontScaling | boolean | true | Follow the system text size. |
maxFontSizeMultiplier | number | 0 | The upper bound of the system scale (0 is none). |
#Keyboard
| Prop | Type | Default | Description |
|---|---|---|---|
keyboardType | "default" | "number-pad" | "numeric" | "decimal-pad" | "email-address" | "phone-pad" | "url" | "default" | The keyboard layout. |
returnKeyType | "default" | "done" | "go" | "next" | "search" | "send" | "default" | The label of the return key. |
autoCapitalize | "none" | "sentences" | "words" | "characters" | "sentences" | Automatic capitalization. |
autoCorrect | boolean | true | The system’s corrections. |
autoComplete | AutoComplete | TextContentType | What the input holds, for autofill: "username", "current-password", "new-password", "one-time-code", "email", "tel", "postal-code", "cc-number", "off", … | |
blurOnSubmit | boolean | true | Return unfocuses a single-line input; false keeps the keyboard up (e.g. to send several messages). |
autoFocus | boolean | false | Focused when it appears. |
#Caret and selection
| Prop | Type | Description |
|---|---|---|
caretHidden | boolean | No caret is drawn. |
selectionColor | ColorValue | The color of the caret and the selection. |
selection | { start: number; end?: number } | The controlled selection, in UTF-16 units; end defaults to start (a caret). |
#iOS
ios key | Type | Description |
|---|---|---|
textContentType | AutoComplete | TextContentType | iOS’s names for autoComplete ("oneTimeCode", "newPassword", …). |
inputAccessoryViewID | string | The nativeID of an InputAccessoryView (nsh/ios) shown over the keyboard while the input has focus. |
<Input autoComplete="one-time-code" keyboardType="number-pad" ios={{ textContentType: "oneTimeCode" }} />
#Events
| Event | Payload | When |
|---|---|---|
onChangeText | (text: string) | The user changed the text. |
onSubmitEditing | { text } | The return key was pressed (single line). |
onFocus / onBlur | none | The input gained / lost focus. |
onSelectionChange | { start, end } | The caret or selection moved (UTF-16 units). |
onKeyPress | { key } | Before the text changes: key is the typed text, "Backspace" or "Enter". |
onContentSizeChange | { width, height } | The size of the text changed (at the input’s width when multiline). |
A multiline input that grows with its text:
const height = signal(40);
<Input
multiline
onContentSizeChange={(e) => height.set(Math.min(120, Math.max(40, e.height + 16)))}
style={{ height: height(), paddingHorizontal: 12, paddingVertical: 8 }}
/>;
#Ref methods
| Method | Description |
|---|---|
focus() | Focuses the input and shows the keyboard. |
blur() | Unfocuses it. |
setSelection(start, end?) | Selects start..end (UTF-16 units); a caret without end. |
Moving focus to the next field on return:
let password!: YNode;
<>
<Input returnKeyType="next" onSubmitEditing={() => password.focus()} />
<Input ref={password} secureTextEntry autoComplete="current-password" />
</>;
Keyboard.dismiss() from nsh/ui/mobile hides the keyboard whichever input has it.
#Platform notes
- Inputs inside a
<Scroll>are kept clear of the keyboard natively. Elsewhere useKeyboardAvoidingView. disableddisables the input as well as applyingdisabled:classes.inputAccessoryViewIDhas no effect on Android, which has no keyboard accessory bar.