Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Inputs

Currency Input

Inputs

Currency Input React component for Tailwind CSS v4

Cron PickerDate Picker
Live props
Field
label
description
error
required
invalid
disabled
readOnly
Content
placeholder
name
currencyName
Appearance
size
variant
radius
currencyDisplay
Behavior
defaultCurrency
locale
min
max
decimals
allowNegative
step
clearable
selectOnFocus
responsive
Code
import { CurrencyInput } from "@/components/ui/currency-input"

<CurrencyInput />
  1. Home
  2. Components
  3. Inputs
  4. Currency Input
Pro

Currency Input React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React currency input: a money field formatted per locale as you type, with an optional currency selector and quick amount presets.

Type an amount the way you write it: in English 1234.5 becomes 1,234.5 as you type, in Romanian 1.234,5, and either a comma or a dot typed at the end becomes the decimal. Letters never get in, extra decimals are cut, and a currency without cents (JPY) ignores the decimal key. The symbol sits where the locale puts it ($1,234.56, 1.234,56 lei) and is not part of the text. When the field loses focus, or on Enter, the amount is padded to the currency's decimals (1,234.5 becomes 1,234.50) and clamped to min and max; out of range while typing, the field shows the red ring. Focusing the field selects the amount so the next keys replace it. ArrowUp and ArrowDown add or take away one step, Shift ten steps. With clearable, the x or Escape empties the field. With two or more currencies a compact code button (USD) sits at the end of the box: click it or press Enter, Space or the arrows on it to open the list (a bottom sheet on phones), type a letter to jump to a currency, and pick one; the symbol crossfades and the amount stays the same (it is never converted). Quick amounts sit as chips under the field; tapping one fills it in and the matching chip turns solid. Pasting 1 234,56 € or $1,234.56 over the field reads the whole amount. On a phone the decimal keypad opens (either separator is accepted), the text is 16px so iOS never zooms, the md box grows to 44px and the chips and the currency button have 44px hit areas.

Currency Input is a Pro React component in the React input components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add currency-input, together with Button, Field, Select, Number format, useLocale and 3 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add currency-input

Currency Input is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import CurrencyInput from @/components/ui/currency-input.

import { useState } from "react"
import { CurrencyInput } from "@/components/ui/currency-input"

export function ListingPrice() {
  const [price, setPrice] = useState<number | null>(1250)
  const [currency, setCurrency] = useState("USD")

  return (
    <form className="flex flex-col gap-4">
      <CurrencyInput
        name="price"
        currencyName="currency"
        label="Price"
        description="The price includes tax"
        locale="en-US"
        currencies={["USD", "EUR", "GBP"]}
        currency={currency}
        onCurrencyChange={setCurrency}
        value={price}
        onValueChange={setPrice}
        min={1}
        error={price !== null && price < 1 ? "The minimum price is $1" : undefined}
        required
      />
      <CurrencyInput label="Donation" locale="en-US" presets={[50, 100, 200, 500]} defaultValue={100} clearable />
    </form>
  )
}

Props

  • labeldefault ""
    ReactNode

    Renders the field anatomy around the box and links it to the input with htmlFor. Leave it empty for a bare field.

  • descriptiondefault ""
    ReactNode

    Helper text under the box, linked with aria-describedby (followed by a hidden "Amount in …" naming the currency). The error takes its place while it shows.

  • errordefault ""
    ReactNode

    Replaces the description with the animated error and marks the field invalid (red ring, aria-invalid).

  • requireddefault false
    boolean

    The mark after the label, and required on the input, so native form validation stops an empty submit.

  • invaliddefault false
    boolean

    The invalid look and aria-invalid without a message. An amount outside min and max sets it on its own until it is back in range.

  • disableddefault false
    boolean

    Dims the field to 50%, blocks typing, the currency button and the chips, and leaves the hidden inputs out of the submit.

  • readOnlydefault false
    boolean

    The amount stays visible and selectable; typing, the arrows, the currency list and the chips stop.

  • placeholderdefault ""
    string

    Hint text while the field is empty. Empty shows zero in the locale and the currency's decimals ("0.00" in en-US, "0" for JPY).

  • namedefault ""
    string

    Renders a hidden input with this name and the amount as "1234.56" (dot decimal, empty when null), so FormData gets a number, not the formatted text. The visible input has no name.

  • currencyNamedefault ""
    string

    With a currency selector, a second hidden form field with the chosen code ("EUR").

  • sizedefault "md"
    "sm" | "md" | "lg"

    32 / 40 / 48px, like a button. md grows to 44px on touch screens and the text is 16px on phones so iOS never zooms.

  • variantdefault "soft"
    "soft" | "outline" | "underline" | "ghost"

    The box: a soft fill, an outline, a bottom border that grows a bar on focus, or ghost for tables and toolbars.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    Corner shape, scaled per size like the button (md is 8 / 10 / 12px); the currency button follows it. Ignored by underline.

  • currencyDisplaydefault "narrowSymbol"
    "narrowSymbol" | "symbol" | "code" | "name"

    How the currency reads next to the amount and on the chips: narrowSymbol is what people write ("$", "€", "lei"), symbol and code print "RON" for lei, name spells it out ("US dollars").

  • defaultCurrencydefault "USD"
    string

    The starting ISO 4217 code while uncontrolled. Its decimals (USD 2, JPY 0) and symbol follow.

  • localedefault ""
    string

    Formats and parses with this locale: separators, and which side the symbol goes on. Empty uses the nearest LocaleProvider, then the page language. The demo passes "en-US".

  • mindefault -90071992547409
    number

    The lowest amount. A lower typed amount shows invalid and is clamped on blur; the arrows stop here. Without allowNegative nothing goes below 0 anyway.

  • maxdefault 90071992547409
    number

    The highest amount, the same way. Anything above Number.MAX_SAFE_INTEGER / 100 cannot be typed at all.

  • decimalsdefault -1
    number

    Fraction digits typed and padded to (0 to 6). The default -1 takes the currency's: USD 2, JPY 0, BHD 3.

  • allowNegativedefault false
    boolean

    Lets a minus in (a balance adjustment, a refund). iOS decimal keypads have no minus key: negatives come from ArrowDown or a hardware keyboard.

  • stepdefault 1
    number

    What ArrowUp and ArrowDown add or take away; Shift moves ten steps.

  • clearabledefault false
    boolean

    An x at the end while there is an amount; Escape clears too. Focus stays in the field.

  • selectOnFocusdefault true
    boolean

    Selects the whole amount when the field takes focus (a click too), so typing replaces it.

  • responsivedefault true
    boolean

    The currency list becomes a bottom sheet below 768px.

  • value
    number | null

    The controlled amount in major units (1234.56); null is an empty field. An amount outside min and max shows as given and is flagged invalid.

  • defaultValuedefault "null"
    number | null

    The starting amount while uncontrolled. A form reset returns to it.

  • onValueChange
    (value: number | null) => void

    Every change: live while typing (unclamped until blur), arrows, chips, clearing, and the rounding when a currency with fewer decimals is picked.

  • onChange
    (event: ChangeEvent<HTMLInputElement>) => void

    The native input event, for keystrokes the field accepts.

  • currency
    string

    The controlled ISO 4217 code. Switching currency never converts the amount.

  • onCurrencyChange
    (currency: string) => void

    When a currency is picked from the selector.

  • currencies
    string[]

    Two or more codes show the currency selector: a compact code button at the end of the box, opening a list with the localized names ("US dollar", "Euro") and symbols. With the selector, a suffix symbol is left out (the button names the currency); a prefix one ($) stays.

  • presets
    number[]

    Quick amounts as chips under the field ("$50", "$100"), 44px hit areas on touch. The chip equal to the amount is solid and aria-pressed.

  • labels
    Partial<{ currency: string; clear: string; presets: string; amountIn: (currencyName: string) => string }>

    Accessible names, merged over English defaults: currency ("Currency", the button reads "Currency: USD" and titles the phone sheet), clear ("Clear"), presets ("Quick amounts", the chips group) and amountIn (n => `Amount in ${n}`, read with the field).

  • container
    HTMLElement | null

    Portal target for the currency list. Defaults to document.body.

  • fieldProps
    Partial<FieldProps>

    Anything else for the Field wrapper: optional and optionalLabel, labelAction, orientation, labelWidth, hideLabel.

  • id
    string

    The input's id. Defaults to useId(); inside a <Field> the Field's id wins.

  • ref
    RefObject<HTMLInputElement | null> or (node) => void

    The native text input. Every other native prop lands there too: inputMode (decimal), enterKeyHint (done), autoComplete (transaction-amount), onBlur, onKeyDown, aria-* and data-*.

  • classNames
    Partial<Record<"field" | "label" | "description" | "error" | "counter" | "box" | "input" | "symbol" | "currency" | "clear" | "presets" | "preset", string>>

    Extra classes for the inner parts. box targets the field box, input the native element, currency the code button, preset every chip.

  • className
    string

    Classes for the outermost element: the Field root when there is a label, description or error, else the box (or the column holding box and chips).

Examples

Selectable currency

USD, EUR and GBP in a selector; switching keeps the amount, a hidden field submits the code.

Donation presets

Quick amounts as chips under the field; the matching chip turns solid, and a custom amount validates.

Locales

The same value as 1.234,56 lei in ro-RO and $1,234.56 in en-US; type in either one.

Negative amounts

allowNegative for a balance adjustment, with ArrowUp / ArrowDown in steps of $10.

Min and max

Out of range shows red while typing and is clamped on blur; the error explains the limit.

Sizes and variants

sm, md and lg (lg with a currency selector) and the four box variants.

Currency display

narrowSymbol, symbol, code and name, yen without decimals and a German read-only amount.

Dependencies

motionlucide-reactbuttonfieldselectnumber-formatuse-localemotionuse-controllable-stateui-config

Files

  • components/ui/currency-input.tsx

FAQ

Is the React Currency Input component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Currency Input?

Run npx wingo-ui@latest add currency-input. It also adds the Wingo UI items it builds on (Button, Field, Select, Number format, useLocale and 3 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/currency-input.

How does Currency Input behave on phones?

The currency list becomes a bottom sheet below 768px. The responsive prop controls this and is on by default.

See also

Number InputFieldSelectInputNumber formatlibuseLocalehook

Used in

Filter BuilderInvoice line itemsEditable GridRule BuilderPrice rangeCRM Contact PageCRM Pipeline PageInbox PagePost Listing WizardROI Calculator Page

More React input components

Date PickerDate Time PickerEmail InputEmoji PickerFile UploadInput Group
All 40 React input components

Articles using this component

  • Shadcn Number Input: Steppers, Min, Max and Decimals

    Component guides · Nov 6, 2026 · 8 min read

  • React Currency Input: Intl Formatting Without Caret Jumps

    Component guides · Oct 16, 2026 · 10 min read

  • E-Factura VAT Calculation in TypeScript and React

    Build it: screens and apps · Oct 9, 2026 · 11 min read

PreviousCron PickerNext Date Picker

Post a listing

Step 3 of 4 · Price

75%
Pegas city bikeSports and outdoors · Brooklyn
$1,250.00Amount in US dollars

The price includes tax

The listing shows$1,250.00· Negotiable

onValueChange 1250 · USD
↑ ↓ ±1 · Shift ±10