ComponentsFlows & wizards

Import wizard

Import CSV or XLSX into any collection: upload (encoding and delimiter sniffing, sheet, header row, the file's number/date format) → column mapping (locale-folded auto-mapping and saved templates) → review with inline fixes → duplicate strategy, dry run and a chunked, cancellable import → report with a failed-rows download. papaparse and read-excel-file load only when a file is read.

Multi-step form · File upload · papaparse · read-excel-file business/import-wizard.tsx, business/import-core.tstheme · material · accent · density · font
  1. Step 1 of 4, Upload, Current
  2. Step 2 of 4, Map columns
  3. Step 3 of 4, Review
  4. Step 4 of 4, Import

Step 1 of 4Upload

Step 1 of 4: Upload

CSV or Excel. The first row should hold column names.

Download a sample file

Installation

$ pnpm add @vamos-ui/react

The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";

Usage

import { ImportWizard, type ImportField } from "@vamos-ui/react/business";

const fields: ImportField[] = [
  { id: "name", label: "Full name", aliases: ["Ad Soyad"], required: true, type: "text" },
  { id: "email", label: "Email", aliases: ["E-posta", "Mail"], required: true, type: "email" },
  { id: "value", label: "Deal value", type: "money" },
];

<ImportWizard
  fields={fields}
  dedupeKeys={["email"]}
  onImport={(rows, { signal, strategy, dryRun, onProgress }) => api.contacts.bulk(rows, { signal, strategy, dryRun })}
/>

The drop zone is a real file button. Review counts are aria-live, error cells are labelled inputs ("Email, row 2") with their message linked, progress is a progressbar and the report has a focused heading, summary text and a table.

Examples

Locale-pack validators

HR bulk import: TCKN and IBAN checked with the tr pack, "01.12.2026" and "96.000,00" parsed in the file's Turkish format. Fix bad cells inline; F8 jumps to the next error.

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

Step 1 of 4Upload

Step 1 of 4: Upload

CSV or Excel. The first row should hold column names.

Download a sample file

Saved mapping templates

Headers that don't auto-map are mapped once and saved; templates.list() offers them next time.

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

Step 1 of 4Upload

Step 1 of 4: Upload

CSV or Excel. The first row should hold column names.

Download a sample file

Windows-1254 semicolon CSV

Download the file and drop it in: the encoding and ; delimiter are sniffed, so Turkish letters come out right. If a file still looks garbled, a banner offers Windows-1254.

Download a Windows-1254 file
  1. Step 1 of 4, Upload, Current
  2. Step 2 of 4, Map columns
  3. Step 3 of 4, Review
  4. Step 4 of 4, Import

Step 1 of 4Upload

Step 1 of 4: Upload

CSV or Excel. The first row should hold column names.

API

ImportWizard

PropTypeDefault
fields
{ id, label, aliases?, required?, type, options?, validate?(v, row), normalize?(v) }. type: text · number · money · date · boolean · enum · email · phone · id · iban.
ImportField[]—
onImport
Called per chunk with typed values. ctx = { signal, onProgress, strategy, dryRun, offset }; failed rows index into the chunk.
(rows, ctx) => Promise<{ ok; failed: { row; error }[] }>—
dedupeKeys
Fields that identify a record: duplicates in the file become warnings and the duplicate strategy (skip / update / create) is offered.
string[]—
chunkSize
Rows per onImport call; cancel stops between chunks.
number200
maxRows / maxSize
Limits checked on upload.
number50,000 / 20 MB
templates
Saved column mappings.
{ list(): Promise<MappingTemplate[]>; save(t): Promise<void> }—
sampleFile
"Download a sample file" link.
{ name; href }—
fileLocales
Choices for the file's number/date format (defaults to the UI locale).
{ value; label }[]—
previewLimit
Rows rendered in the review table.
number200
onDone
Shows a Done button on the report.
(report: ImportReport) => void—
labels
Built-in strings (importWizard namespace).
Partial<ImportWizardLabels>—

ImportMapping / ImportReview

PropTypeDefault
ImportMapping
The mapping list on its own: one labelled Select per column.
{ headers, sample?, fields, mapping, onMappingChange }—
ImportReview
The validation preview on its own.
{ results, fields, onlyErrors, onOnlyErrorsChange, errorCount, warningCount, onFix? }—

core (worker-safe)

PropTypeDefault
sniffCsv(bytes)
UTF-8 vs Windows-1254 / 1252, and , ; or tab.
{ encoding, delimiter, hasBom }—
autoMap(headers, fields, locale)
Folded exact, then contains matching (tr "E-posta" ≈ "eposta").
Record<column, fieldId | null>—
parseCell(v, field, locale)
"1.234,50", "10.11.2026", Excel serial dates, yes/no/evet/hayır.
{ value, error? }—
validateImport(rows, mapping, fields, { locale, dedupeKeys })
Typed values, errors and warnings per field.
ImportRowResult[]—