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

@rentnerkev/inputs

v2.1.3

Published

Controlled React input components with validation, icons, length counters, and Tailwind design hooks.

Readme

@rentnerkev/inputs

Controlled React input components with labels, validation, icons, length counters, localization, and customizable Tailwind styling.

Installation

With npm:

npm install @rentnerkev/inputs

Or with Bun:

bun add @rentnerkev/inputs

Components

Each field type has a dedicated component, logic hook, and props type:

  • TextInput
  • EmailInput
  • PhoneInput
  • NumberInput
  • MoneyInput
  • PasswordInput
  • QuantityInput
  • TimeInput
  • Textarea
  • SearchInput and NativeTimeInput (native search and time controls)
  • CheckboxInput, RadioInput, RangeInput, and FileInput (native semantics)

CustomInput remains available as a backward-compatible entry point and selects the concrete component through its type prop. It also accepts type="search" and type="native-time".

Project-wide defaults

Wrap the app once with InputProvider to set locale, messages, visual classes, and validation behavior centrally. Nested providers inherit settings and can override only the relevant values, such as the authenticated user's locale.

import { InputProvider } from '@rentnerkev/inputs'

;<InputProvider
    locale="en"
    validationMode="external"
    customDesign={{
        bg: 'bg-app-field',
        border: 'border-app-border',
        text: 'text-app-ink',
        focusBorder: 'focus:border-app-accent',
    }}
    classNames={{
        input: 'h-12 w-full rounded-xl text-sm',
        textarea: 'w-full rounded-xl text-sm',
        checkbox: 'size-4 accent-app-accent',
    }}
>
    <YourApp />
</InputProvider>

validationMode="external" leaves validation messages to a form library while preserving native attributes, events, and explicit error or aria-invalid props. The default "built-in" mode retains the package's own validation. Each field can override the provider's locale, messages, design, classes, or validation mode as needed. The package includes German, English, Spanish, and French messages; other locales continue to fall back to German.

Quick start

import { useRef, useState } from 'react'
import { EmailInput, Textarea } from '@rentnerkev/inputs'

export function ContactForm() {
    const [email, setEmail] = useState('')
    const [message, setMessage] = useState('')
    const emailRef = useRef<HTMLInputElement>(null)

    return (
        <form>
            <EmailInput
                ref={emailRef}
                id="email"
                label="Email address"
                name="email"
                value={email}
                onChange={(event) => setEmail(event.target.value)}
                onKeyDown={(event) => {
                    if (event.key === 'Escape') emailRef.current?.blur()
                }}
                placeholder="[email protected]"
                autoComplete="email"
                required
                maxLength={120}
                showLength
            />

            <Textarea
                id="message"
                label="Message"
                name="message"
                value={message}
                onChange={(event) => setMessage(event.target.value)}
                placeholder="Your message"
                rows={6}
                minLength={10}
                maxLength={500}
                showLength
                required
            />
        </form>
    )
}

CustomInput

Use CustomInput when the field type is selected dynamically or when migrating from an earlier package version.

import { CustomInput } from '@rentnerkev/inputs'

;<CustomInput
    type="password"
    value={password}
    onChange={(event) => setPassword(event.target.value)}
    placeholder="Password"
    autoComplete="current-password"
    showPasswordStrength
/>

;<CustomInput
    type="quantity"
    value={quantity}
    onChange={(event) => setQuantity(event.target.value)}
    placeholder="Quantity"
    minValue={1}
    maxValue={999}
    suffix="x"
/>

Native attributes and events

The concrete input components support the usual InputHTMLAttributes<HTMLInputElement>. Textarea supports TextareaHTMLAttributes<HTMLTextAreaElement>, including:

  • events such as onFocus, onBlur, onInvalid, onInput, onKeyDown, onKeyUp, onClick, onPaste, and onCopy;
  • form attributes such as id, name, required, disabled, readOnly, form, and autoComplete;
  • constraints such as minLength, maxLength, min, max, step, and pattern;
  • accessibility and metadata attributes such as aria-*, data-*, tabIndex, and title;
  • React refs through ref.

Internal and consumer event handlers are composed. A custom onFocus, for example, does not replace the package's focus handling.

Shared field contract

| Prop | Type | Default | Description | | -------------- | ---------------------------------------------- | ------------------------ | -------------------------------------------------------------------- | | value | string | Required | Controlled field value. | | onChange | ChangeEventHandler | Required | Called after internal input handling. | | label | ReactNode | – | Accessible label above the field. | | description | ReactNode | – | Help text with a stable ARIA relationship. | | error | string \| null | – | External error; overrides internal errors, while null clears them. | | icon | ReactNode | – | Icon displayed on the left. | | triggerRef | Ref<HTMLInputElement \| HTMLTextAreaElement> | – | Alias ref in addition to the standard ref. | | showLength | boolean | false | Displays the current character count. | | customDesign | CustomDesign | – | Overrides individual design classes. | | locale | InputLocale | 'de' | Message language or Intl locale string. | | messages | Partial<InputMessages> | – | Overrides selected localized messages. | | className | string | w-full py-3 rounded-xl | Classes for the actual input element. |

Use native maxLength and minLength props for character constraints. showLength adds the visible counter.

Consumer IDs, the description ID, and the visible error ID are merged into aria-describedby. An invalid native submit focuses the visible field. disabled removes the field from internal validation. readOnly prevents changes and internal constraint validation while retaining its value and native attributes.

For TimeInput, triggerRef points to the visible hour trigger. The same button ref is available through CustomInput when type="time".

Component-specific props

| Component | Additional props | | --------------- | --------------------------------------------------------------------------- | | NumberInput | minValue, maxValue | | MoneyInput | currency (default EUR), locale ('de', 'en', or an Intl locale) | | PasswordInput | showPasswordStrength | | QuantityInput | minValue, maxValue, suffix (default x) | | Textarea | Native textarea props such as rows and wrap; resize through className |

Localization and messages

German remains the default for backward compatibility. Set locale="en" for the complete English validation and ARIA messages. Existing Intl locale strings such as en-GB remain available to MoneyInput; en- and en_ variants use the English message catalog.

<EmailInput
    value={email}
    onChange={(event) => setEmail(event.target.value)}
    locale="en"
    messages={{ required: 'Please enter your email address' }}
    required
/>

The typed catalog is available from the root entry and @rentnerkev/inputs/messages.

Custom design

<EmailInput
    value={email}
    onChange={(event) => setEmail(event.target.value)}
    customDesign={{
        labelText: 'text-blue-200',
        focusRing: 'focus:ring-blue-500/50',
        focusBorder: 'focus:border-blue-500',
        iconFocus: 'group-focus-within:text-blue-500',
    }}
/>

CustomDesign supports bg, border, text, labelText, placeholder, focusRing, focusBorder, errorBorder, errorRing, errorText, iconColor, iconFocus, counterBg, counterText, counterBorderFocus, and the password-strength classes.

When no id is provided, the component creates a stable ID so its label and field remain accessible.

The native checkbox, radio, range, and file controls forward all native input attributes and refs. They are available when a field should retain browser behavior, including unchecked radio values and uncontrolled file selection.

Tailwind CSS

Import the package entry after Tailwind CSS in your main stylesheet:

@import 'tailwindcss';
@import '@rentnerkev/inputs/tailwind.css';

The entry scans only published JavaScript under dist. It provides the shared primary, primary-hover, background-dark, surface-dark, input-dark, border-dark, secondary-text, and muted-foreground theme tokens. Override them with a later @theme block when needed.

Public entry points

All components and prop types are exported from @rentnerkev/inputs. Direct component entry points are also available:

  • @rentnerkev/inputs/input
  • @rentnerkev/inputs/text-input
  • @rentnerkev/inputs/search-input
  • @rentnerkev/inputs/native-time-input
  • @rentnerkev/inputs/checkbox-input
  • @rentnerkev/inputs/radio-input
  • @rentnerkev/inputs/range-input
  • @rentnerkev/inputs/file-input
  • @rentnerkev/inputs/number-input
  • @rentnerkev/inputs/phone-input
  • @rentnerkev/inputs/email-input
  • @rentnerkev/inputs/money-input
  • @rentnerkev/inputs/password-input
  • @rentnerkev/inputs/quantity-input
  • @rentnerkev/inputs/time-input
  • @rentnerkev/inputs/textarea
  • @rentnerkev/inputs/types
  • @rentnerkev/inputs/messages
  • @rentnerkev/inputs/tailwind.css

Development

bun install --frozen-lockfile
bun install --cwd playground --frozen-lockfile
bun run verify
bun run playground:build

bun run verify checks types, Oxlint, Oxfmt, tests, the package build, and the published package contents.

License

MIT