Cursor aware
Formatting never feels like fighting the input. Rifm tracks accepted characters and restores the natural cursor position.
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.
Formatting never feels like fighting the input. Rifm tracks accepted characters and restores the natural cursor position.
Native inputs, your design system, or a third-party text field. Rifm only supplies value and onChange.
Numbers, dates, phone numbers, card masks, casing rules, and domain-specific values all use the same small API.
Every example runs locally with Rifm and includes the complete component. Edit from the middle, delete separators, or paste a messy value.
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>
)
}
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>
)
}
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" />
}
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>
)
}