npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rn-inputforge

v0.5.2

Published

A production-ready React Native input/form system (bare CLI & Expo) — address/money/consent fields, toasts, bottom sheets, split rows, morph/haptics, density theming, autofill, wizard, drafts, and a full field kit. Pure JS/TS, no native modules.

Readme

rn-inputforge

A production-ready input & form system for React Native (bare CLI and Expo). Pure JS/TS — no native modules, works with npm or yarn.

One package for fields, validation, theming, motion, drafts, and form UX — so you don’t bolt together five libraries for a signup screen.


What's included

Everything ships from rn-inputforge. Use this map to find the right export.

Fields & controls

| You need… | Use | | --------- | --- | | Text / email / phone / password / search / number | FieldFlowInput | | Phone + country dial code | FieldFlowPhone | | OTP / PIN digits | FieldFlowOTP | | Card / phone / currency / date mask | FieldFlowMaskedInput | | Card number + expiry + CVC | FieldFlowCreditCard | | Date / time / datetime | FieldFlowDateTimePicker | | Checkbox / radio / switch | Checkbox, radio & switch | | Multi-value chips (skills, tags) | FieldFlowTags | | Single or multi select | FieldFlowDropdown | | Async search / typeahead | FieldFlowTypeahead | | File / image upload (pluggable picker) | FieldFlowUpload | | Star rating | FieldFlowRating | | Range slider | FieldFlowSlider | | +/- quantity stepper | FieldFlowNumberStepper | | Signature pad | FieldFlowSignature | | Plan / filter pill tabs | FieldFlowSegmented | | Structured address | FieldFlowAddress | | Money / percent | FieldFlowMoney · FieldFlowPercent | | Color swatches / avatar | FieldFlowColor · FieldFlowAvatarPicker | | Consent / scroll-to-accept terms | FieldFlowConsent · FieldFlowTermsGate | | Confirm-details review | FieldFlowReviewSummary | | Long bio / notes | DescriptionField | | Chat composer + attachments | ChatInput |

Form engine

| You need… | Use | | --------- | --- | | Full form state + submit | useFieldFlowForm | | Sync / async / cross-field rules | Validation · Zod | | Server / API field errors (422) | setErrors / setFieldError | | Localized validation messages | locale / messages | | Multi-step wizard | useFieldFlowSteps + FieldFlowStepper | | Warn before leaving dirty form | useFieldFlowDirtyGuard | | Show / hide / require fields by values | Conditional fields | | Repeatable rows (guests, line items) | useFieldFlowArray | | Resume unfinished forms | useFieldFlowDraft + FieldFlowDraftBanner | | Keyboard next / done across fields | useKeyboardManager + KeyboardAwareFormWrapper | | Friction analytics + a11y on failed submit | Form intelligence | | Autofill / password manager hints | autofill / autofillProfiles | | Keyboard Prev/Next/Done bar | FieldFlowKeyboardToolbar | | Smart paste (OTP / card / address) | routeSmartPaste | | “N fields left” progress coach | FieldFlowCompleteness | | Passkey / SSO / biometric strip | FieldFlowAuthActions | | JSON schema → form UI | FieldFlowSchemaForm | | Dev accessibility audit | FieldFlowA11yAudit | | Offline submit retry queue | useFieldFlowSubmitQueue | | Already on React Hook Form | FieldFlowController |

UI polish & branding

| You need… | Use | | --------- | --- | | Group fields in a card / collapse | FieldFlowSection | | Focus glow, error shake, success pop | motion on inputs · useFieldMotion | | +92 / $ / kg inside the field | prefix / suffix | | Email domain / dial-code chips | suggestions · FieldFlowAffordance | | Password meter | showStrength | | Look: outlined / filled / underline / floating | Variants | | Built-in icons (no icon pack) | Icons | | Brand colors in one line | themePresets · FieldFlowThemeProvider | | Density / size / shape / surface | density size shape surface focusRing | | Calm tips & alerts | FieldFlowInlineAlert · FieldFlowInfoTip | | Loading skeletons | FieldFlowSkeleton | | Form intro header | FieldFlowFormHeader | | Empty / no-results | FieldFlowEmptyState | | Toasts & banner stack | FieldFlowToastProvider · FieldFlowBannerStack | | Dividers / or-gate | FieldFlowDivider · FieldFlowOrGate | | Shared label / side-by-side | FieldFlowFieldGroup · FieldFlowSplitRow | | Bottom sheet field | FieldFlowBottomSheetField | | Error/success morph + float presets | morph · floatPreset | | Field haptics | haptics · useFieldFlowHaptics | | Per-field theme crossfade | themeCrossfade |

Helpers (also exported)

Masks (applyMask, unmask, …) · password strength (evaluatePasswordStrength) · affordance helpers · a11y (focusFirstError, announceForAccessibility) · validators · Zod adapters · FieldFlowIcon / renderFieldFlowIcon

Full walkthrough: Quick start · Try it · Public API


What's new in 0.5.2

| Addition | What you get | | -------- | ------------ | | EmptyState | Start-typing / empty / no-results surfaces | | Toast + BannerStack | Transient toasts + stacked form banners | | Divider / OrGate | “or continue with email” chrome | | FieldGroup / SplitRow | Shared labels + side-by-side layouts | | BottomSheetField | Reusable sheet for long pickers | | Morph + float presets | Error pulse, success morph, subtle / dramatic / material | | Haptics kit | Optional Vibration (or inject expo-haptics) | | Per-field theme crossfade | Soft opacity when a field theme changes | | Address / Money / Percent | Structured address + currency/% fields | | Color / AvatarPicker | Swatches + profile avatar chrome | | Consent / TermsGate | Linked legal checkbox + scroll-to-accept | | ReviewSummary | Read-only confirm step before submit |

What's new in 0.5.0

| Addition | What you get | | -------- | ------------ | | Smart Affordance | Email / phone / currency suggestion chips | | Form Intelligence | Friction report + autofocus / announce on failed submit | | Conditional fields | showWhen / requiredWhen / disabledWhen | | Field arrays | useFieldFlowArray | | Draft resume | useFieldFlowDraft + FieldFlowDraftBanner | | Password strength | showStrength / FieldFlowPasswordStrength | | Tags | FieldFlowTags | | React Hook Form | FieldFlowController / FieldFlowTagsController | | Motion UI | Glow, shake, fade, success pop (motion prop) | | Sections | FieldFlowSection | | Theme presets | themePresets.soft / warm / teal / midnight / contrast | | Prefix / suffix | Inline affixes on inputs | | Segmented | FieldFlowSegmented | | Multi-step wizard | useFieldFlowSteps + FieldFlowStepper | | Phone + country | FieldFlowPhone | | File upload | FieldFlowUpload (inject your picker) | | Typeahead | FieldFlowTypeahead (async fetchOptions) | | Server errors | setErrors / setFieldError / clearErrors | | Rating / slider / +/- | FieldFlowRating, FieldFlowSlider, FieldFlowNumberStepper | | Dirty leave guard | useFieldFlowDirtyGuard + isDirty | | i18n messages | locale / messages (en es fr de pt ar) | | Credit card group | FieldFlowCreditCard | | Signature pad | FieldFlowSignature | | Autofill profiles | autofill prop + autofillProfiles.login/signup/… | | Keyboard toolbar | FieldFlowKeyboardToolbar Prev/Next/Done | | Smart paste | routeSmartPaste / useSmartPaste | | Completeness coach | useFieldFlowCompleteness + FieldFlowCompleteness | | Auth actions | FieldFlowAuthActions (passkey / SSO / biometric slots) | | Schema form | FieldFlowSchemaForm JSON → UI | | A11y auditor | runA11yAudit + FieldFlowA11yAudit (dev) | | Submit queue | useFieldFlowSubmitQueue offline / retry | | Visual system | Density, size, shape, surface, focus rings | | Inline alert / skeleton / header | FieldFlowInlineAlert, FieldFlowSkeleton, FieldFlowFormHeader | | Info tips + helper status | infoTip, helperStatus, FieldFlowInfoTip | | Theme crossfade | Soft fade when switching presets (animateTheme) |

Still from 0.4.0: OTP, masks, date/time, checkbox/radio/switch, variants, icons, async + cross-field validation, Zod, Expo.

Note: since 0.4.0, validateField / validateAll return Promise<boolean>.


Installation

Pick whichever your project already uses — the package itself doesn't care.

# npm
npm install rn-inputforge

# yarn
yarn add rn-inputforge

That's it — this is a pure JavaScript/TypeScript package (no native code), so there is no pod install step and no manual linking required on bare React Native CLI. Same install works in Expo (Managed or Dev Client) — just npx expo install is not required for this package because it has no native dependencies; npm install rn-inputforge / yarn add rn-inputforge is enough.

Requirements

  • react >= 18 (tested against 19.2.x)
  • react-native >= 0.73 (tested against 0.86.x)
  • Works with Expo SDK that ships RN ≥ 0.73
  • TypeScript is optional but recommended (type defs are bundled)
  • zod is an optional peer dependency (only if you use the Zod helpers)
  • react-hook-form is an optional peer dependency (only if you use FieldFlowController)

Quick start

import { FieldFlowInput } from 'rn-inputforge';

function LoginForm() {
  const [email, setEmail] = useState('');

  return (
    <FieldFlowInput
      type="email"
      label="Email address"
      placeholder="[email protected]"
      value={email}
      onChangeText={setEmail}
      required
    />
  );
}

Setting type="email" automatically wires up the email keyboard, disables autocorrect/autocapitalize, and applies email-format validation on blur — no extra config needed. Same idea for number, decimal, phone, password, and search.


Field types

| type | Keyboard | Notes | | ------------- | -------------------- | ------------------------------------- | | text | default | sentence auto-capitalize | | email | email-address | auto validates email format | | number | numeric / number-pad | strips non-digits as you type | | decimal | decimal-pad | allows a single . | | phone | phone-pad | strips invalid phone characters | | password | default, masked | built-in show/hide toggle | | search | default | search icon + clear button by default | | multiline | default, multi-line | free-form multi-line text | | description | default, multi-line | used internally by DescriptionField | | chat | default | used internally by ChatInput |

Core FieldFlowInput props (cheat sheet)

| Prop | Purpose | | ---- | ------- | | type | Keyboard + defaults (email, password, phone, …) | | variant | outlined | filled | underline | floating | | label / helperText / errorText / successText | Copy + states | | rules / validateOnChange | Built-in validation | | leftIcon / rightIcon / iconSize / clearable | Icons (built-in name or custom node) | | prefix / suffix | Inline affix text or node | | suggestions | Smart chips (true, 'email', custom list) | | showStrength | Password strength meter | | motion | Micro-interactions (default true) | | maxLength / showCharacterCount | Limits + counter | | theme / style props | Per-field override |

All standard TextInput props still pass through (placeholder, returnKeyType, etc.).


Validation

Rules are declarative and reusable across the whole library:

<FieldFlowInput
  type="email"
  label="Email"
  value={email}
  onChangeText={setEmail}
  rules={{ required: 'Email is required', email: true }}
  validateOnChange
/>

Available rule keys: required, minLength, maxLength, pattern (RegExp), email, number, and custom (one or more (value: string) => { valid: boolean; message?: string } functions).

For a whole form at once, use useFieldFlowForm:

import { useFieldFlowForm, FieldFlowInput } from 'rn-inputforge';

const form = useFieldFlowForm({
  initialValues: { name: '', email: '' },
  rules: {
    name: { required: true, minLength: 2 },
    email: { required: true, email: true },
  },
  onSubmit: async values => {
    await api.createAccount(values);
  },
});

<FieldFlowInput
  label="Name"
  value={form.values.name}
  onChangeText={form.handleChange('name')}
  onBlur={form.handleBlur('name')}
  errorText={form.touched.name ? form.errors.name : undefined}
/>;

form.submit() validates every field (including async + cross-field rules) and only calls onSubmit if everything passes.

Cross-field & async validation

const form = useFieldFlowForm({
  initialValues: { password: '', confirm: '', email: '' },
  rules: {
    password: { required: true, minLength: 8 },
    confirm: { required: true },
    email: { required: true, email: true },
  },
  crossFieldRules: [
    {
      target: 'confirm',
      deps: ['password'],
      validate: v =>
        v.password === v.confirm
          ? { valid: true }
          : { valid: false, message: 'Passwords must match' },
    },
  ],
  asyncValidators: {
    email: async value => {
      const taken = await api.isEmailTaken(value);
      return taken
        ? { valid: false, message: 'Email is already taken' }
        : { valid: true };
    },
  },
});

// form.validating.email === true while the async check runs
// form.isValidating === true if any async validator is in flight

Note (0.4.0): validateField and validateAll are now async and return Promise<boolean>.

Zod (optional)

npm install zod
import { z } from 'zod';
import { useFieldFlowForm, zodFormValidate } from 'rn-inputforge';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

const form = useFieldFlowForm({
  initialValues: { email: '', password: '' },
  formValidate: zodFormValidate(schema),
  onSubmit: async values => api.login(values),
});

Also exported: zodToFormErrors, createZodSubmitGuard, fieldRulesFromZodString.


Try it (Expo / Snack)

  1. Create a blank Snack at snack.expo.dev.
  2. Add dependency rn-inputforge (and react-hook-form if you try the RHF adapter).
  3. Paste any snippet from this README or copy example/App.tsx from the repo.

Same install works in a local Expo app:

npx create-expo-app MyFormDemo
cd MyFormDemo
npx expo install rn-inputforge

Password strength

<FieldFlowInput
  type="password"
  label="Password"
  value={password}
  onChangeText={setPassword}
  showStrength
  // or: showStrength={{ showRules: true, rules: customRules }}
/>

// Standalone meter
<FieldFlowPasswordStrength password={password} />

Pure helper: evaluatePasswordStrength(password) → { level, progress, passed, failed }.


Tags / chip input

const [skills, setSkills] = useState<string[]>([]);

<FieldFlowTags
  label="Skills"
  value={skills}
  onChange={setSkills}
  maxTags={8}
  placeholder="Type a skill and press Enter"
  helperText="Comma or Enter to add"
/>

React Hook Form adapter

npm install react-hook-form
import { useForm } from 'react-hook-form';
import { FieldFlowController, FieldFlowTagsController } from 'rn-inputforge';

const { control, handleSubmit } = useForm({
  defaultValues: { email: '', skills: [] as string[] },
});

<FieldFlowController
  control={control}
  name="email"
  type="email"
  label="Email"
  suggestions
  rules={{ required: 'Email is required' }}
/>

<FieldFlowTagsController
  control={control}
  name="skills"
  label="Skills"
  rules={{ validate: v => (v?.length ? true : 'Add at least one skill') }}
/>

Also exported: mapRhfToFieldFlowProps for custom Controller renders.


Smart suggestion chips

Show quick-complete chips while a field is focused — a UX gap most RN input kits skip:

// Infer from type: email → @gmail.com…, phone → +1/+44…, number/decimal → $/€/£
<FieldFlowInput type="email" label="Email" value={email} onChangeText={setEmail} suggestions />

// Force a preset or pass a custom list
<FieldFlowInput type="phone" suggestions="phone" value={phone} onChangeText={setPhone} />
<FieldFlowInput suggestions={['@company.com', '@partner.io']} value={work} onChangeText={setWork} />

// Standalone bar (e.g. above the keyboard area)
<FieldFlowAffordance
  kind="email"
  value={email}
  onSelect={setEmail}
/>

Helpers exported: applyEmailDomainSuggestion, DEFAULT_EMAIL_DOMAINS, DEFAULT_PHONE_DIAL_CODES, DEFAULT_CURRENCY_SYMBOLS.


Form intelligence

Enable friction tracking and accessibility autopilot on the form hook:

const form = useFieldFlowForm({
  initialValues: { email: '', password: '' },
  rules: { email: { required: true, email: true }, password: { required: true, minLength: 8 } },
  fieldOrder: ['email', 'password'],
  intelligence: {
    onReport: report => console.log(report.mostFriction),
    // focusFirstError: true (default)
    // announceErrors: true (default) — VoiceOver / TalkBack
  },
  onSubmit: async values => api.login(values),
});

<FieldFlowInput
  ref={r => form.registerFieldRef('email', { current: r })}
  label="Email"
  {...form.getInputProps('email')}
  suggestions
/>;

// After interactions:
form.intelligence?.getReport();
// → { sessionMs, submitAttempts, fields: { email: { focusCount, errorCount, totalFocusMs, … } }, mostFriction }

Standalone helpers: useFormIntelligence, focusFirstError, announceForAccessibility.


Conditional fields & field arrays

const form = useFieldFlowForm({
  initialValues: { accountType: 'personal', company: '', taxId: '' },
  conditions: {
    company: {
      showWhen: v => v.accountType === 'business',
      requiredWhen: v => v.accountType === 'business',
    },
    taxId: {
      showWhen: v => v.accountType === 'business',
      disabledWhen: v => v.accountType !== 'business',
    },
  },
});

{form.isVisible('company') && (
  <FieldFlowInput label="Company" {...form.getInputProps('company')!} />
)}

Repeatable groups:

import { useFieldFlowArray, FieldFlowInput } from 'rn-inputforge';

const guests = useFieldFlowArray({
  createItem: () => ({ name: '', email: '' }),
  min: 1,
  max: 5,
});

{guests.items.map((guest, i) => (
  <FieldFlowInput
    key={i}
    label={`Guest ${i + 1}`}
    value={guest.name}
    onChangeText={name => guests.update(i, g => ({ ...g, name }))}
  />
))}
<Button title="Add guest" disabled={guests.isMax} onPress={() => guests.append()} />

Draft resume

Persist form values with any storage backend (AsyncStorage, MMKV, custom). No hard dependency:

import AsyncStorage from '@react-native-async-storage/async-storage';
import { useFieldFlowDraft, FieldFlowDraftBanner, useFieldFlowForm } from 'rn-inputforge';

const form = useFieldFlowForm({ initialValues: { name: '', email: '' }, /* … */ });

const draft = useFieldFlowDraft({
  key: 'signup-v1',
  storage: {
    getItem: k => AsyncStorage.getItem(k),
    setItem: (k, v) => AsyncStorage.setItem(k, v),
    removeItem: k => AsyncStorage.removeItem(k),
  },
  values: form.values,
  onRestore: form.setValues,
  exclude: ['password'], // never persist secrets
  ttlMs: 7 * 24 * 60 * 60 * 1000,
});

<FieldFlowDraftBanner
  visible={draft.hasDraft}
  savedAt={draft.draftSavedAt}
  onResume={draft.resume}
  onDiscard={draft.discard}
/>;

On successful submit, call await draft.clear() so the banner doesn't reappear.


Multi-step wizard

import { useFieldFlowForm, useFieldFlowSteps, FieldFlowStepper, FieldFlowInput } from 'rn-inputforge';

const form = useFieldFlowForm({
  initialValues: { name: '', email: '', bio: '' },
  rules: {
    name: { required: true },
    email: { required: true, email: true },
  },
  onSubmit: async values => api.signup(values),
});

const wizard = useFieldFlowSteps({
  values: form.values,
  validateFields: form.validateFields,
  onComplete: form.submit,
  steps: [
    { id: 'profile', title: 'Profile', fields: ['name'] },
    { id: 'contact', title: 'Contact', fields: ['email'] },
    { id: 'about', title: 'About', fields: ['bio'] },
  ],
});

<>
  <FieldFlowStepper steps={wizard.steps} stepIndex={wizard.stepIndex} onStepPress={wizard.goTo} />
  {wizard.step.id === 'profile' && <FieldFlowInput label="Name" {...form.getInputProps('name')!} />}
  {wizard.step.id === 'contact' && <FieldFlowInput type="email" label="Email" {...form.getInputProps('email')!} />}
  {wizard.step.id === 'about' && <FieldFlowInput label="Bio" {...form.getInputProps('bio')!} />}
  <Button title={wizard.isLast ? 'Submit' : 'Next'} onPress={() => wizard.next()} />
  {!wizard.isFirst && <Button title="Back" onPress={wizard.back} />}
</>;

Server errors

Map API 422 responses straight into the form:

try {
  await api.signup(form.values);
} catch (err) {
  // { email: 'Already taken', password: 'Too weak' }
  form.setErrors(err.fieldErrors);
  // or: form.setFieldError('email', 'Already taken');
  // form.clearErrors('email') / form.clearErrors()
}

Also available: form.isDirty, form.dirtyFields, form.validateFields(['email', 'password']).


Localized validation

const form = useFieldFlowForm({
  initialValues: { email: '' },
  rules: { email: { required: true, email: true } },
  locale: 'es', // en | es | fr | de | pt | ar
  // or: messages: createValidationMessages({ required: 'Custom…' }, 'en'),
});

Helpers: getValidationMessages('fr'), validationMessageCatalogs, createValidationMessages.


Dirty leave guard

import { useFieldFlowDirtyGuard } from 'rn-inputforge';

const { confirmIfDirty } = useFieldFlowDirtyGuard({
  isDirty: form.isDirty,
  onDiscard: form.reset,
});

// React Navigation:
navigation.addListener('beforeRemove', async e => {
  if (!form.isDirty) return;
  e.preventDefault();
  if (await confirmIfDirty()) navigation.dispatch(e.data.action);
});

Android hardware back is intercepted automatically when dirty.


Phone with country code

import { FieldFlowPhone } from 'rn-inputforge';

const [phone, setPhone] = useState(null);

<FieldFlowPhone
  label="Mobile"
  value={phone}
  onChange={setPhone}
  defaultCountryCode="PK"
  required
/>;
// phone.e164 → "+923001234567"

File / image upload

Pure JS — you inject the picker (Expo or RN):

import * as ImagePicker from 'expo-image-picker';
import { FieldFlowUpload } from 'rn-inputforge';

<FieldFlowUpload
  label="ID photo"
  maxFiles={2}
  value={files}
  onChange={setFiles}
  pick={async () => {
    const res = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'] });
    if (res.canceled || !res.assets[0]) return null;
    const a = res.assets[0];
    return { uri: a.uri, name: a.fileName ?? 'photo.jpg', mimeType: a.mimeType, size: a.fileSize };
  }}
/>;

Async typeahead

import { FieldFlowTypeahead } from 'rn-inputforge';

<FieldFlowTypeahead
  label="City"
  value={city}
  displayValue={cityLabel}
  onChange={(value, option) => {
    setCity(value);
    setCityLabel(option?.label ?? '');
  }}
  fetchOptions={async query => {
    const rows = await api.searchCities(query);
    return rows.map(r => ({ label: r.name, value: r.id }));
  }}
  minChars={2}
/>;

Rating / slider / number stepper

<FieldFlowRating label="Experience" value={rating} onChange={setRating} />
<FieldFlowSlider label="Budget" value={budget} onChange={setBudget} min={0} max={1000} step={50} />
<FieldFlowNumberStepper label="Guests" value={guests} onChange={setGuests} min={1} max={12} />

Credit card group

import { FieldFlowCreditCard } from 'rn-inputforge';

const [card, setCard] = useState({ number: '', expiry: '', cvc: '', name: '' });

<FieldFlowCreditCard value={card} onChange={setCard} showName required />;

Signature pad

import { FieldFlowSignature } from 'rn-inputforge';

<FieldFlowSignature
  label="Sign to confirm"
  value={signature}
  onChange={setSignature}
  required
/>;
// signature.isEmpty / signature.strokes

Variants

Every text-like field (FieldFlowInput, FieldFlowMaskedInput, date picker trigger) supports:

| variant | Look | | ------------ | ----------------------------------------- | | outlined | Bordered box (default) | | filled | Soft filled background | | underline | Bottom border only | | floating | Label animates into the border when filled / focused |

<FieldFlowInput variant="floating" label="Email" type="email" value={email} onChangeText={setEmail} />
<FieldFlowInput variant="underline" label="Name" leftIcon="user" value={name} onChangeText={setName} />

Icons

Built-in, dependency-free icons — pass a name or any custom React node:

<FieldFlowInput leftIcon="mail" rightIcon={<MyLucideIcon />} />
<FieldFlowInput type="email" />           // auto leftIcon="mail"
<FieldFlowInput type="password" />        // auto leftIcon="lock" + show/hide
<FieldFlowMaskedInput mask="card" />      // auto leftIcon="creditCard"

Built-in names include: eye, eyeOff, clear, check, alert, search, send, camera, image, paperclip, mic, mapPin, chevronDown/Up/Left/Right, calendar, clock, creditCard, currency, phone, mail, lock, user, key, hash, otp, radio, radioFilled, switch, star, plus, minus, info, home, settings, edit, trash, link, globe, file, folder, bell, heart, spinner.

Use FieldFlowIcon / renderFieldFlowIcon anywhere in your app. Set iconSize on components to scale them. Pass leftIcon={null} / undefined carefully — omit the prop to keep defaults, or pass a custom node to replace.

OTP / PIN

import { FieldFlowOTP } from 'rn-inputforge';

<FieldFlowOTP
  label="Verification code"
  leftIcon="otp"
  length={6}
  value={code}
  onChangeText={setCode}
  onComplete={value => verify(value)}
  cellSize={48}
/>

Supports paste/autofill (textContentType="oneTimeCode"), backspace navigation, secure mode, custom renderCell, cell styles, and icons.


Masked inputs

import { FieldFlowMaskedInput } from 'rn-inputforge';

<FieldFlowMaskedInput mask="phone" label="Phone" value={phone} onChangeText={setPhone} />
<FieldFlowMaskedInput mask="card" label="Card" valueMode="raw" value={card} onChangeText={(_, raw) => setCard(raw)} />
<FieldFlowMaskedInput mask="currency" maskOptions={{ currencySymbol: '$' }} value={amount} onChangeText={setAmount} />
<FieldFlowMaskedInput mask="date" label="Date" value={date} onChangeText={setDate} />
<FieldFlowMaskedInput mask="custom" maskOptions={{ pattern: '###-##-####' }} value={ssn} onChangeText={setSsn} />

Built-in masks: phone, card, currency, date, custom (pattern with # = digit). onChangeText(formatted, raw) always receives both; valueMode controls which string you store ('formatted' default, or 'raw').

Low-level helpers are also exported: applyMask, formatMasked, unmask, digitsOnly.


Date & time picker

Pure JS calendar / time sheet (no native modules):

import { FieldFlowDateTimePicker } from 'rn-inputforge';

<FieldFlowDateTimePicker mode="date" label="Birthday" value={dob} onChange={setDob} clearable />
<FieldFlowDateTimePicker mode="time" label="Start time" value={time} onChange={setTime} hourCycle={12} />
<FieldFlowDateTimePicker
  mode="datetime"
  label="Appointment"
  value={appt}
  onChange={setAppt}
  leftIcon="calendar"
  minimumDate="2020-01-01"
  formatDisplay={(d) => d.toLocaleString()}
/>

Fully customizable: icons, labels, confirm/cancel text, renderTrigger, formatDisplay, min/max dates, theme, styles.

Checkbox, radio & switch

import { FieldFlowCheckbox, FieldFlowRadio, FieldFlowSwitch } from 'rn-inputforge';

<FieldFlowCheckbox
  label="I agree to the Terms"
  leftIcon="file"
  checkedIcon="check"
  value={accepted}
  onChange={setAccepted}
  required
/>

<FieldFlowRadio
  label="Plan"
  leftIcon="star"
  value={plan}
  onChange={setPlan}
  options={[
    { label: 'Starter', value: 'starter', icon: 'home' },
    { label: 'Pro', value: 'pro', description: 'Most popular', icon: 'star' },
  ]}
/>

<FieldFlowSwitch label="Push notifications" leftIcon="bell" value={pushOn} onChange={setPushOn} />

Override colors (checkedColor, onTrackColor, …), icons, sizes, and renderOption when you need full control.


Keyboard & form navigation

useKeyboardManager handles "Next" → focus the next field, "Done" → submit, and keeps the focused field scrolled above the keyboard:

const keyboardManager = useKeyboardManager({
  order: ['name', 'email', 'password'],
  onSubmit: form.submit,
});

<KeyboardAwareFormWrapper keyboardManager={keyboardManager}>
  <FieldFlowInput
    ref={r => keyboardManager.registerField('name', { current: r })}
    returnKeyType={keyboardManager.getReturnKeyType('name')}
    onSubmitEditing={() => keyboardManager.focusNext('name')}
    /* ...value, onChangeText, etc. */
  />
  {/* ...more fields */}
</KeyboardAwareFormWrapper>;

See example/App.tsx for a complete multi-field form wired end to end.


Dropdown / select (single & multiple)

import { FieldFlowDropdown } from 'rn-inputforge';

// Single select — slides up from the bottom by default
<FieldFlowDropdown
  label="Country"
  placeholder="Select your country"
  options={[
    { label: 'United States', value: 'us' },
    { label: 'Pakistan', value: 'pk' },
    { label: 'United Kingdom', value: 'uk' },
  ]}
  value={country}
  onChange={setCountry}
  clearable
  required
/>

// Multi select — centered popup modal, with search, confirmed via a "Done" button
<FieldFlowDropdown
  multiple
  label="Skills"
  presentation="popup"
  options={skillOptions}
  values={skills}
  onChangeMulti={setSkills}
  searchable
/>

Presentation modes (presentation prop):

  • "bottomSheet" (default) — slides up from the bottom, dismiss by tapping the backdrop
  • "popup" — fades in, centered on screen
  • "inline" — drops down directly under the field itself (flips upward automatically if there isn't room below)

Single vs multiple select: pass value + onChange for single select, or multiple + values + onChangeMulti for multi-select. In multi-select, selections are staged until the user taps the confirm button (confirmLabel, default "Done") — pass closeOnSelect if you'd rather commit immediately on each tap instead.

Search is shown automatically once there are more than searchThreshold options (default 6) — set searchable explicitly to force it on or off. Every option can carry a description (shown as a second line) and can be individually disabled. Like every other FieldFlow component, it accepts theme, label, helperText, errorText, and disabled for consistency with the rest of your form.


Description fields

import { DescriptionField } from 'rn-inputforge';

<DescriptionField
  label="Bio"
  placeholder="Tell us about yourself..."
  value={bio}
  onChangeText={setBio}
  maxLength={240}
  minHeight={80}
  maxHeight={200}
/>;

Grows automatically as the user types, shows a live character counter, and supports every validation/theming prop FieldFlowInput does.


Chat input

import { ChatInput } from 'rn-inputforge';

<ChatInput
  value={message}
  onChangeText={setMessage}
  onSend={text => sendMessage(text)}
  attachmentActions={['camera', 'gallery', 'document', 'location']}
  onAttachmentPress={action => handleAttachment(action)}
  customActions={[{ id: 'gif', icon: 'image', onPress: openGifPicker }]}
  placeholder="Type a message..."
/>;

Handles expandable height, keyboard-avoiding behavior, and a send button that's only active once there's text to send. Attachment buttons are opt-in per screen — pass only the ones your chat needs.


Autofill profiles

import { FieldFlowInput, autofillProfiles } from 'rn-inputforge';

<FieldFlowInput type="email" label="Email" autofill={autofillProfiles.signup.email} ... />
<FieldFlowInput type="password" label="Password" autofill="newPassword" ... />
<FieldFlowOTP autofill="oneTimeCode" ... /> // or pass through TextInput props

Roles: email, password, newPassword, oneTimeCode, tel, name, streetAddress, ccNumber, … — see getAutofillProps / autofillProfiles.


Keyboard toolbar

import {
  FieldFlowKeyboardToolbar,
  FIELD_FLOW_KEYBOARD_TOOLBAR_ID,
  useKeyboardManager,
} from 'rn-inputforge';

const kb = useKeyboardManager({ order: ['email', 'password'], onSubmit: form.submit });

<>
  <FieldFlowInput
    inputAccessoryViewID={FIELD_FLOW_KEYBOARD_TOOLBAR_ID}
    ...
  />
  <FieldFlowKeyboardToolbar
    nativeID={FIELD_FLOW_KEYBOARD_TOOLBAR_ID}
    onPrevious={() => kb.focusPrevious(activeId)}
    onNext={() => kb.focusNext(activeId)}
    onDone={form.submit}
    canPrevious={kb.canGoPrevious(activeId)}
    canNext={kb.canGoNext(activeId)}
  />
</>

Smart paste

import { routeSmartPaste, useSmartPaste } from 'rn-inputforge';

const { handlePaste } = useSmartPaste({
  targets: {
    otp: r => setCode(r.value),
    card: r => setCardNumber(r.raw ?? r.value),
    email: r => form.handleChange('email')(r.value),
    addressFields: addr => {
      form.handleChange('street')(addr.street ?? '');
      form.handleChange('city')(addr.city ?? '');
    },
  },
});

// After Clipboard.getString():
handlePaste(pastedText);

Completeness coach

import { useFieldFlowCompleteness, FieldFlowCompleteness } from 'rn-inputforge';

const completeness = useFieldFlowCompleteness({
  values: form.values,
  requiredFields: ['name', 'email', 'password'],
});

<FieldFlowCompleteness completeness={completeness} />;

Auth actions

import { FieldFlowAuthActions } from 'rn-inputforge';

<FieldFlowAuthActions
  actions={[
    { id: 'passkey', label: 'Passkey', icon: 'key', onPress: signInWithPasskey },
    { id: 'apple', label: 'Apple', icon: 'user', onPress: signInWithApple },
    { id: 'google', label: 'Google', icon: 'globe', onPress: signInWithGoogle },
  ]}
/>

Schema form renderer

import { FieldFlowSchemaForm, type FieldFlowFormSchema } from 'rn-inputforge';

const schema: FieldFlowFormSchema = {
  fields: [
    { name: 'name', type: 'text', label: 'Name', required: true, section: 'profile', autofill: 'name' },
    { name: 'email', type: 'email', label: 'Email', required: true, section: 'profile' },
    { name: 'plan', type: 'segmented', label: 'Plan', options: [
      { label: 'Free', value: 'free' },
      { label: 'Pro', value: 'pro' },
    ]},
  ],
  sections: [{ id: 'profile', title: 'Profile', icon: 'user' }],
};

<FieldFlowSchemaForm
  schema={schema}
  onSubmit={async values => api.save(values)}
  renderFooter={({ submit, isSubmitting }) => (
    <Button title={isSubmitting ? '…' : 'Save'} onPress={() => void submit()} />
  )}
/>;

A11y auditor

import { FieldFlowA11yAudit, runA11yAudit } from 'rn-inputforge';

<FieldFlowA11yAudit
  audit={{
    fields: [
      { name: 'email', label: 'Email', required: true, hasAccessibilityLabel: true },
      { name: 'bio', placeholderOnly: true, hitHeight: 36 },
    ],
    respectsReducedMotion: true,
  }}
/>;

Dev-only by default (devOnly).


Offline submit queue

import AsyncStorage from '@react-native-async-storage/async-storage';
import { useFieldFlowSubmitQueue } from 'rn-inputforge';

const queue = useFieldFlowSubmitQueue({
  key: 'signup-queue',
  storage: AsyncStorage,
  submit: async payload => {
    await api.signup(payload);
  },
});

// Instead of failing hard when offline:
await queue.enqueue(form.values);
// queue.pendingCount / queue.flush() / queue.clear()

Custom render slots

Most controls accept optional render props so apps can keep FieldFlow behavior and swap chrome:

| Component | Slots | | --------- | ----- | | FieldFlowUpload | renderDropzone, renderFile | | FieldFlowTypeahead | renderOption, renderEmpty | | FieldFlowRating | renderStar | | FieldFlowSlider | renderThumb | | FieldFlowNumberStepper | renderDecrease, renderIncrease, renderValue | | FieldFlowPhone | renderDialButton, renderCountryOption | | FieldFlowStepper | renderStep, headerRight | | FieldFlowSignature | renderClear, renderPlaceholder |

<FieldFlowRating
  value={rating}
  onChange={setRating}
  renderStar={({ filled, size }) => (
    <Text style={{ fontSize: size }}>{filled ? '🟢' : '⚪'}</Text>
  )}
/>

<FieldFlowUpload
  pick={myPicker}
  value={files}
  onChange={setFiles}
  renderFile={({ file, remove }) => (
    <Pressable onPress={remove}>
      <Text>{file.name} ✕</Text>
    </Pressable>
  )}
/>

Defaults stay unchanged when you omit these props.


Visual system

One-liner knobs on FieldFlowThemeProvider — easy on the eyes, fully customizable:

<FieldFlowThemeProvider
  theme={themePresets.soft}
  density="comfortable"   // compact | comfortable | spacious
  size="md"               // sm | md | lg
  shape="rounded"         // square | rounded | pill
  surface="soft"          // flat | elevated | soft | inset
  focusRing="softGlow"    // solid | softGlow | underlinePulse
  animateTheme            // soft crossfade when theme changes
>
  <App />
</FieldFlowThemeProvider>

Inline alert / skeleton / form header

<FieldFlowFormHeader
  title="Create account"
  subtitle="A calm start — we’ll guide you through a few short steps."
  progress={0.35}
/>

<FieldFlowInlineAlert tone="info" title="Privacy" message="We’ll never share your email." />

{loading ? <FieldFlowSkeleton count={4} lastMultiline /> : <FormFields />}

Info tip + helper status

<FieldFlowInput
  label="Tax ID"
  infoTip="Used only for invoices. Never shown publicly."
  helperText="Optional for personal accounts"
  helperStatus="info"
  ...
/>

Layout & form chrome

UI building blocks added in 0.5.2 — pair with the visual system for polished signup / checkout / profile screens.

<FieldFlowToastProvider>
  <FieldFlowBannerStack
    banners={[{ id: '1', tone: 'warning', message: 'Verify your email to unlock exports.' }]}
  />

  <FieldFlowOrGate
    above={<FieldFlowAuthActions actions={ssoActions} dividerLabel="" />}
    label="or continue with email"
    below={<EmailPasswordFields />}
  />

  <FieldFlowSplitRow weights={[1, 1]}>
    <FieldFlowInput label="First name" ... />
    <FieldFlowInput label="Last name" ... />
  </FieldFlowSplitRow>

  <FieldFlowAddress value={address} onChange={setAddress} />
  <FieldFlowMoney label="Budget" currencySymbol="$" value={amount} onChangeText={setAmount} />
  <FieldFlowPercent label="Discount" value={pct} onChangeText={setPct} />

  <FieldFlowColor
    label="Brand color"
    value={color}
    onChange={setColor}
    options={[
      { value: '#3B82F6' },
      { value: '#22C55E' },
      { value: '#EF4444' },
    ]}
  />

  <FieldFlowAvatarPicker initials="SW" onPress={openImagePicker} />

  <FieldFlowBottomSheetField label="Country" valueLabel={country}>
    {({ close }) => <CountryList onPick={c => { setCountry(c); close(); }} />}
  </FieldFlowBottomSheetField>

  <FieldFlowConsent
    label="I agree to the Terms"
    value={agreed}
    onChange={setAgreed}
    links={[{ label: 'Terms', url: 'https://example.com/terms' }]}
  />

  <FieldFlowTermsGate terms={LONG_TERMS} value={accepted} onChange={setAccepted} />

  <FieldFlowReviewSummary
    items={[
      { id: 'email', label: 'Email', value: email, section: 'Account', onEdit: () => goToStep(0) },
      { id: 'plan', label: 'Plan', value: 'Pro', section: 'Billing' },
    ]}
  />

  <FieldFlowEmptyState kind="noResults" actionLabel="Clear filters" onAction={clear} />
</FieldFlowToastProvider>

Morph, float presets, haptics

<FieldFlowInput
  variant="floating"
  floatPreset="material"   // subtle | dramatic | material
  morph                    // error pulse + success morph (default on with motion)
  haptics                  // light Vibration on focus / success / error
  themeCrossfade           // soft fade when this field’s theme prop changes
  ...
/>

const { onTick } = useFieldFlowHaptics(); // or inject expo-haptics via `trigger`

Motion & UI

Field motion

FieldFlowInput ships with micro-interactions on by default (motion={true}):

  • Focus glow — soft ring around the field chrome
  • Error shake — brief horizontal shake when validation fails
  • Message fade — helper / error text cross-fades in
  • Success pop — right-side success icon scales in

All of this uses React Native Animated (no Reanimated) and respects OS Reduce Motion. Pass motion={false} to disable per field, or use useReducedMotion / useFieldMotion if you build custom chrome.

<FieldFlowInput label="Email" type="email" value={email} onChangeText={setEmail} motion />

Prefix & suffix

Inline affixes sit inside the field before / after the value (alongside icons):

<FieldFlowInput
  type="phone"
  label="Phone"
  prefix="+92"
  value={phone}
  onChangeText={setPhone}
/>

<FieldFlowInput
  type="decimal"
  label="Weight"
  suffix="kg"
  value={weight}
  onChangeText={setWeight}
/>

Form sections

Group related fields into a themed card with optional collapse:

import { FieldFlowSection, FieldFlowInput } from 'rn-inputforge';

<FieldFlowSection title="Account" description="How we’ll reach you" icon="mail" collapsible>
  <FieldFlowInput type="email" label="Email" value={email} onChangeText={setEmail} />
  <FieldFlowInput type="phone" label="Phone" prefix="+1" value={phone} onChangeText={setPhone} />
</FieldFlowSection>;

Segmented control

Animated pill control for plan pickers, account type, filters:

import { FieldFlowSegmented } from 'rn-inputforge';

<FieldFlowSegmented
  label="Account type"
  value={accountType}
  onChange={setAccountType}
  options={[
    { label: 'Personal', value: 'personal', icon: 'user' },
    { label: 'Business', value: 'business', icon: 'globe' },
  ]}
/>;

Theming

Wrap your app (or just a screen) in FieldFlowThemeProvider and override only what you need — everything else falls back to the default theme:

import { FieldFlowThemeProvider, themePresets } from 'rn-inputforge';

// One-liner brand start
<FieldFlowThemeProvider theme={themePresets.soft}>
  <App />
</FieldFlowThemeProvider>

// Or merge your own tokens
<FieldFlowThemeProvider
  theme={{
    colors: { borderFocused: '#7C3AED', labelFocused: '#7C3AED' },
    spacing: { borderRadius: 14 },
  }}
>
  <App />
</FieldFlowThemeProvider>;

Bundled presets: default, dark, soft, warm, teal, midnight, contrast. Use getThemePreset('teal') if you prefer a function call.

You can also pass a one-off theme prop directly to any single field to override it locally, or use the bundled darkTheme as a starting point for dark mode.

Theme tokens you can override: colors.*, spacing.*, typography.*, animation.* (see FieldFlowTheme in the package types).


Public API at a glance

Import anything below from 'rn-inputforge':

// Components
FieldFlowInput, FieldFlowOTP, FieldFlowMaskedInput, FieldFlowDateTimePicker,
FieldFlowCheckbox, FieldFlowRadio, FieldFlowSwitch, FieldFlowDropdown,
FieldFlowTags, FieldFlowSegmented, FieldFlowSection, FieldFlowAffordance,
FieldFlowDraftBanner, FieldFlowPasswordStrength, FieldFlowStepper,
FieldFlowPhone, FieldFlowUpload, FieldFlowTypeahead, FieldFlowRating,
FieldFlowSlider, FieldFlowNumberStepper, FieldFlowCreditCard, FieldFlowSignature,
DescriptionField, ChatInput, KeyboardAwareFormWrapper

// Form hooks
useFieldFlowForm, useFieldFlowArray, useFieldFlowDraft,
useFieldFlowSteps, useFieldFlowDirtyGuard,
useKeyboardManager, useFieldState, useFormIntelligence

// Motion / Theming / i18n
useFieldMotion, useReducedMotion
FieldFlowThemeProvider, themePresets, getThemePreset, defaultTheme, darkTheme
getValidationMessages, createValidationMessages, validationMessageCatalogs

// Adapters / Icons
FieldFlowController, FieldFlowTagsController, mapRhfToFieldFlowProps
FieldFlowIcon, renderFieldFlowIcon

// Validation, masks, phone, a11y
runRules, zod helpers, applyMask, unmask, DEFAULT_COUNTRY_DIALS,
evaluatePasswordStrength, focusFirstError, announceForAccessibility, …

Types for props, themes, rules, and draft storage are exported alongside the values. See example/App.tsx for a full usage reference screen.


Project structure

rn-inputforge/
├── src/
│   ├── components/
│   │   ├── FieldFlowInput/            core input + variants + motion + affixes
│   │   ├── FieldFlowSection/          themed form card / collapse
│   │   ├── FieldFlowSegmented/        animated pill control
│   │   ├── FieldFlowAffordance/       standalone suggestion chip bar
│   │   ├── FieldFlowOTP/              OTP / PIN digit cells
│   │   ├── FieldFlowMaskedInput/      phone, card, currency, date, custom masks
│   │   ├── FieldFlowDateTimePicker/   date / time / datetime (pure JS)
│   │   ├── FieldFlowCheckbox/         themed checkbox
│   │   ├── FieldFlowRadio/            radio group
│   │   ├── FieldFlowSwitch/           animated switch
│   │   ├── FieldFlowDraftBanner/      resume-draft UI
│   │   ├── FieldFlowTags/             multi-value chip input
│   │   ├── FieldFlowPasswordStrength/ strength meter + rules
│   │   ├── DescriptionField/          auto-growing multiline field
│   │   ├── ChatInput/                 chat composer + attachment toolbar
│   │   ├── FieldFlowDropdown/         single/multi select
│   │   └── KeyboardAwareFormWrapper/
│   ├── adapters/                      react-hook-form Controllers
│   ├── hooks/                         form, array, draft, intelligence, keyboard
│   ├── intelligence/                  a11y helpers + friction report types
│   ├── motion/                        field motion + reduce-motion hooks
│   ├── theme/                         ThemeProvider + presets + default/dark
│   ├── validation/                    validators, Zod adapter
│   ├── icons/                         dependency-free icon set
│   ├── utils/                         masks, affordance, password strength, …
│   ├── __tests__/                     Jest unit tests
│   └── index.ts                       public API
├── example/App.tsx                    full usage reference
├── package.json
└── tsconfig*.json

Building the library locally

The package is set up with react-native-builder-bob to produce CommonJS, ES module, and TypeScript declaration output — the standard setup for RN libraries, and it works the same regardless of whether the consuming app uses npm or Yarn.

# with yarn
yarn install
yarn build

# with npm
npm install
npm run build

This produces:

lib/commonjs/   -> require()-style output
lib/module/     -> ES module output (used by Metro when bundling)
lib/typescript/ -> .d.ts declaration files

Publishing

npm version patch   # or minor / major
npm publish          # or: yarn publish

Both package managers read the same package.json, so there's nothing extra to configure to support either one.

License

MIT