Rifm / React input format & mask

Formatting that keeps the cursor in place.

Rifm turns any input into a precise formatter or mask. Tiny, dependency-free, and designed around the detail that matters: where your cursor lands next.

~800bminified + gzip
0dependencies
MITopen source
01 / WHY RIFM

One small job. Done carefully.

Cursor aware

Formatting never feels like fighting the input. Rifm tracks accepted characters and restores the natural cursor position.

Input agnostic

Native inputs, your design system, or a third-party text field. Rifm only supplies value and onChange.

±

Format anything

Numbers, dates, phone numbers, card masks, casing rules, and domain-specific values all use the same small API.

02 / GET STARTED

Learn by typing. Then make it yours.

Every example runs locally with Rifm and includes the complete component. Edit from the middle, delete separators, or paste a messy value.

01

Number formatting

Most common
Copy code
import { useState } from 'react'
import { useRifm } from 'rifm'

const formatNumber = (value: string) =>
  value
    .replace(/\D/g, '')
    .replace(/\B(?=(\d{3})+(?!\d))/g, ',')

export function PriceInput() {
  const [value, setValue] = useState('240000')
  const rifm = useRifm({
    value,
    onChange: setValue,
    format: formatNumber,
    accept: /\d/g,
  })

  return (
    <label>
      Invoice total
      <span>$</span>
      <input {...rifm} inputMode="numeric" />
    </label>
  )
}
02

Phone number

Copy code
import { useState } from 'react'
import { Rifm } from 'rifm'
import { AsYouType } from 'libphonenumber-js'

const formatPhone = (value: string) =>
  new AsYouType('US').input(value.replace(/\D/g, '').slice(0, 10))

export function PhoneInput() {
  const [phone, setPhone] = useState('4155552671')
  return (
    <Rifm value={phone} onChange={setPhone} accept={/\d/g} format={formatPhone}>
      {props => <input {...props} inputMode="tel" />}
    </Rifm>
  )
}
03

Date mask

Copy code
import { useState } from 'react'
import { useRifm } from 'rifm'

const formatDate = (value: string) => {
  const digits = value.replace(/\D/g, '').slice(0, 8)
  return [
    digits.slice(0, 2),
    digits.slice(2, 4),
    digits.slice(4),
  ].filter(Boolean).join(' / ')
}

export function DateInput() {
  const [date, setDate] = useState('12082026')
  const rifm = useRifm({
    value: date,
    onChange: setDate,
    format: formatDate,
    accept: /\d/g,
  })

  return <input {...rifm} inputMode="numeric" placeholder="DD / MM / YYYY" />
}
04

Text enforcement

Copy code
import { useState } from 'react'
import { Rifm } from 'rifm'

const uppercaseLatin = (value: string) =>
  value.replace(/[^a-z ]/gi, '').toUpperCase()

export function AirportInput() {
  const [airport, setAirport] = useState('london heathrow')

  return (
    <Rifm
      value={airport}
      onChange={setAirport}
      accept={/[a-z ]/gi}
      format={value => value}
      replace={uppercaseLatin}
    >
      {props => <input {...props} placeholder="TYPE A NAME" />}
    </Rifm>
  )
}