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

Forms

CSV Import

Forms

CSV Import React component for Tailwind CSS v4

Booking WidgetField
Live props
State
defaultStep
defaultDuplicateStrategy
defaultHasHeader
Behavior
accept
maxFileSize
maxRows10000
uniqueField
allowSkipInvalid
inlineEdit
delimiter
defaultCountry
locale
Content
templateFileName
dateFormat
currency
Layout
stepper
fullScreenOnMobile
Appearance
size
tone
Code
import { CsvImport } from "@/components/ui/csv-import"

<CsvImport />
  1. Home
  2. Components
  3. Forms
  4. CSV Import
Pro

CSV Import React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React CSV import wizard for CSV and Excel files in four steps: upload, column mapping, row review with inline fixes, and a run report.

Drop a CSV, TSV or Excel file on the zone (or click it, press Enter or Space on it, or paste a file; on a phone the native picker opens) and a card shows its size, rows, columns, separator and encoding, or its sheet; turn off "The first row is a header" for files that start with data, pick the sheet of a workbook, or download a template built from the fields. Continue to the mapping: every field already points at the column whose name or alias matches, marked Matched, and headerless files get their CUI, IBAN, CNP, email, phone and date columns recognised by their values; open a field's select (a bottom sheet on phones) to pick another column or Don't import, and picking a column another field uses moves it over. Continue stays disabled until every required field has a column, and the missing ones are named. Rows are checked while you map (big files in idle chunks, so ten thousand never freeze the page), so the review opens ready: filter All, With errors or Valid, read the red wavy underline and the tooltip on a bad cell, click it (or Tab to it, error cells are the only cells in the tab order, and press Enter) to fix it in place, where Enter saves and Escape cancels; the row number, status and skip button stay pinned while the table scrolls sideways. On a phone rows are cards and tapping a value opens a sheet with the field and its error, saved with one button. Skip single rows with the ban button, keep "Skip rows with errors" on to leave the rest out, and choose what happens to rows whose key already exists. Import opens straight on a progress bar that Cancel stops and that screen readers hear every 10%, then counts created, updated, skipped and failed rows in tiles, with a bouncing check when nothing failed, the rows that did not make it with their column and reason, a CSV of them with an Error column, and Import another file to start over; while an import runs, and after it ran, the steps stay where they are.

CSV Import is a Pro React component in the React form components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add csv-import, together with Alert, Badge, Button, Drawer, Empty State and 22 more and the npm packages libphonenumber-js, 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 csv-import

CSV Import 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 CsvImport from @/components/ui/csv-import.

import { CsvImport, type ImportField } from "@/components/ui/csv-import"

const FIELDS: ImportField[] = [
  { id: "company", label: "Company", required: true, aliases: ["Company name", "Customer"] },
  { id: "cui", label: "Tax ID (CUI)", type: "cui", aliases: ["CIF", "VAT ID"] },
  { id: "email", label: "Email", type: "email" },
  { id: "phone", label: "Phone", type: "phone" },
  { id: "balance", label: "Balance", type: "currency", aliases: ["Balance (USD)"] },
]

export function ImportCustomers({ existing }: { existing: string[] }) {
  return (
    <CsvImport
      fields={FIELDS}
      uniqueField="cui"
      existingKeys={existing}
      locale="en-US"
      dateFormat="MM/DD/YYYY"
      labels={{ upload: "File", map: "Columns", review: "Check", import: "Import" }}
      onImport={async (rows, { onProgress, signal }) => {
        const response = await fetch("/api/customers/import", { method: "POST", body: JSON.stringify(rows), signal })
        onProgress(rows.length)
        return response.json()
      }}
    />
  )
}

Props

  • fields*
    ImportField[]

    What you import: { id, label, type?, required?, aliases?, options?, validate?, transform?, example?, description?, currency? }. type is text, number, currency, date, email, phone, cui, iban, cnp, boolean or select; aliases are other column names that mean the field; currency is the ISO code of a currency field's amounts (else the component's).

  • onImport*
    (rows: Record<string, unknown>[], context: { strategy; onProgress: (done: number) => void; signal: AbortSignal; rows: ImportRow[] }) => Promise<ImportResult>

    Gets the valid, non-skipped rows with typed values (numbers, ISO dates, E.164 phones, formatted CUI and IBAN). Report progress, stop on the signal, resolve with { created, updated, skipped, failed?: { index, error }[] } where index is the file row from context.rows. A server can re-run the same checks with validateImportRows (see Parts).

  • defaultStepdefault "upload"
    "upload" | "map" | "review" | "import"

    The step it opens on. The demo loads its sample file for any step past the upload.

  • step
    "upload" | "map" | "review" | "import"

    The current step, controlled; pair it with onStepChange. A running import holds the wizard on its step.

  • onStepChange
    (step: ImportStepId) => void

    Called on every step change.

  • file
    File | null

    The picked file, controlled. Set it to start from a file you already have (a drop elsewhere on the page, a sample); pair it with onFileChange.

  • defaultFile
    File | null

    A file to start with, uncontrolled.

  • onFileChange
    (file: File | null) => void

    Called when a file is picked or removed.

  • acceptdefault ".csv,.tsv,.txt,.xlsx"
    string

    File types the drop zone takes, like the native attribute; the hint under the zone lists them. Old .xls files are refused with errors.unsupported.

  • maxFileSizedefault "20971520"
    number

    Bytes: 20 MB (20 × 1024 × 1024, so the hint reads a round "20 MB"). Bigger files are refused with errors.tooLarge; the hint under the drop zone shows the limit. 0 means no limit.

  • maxRowsdefault 10000
    number

    Rows read per sheet. The rest is cut and a warning says so.

  • uniqueFielddefault ""
    string

    The field that identifies a record (cui, email). Repeats inside the file become errors ("Duplicate of row 12"), rows found in existingKeys get an Exists badge, and the review shows the duplicates choice. The demo falls back to cui.

  • duplicateStrategy
    "skip" | "update" | "create"

    Controlled: what happens to rows whose unique value already exists. Pair it with onDuplicateStrategyChange; the review's select changes it.

  • defaultDuplicateStrategydefault "skip"
    "skip" | "update" | "create"

    What happens, while uncontrolled, to rows whose unique value already exists: left out (skip), passed on for an update, or added again (create). The review shows the choice once uniqueField is set; onImport gets it as context.strategy.

  • onDuplicateStrategyChange
    (strategy: DuplicateStrategy) => void

    Called when the review's select changes.

  • existingKeys
    string[] | Set<string>

    Values of uniqueField already in your system (compared without case and diacritics; CUIs with or without RO).

  • allowSkipInvaliddefault true
    boolean

    Shows the "Skip rows with errors" switch (on). Off: every error must be fixed, or its row skipped by hand, before Import unlocks.

  • inlineEditdefault true
    boolean

    Fix cells in the review: click a cell to edit it in place (a sheet on phones), and it is checked again at once.

  • defaultHasHeaderdefault true
    boolean

    The starting value of the upload step's "The first row is a header" switch. Off reads the first row as data and names the columns "Column 1"... (labels.columnLabel); CUI, IBAN, CNP, email, phone and date columns are then matched by their values.

  • templateFileNamedefault "import-template"
    string

    Name of the template CSV (the field labels and their examples, ";" separated where the locale writes decimal commas).

  • delimiterdefault ""
    string

    Forces the CSV separator. Empty detects it among , ; tab and |.

  • dateFormatdefault ""
    string

    A hint shown next to date fields in the mapping ("MM/DD/YYYY"). Parsing follows the locale, reads dotted dates day first, and accepts ISO and Excel dates.

  • defaultCountrydefault "RO"
    string

    Country for phone numbers written without a + prefix (libphonenumber-js). Valid numbers are stored as E.164.

  • currencydefault "USD"
    string

    ISO 4217 code for currency fields in the review ("$12,480.00"); a field's own currency wins. It only labels the amounts, nothing is converted.

  • stepperdefault "horizontal"
    "horizontal" | "vertical" | "progress" | "dots" | "none"

    form-wizard's header from 768px up; phones always get the compact progress row.

  • fullScreenOnMobiledefault true
    boolean

    Below 768px the wizard fills its container with a sticky header and footer and 48px buttons (form-wizard's). The container needs a height.

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

    Stepper markers, footer buttons, selects, switches and the filter.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    The active step, the drop zone's drag tint, the switches, the progress bar and the primary button.

  • localedefault ""
    string

    Parses and formats numbers, amounts and dates ("12,480.00" and 09/24/2026 in en-US, "12.480,00" and 24.09.2026 in ro-RO). Empty uses UIProvider's locale, then the browser's.

  • labels
    Partial<CsvImportLabels>

    Every string, English by default: step titles and descriptions, the drop zone (drop, browse, tapTitle, dropHere, fileHint(types, max), fileRejected(names)), template, templateHint, parsing, removeFile, sheet, hasHeader, hasHeaderDescription, columnLabel, rows, columns, delimiter(d), noFile, noFileDescription, chooseFile, the mapping (column, doNotImport, searchColumns, autoMatched, sample, required, usedBy, missingRequired, matched, dateFormat, types), the review (all, errorsFilter, withErrors, valid, skipInvalid, skipInvalidDescription, duplicates, duplicatesDescription, strategies, exists, new, skippedRow, status, errorCount, row, missing, emptyValue, yes, no, skipRow, includeRow, fix, save, cancel, validating, fixToContinue, nothingToImport, noMatches, noMatchesDescription, showMore, tableCaption, toImport), the run (importing(done, total), ready, readySkipped, cancelled, complete, completeWithErrors, completeDescription, importFailed, retry), the summary (created, updated, skipped, failed, failedRows, moreInReport, fieldError(field, error), downloadReport, importMore, reportError, reportFileName), truncated(max), form-wizard's next, back, start, progress and step(index, count), and errors { required, email, phone, cui, iban, cnp, number, date, select, boolean, duplicate(row), tooLarge, unsupported, empty, parse, tooMany }. Counted strings are (count, formatted) => string.

  • wizardClassNames
    FormWizardProps["classNames"]

    Classes for form-wizard's own parts (header, stepper, body, footer...).

  • classNames
    Partial<Record<"upload" | "dropzone" | "template" | "file" | "sheet" | "header" | "map" | "mapRow" | "mapSample" | "review" | "reviewFilter" | "reviewTable" | "reviewCard" | "duplicates" | "skip" | "run" | "progress" | "summary" | "tile" | "report" | "footer", string>>

    Extra classes for the inner parts, each also marked with data-slot="csv-import-<part>".

  • className
    string

    Classes for the wizard root (a form); other props spread onto it too.

Parts

  • validateImportRows

    function

    validateImportRows(rows, fields, mapping, options?) runs the wizard's checks on raw sheet rows without the UI: typed values, raw text and one message per bad cell, plus duplicates. Pure, so an API route can re-check an upload.

    • rows*
      readonly (readonly SpreadsheetCell[])[]

      The data rows of the sheet, header excluded.

    • fields*
      readonly ImportField[]

      The same field list the wizard got.

    • mapping*
      ImportMapping

      Field id to column index, null for fields not imported.

    • options
      ValidateImportOptions

      locale, errors, uniqueField, existingKeys, firstRowIndex (2 under a header) and override for inline fixes.

  • validateImportValue

    function

    validateImportValue(field, cell, context?) parses one cell with its field's type and the required check. Returns { value, error }.

  • IMPORT_VALIDATORS

    constant

    One pure parser per field type (text, number, currency, date, email, phone, cui, iban, cnp, boolean, select): raw cell in, { value, error } out.

  • DEFAULT_CSV_IMPORT_LABELS

    constant

    The English copy of every step and error message. Spread it into labels when you translate only a few keys.

  • ImportField

    type

    One target field: id, label, type, required, aliases (other column names), options for selects, validate, transform, example for the template, description and currency.

Examples

In a dialog

Opened from an Import button, dots as the stepper, starting on the mapping.

Products

A price list: 1,500.00 prices, VAT as 21% / 11% / 0%, units like hr and mo matched through keywords.

No header row

A semicolon separated file without a header: tax ID, email, phone and date columns are found by their values; pick the name column yourself.

Fix before import

Skipping is off: click a red cell to fix it, or skip its row, until Import unlocks.

Summary with a report

Runs on its own: every fifth row fails, and the report holds them with the invalid ones.

10,000 rows

Generate a big file: rows are checked in idle chunks and reviewed 50 at a time.

Phone

At 390px the wizard fills the screen, rows are cards and a tap opens the fix sheet.

Dependencies

libphonenumber-jsmotionlucide-reactalertbadgebuttondrawerempty-statefile-uploadform-wizardinline-editinputprogresssegmented-controlselectswitchtabletooltipuse-controllable-stateuse-localecommercedate-utilsfile-typesinvoicingmotionnumber-formatspreadsheettext-matchtonesui-config

Files

  • components/ui/csv-import.tsx

FAQ

Is the React CSV Import component free?

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

How do I install CSV Import?

Run npx wingo-ui@latest add csv-import. It also adds the Wingo UI items it builds on (Alert, Badge, Button, Drawer, Empty State and 22 more) and the npm packages libphonenumber-js, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/csv-import.

How does CSV Import behave on phones?

Drop a CSV, TSV or Excel file on the zone (or click it, press Enter or Space on it, or paste a file; on a phone the native picker opens) and a card shows its size, rows, columns, separator and encoding, or its sheet; turn off "The first row is a header" for files that start with data, pick the sheet of a workbook.

See also

SpreadsheetlibFile UploadForm WizardData TableInvoicinglib

More React form components

FieldInline EditInvoice line itemsRich Text EditorSchema BuilderAddress Input
All 9 React form components

Articles using this component

  • React CSV Importer: Upload, Map Columns, Fix Rows, Import

    Component guides · Oct 16, 2026 · 7 min read

PreviousBooking WidgetNext Field

Import customers

Acme CRM · Customers

  1. 1Upload
  2. 2Map columns
  3. 3Review
  4. 4Import
Step 1 of 4Upload

Upload

Upload

A CSV, TSV or Excel file with one record per row.

No file at hand?

uniqueField="cui"defaultDuplicateStrategy="skip"defaultHasHeader={true}maxRows={10000}delimiter=autodateFormat=""defaultCountry="RO"currency="USD"allowSkipInvalid={true}inlineEdit={true}templateFileName="import-template"fullScreenOnMobile={true}locale="en-US"