Skip to content

Import mapper

Bring a CSV into an app: choose a file, match its columns to your fields, fix only the rows with problems and import.

import { ImportMapper } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use an Import mapper where people bring a file of records into your app: contacts, orders, stock. For a table that only shows data, use the Data table; for the other direction, the Export dialog.

Describe what can be imported with fields (an id, a label, a type of text, email, number or date, required, and synonyms the matching also tries), say which one tells whether a record already exists with keyField (it is found for you from the first email or required field, and can be changed under More options), and pass the values of it that already exist in existing. Nothing leaves the browser until onImport(rows, context) is called with the rows to create or update (each has its row number, its values by field id and an action); resolve with the counts if your server's differ, reject to stop with the message shown, and report progress with context.onProgress(done). Without onImport a short demo wait stands in, and without fields the card shows the demo's six contact fields.

<ImportMapper
  fields={[
    { id: 'sku', label: 'SKU', required: true, synonyms: ['item code'] },
    { id: 'qty', label: 'Quantity', type: 'number' },
  ]}
  keyField="sku"
  existing={await loadSkus()}
  onImport={async (rows, { onProgress }) => {
    for (const [i, row] of rows.entries()) {
      await save(row)
      onProgress(i + 1)
    }
  }}
/>

Reference

Propsfields, keyField, existing, onImport, variant, copy…

Props

ImportMapper props
PropType
themeThemeInput

Wraps the block in this theme. Default: the theme around it.

–
embeddedboolean

A panel inside another page: an h2 instead of the page's h1, and no page around it.

default true

true
variant'page' | 'dialog'

page (default): one working screen, up to 1024px wide. dialog: the compact version for a ~520px dialog: the file and the column list with a footer of Start over / Continue, and a second step for what needs attention before the import.

default 'page'

'page'
fillboolean

Take the whole width it's given instead of stopping at its natural width (1024px at 100% scale; 512px in the dialog variant).

default false

false
headingLevel1 | 2 | 3

The title's level. Default: 2 when embedded, else 1.

–
copyPartial<ImportMapperCopy>

Any of the block's strings, to rewrite or translate. Default: the dictionary's (YoujProvider's labels).

–
localestring

Writes counts and sizes in this locale (BCP 47). Default: the app's locale (YoujProvider), else en-US.

–
fieldsImportField[]

The fields a CSV column can be imported as. Default: the demo's six contact fields.

–
keyFieldstring

The field that tells whether a contact already exists. Default: the first email field, else the first required field.

–
existingreadonly string[]

Values of the key field that already exist in your app; a row with one of them is a duplicate. Default: the demo's three, else none.

–
maxSizenumber

The largest file, in bytes.

default 5 * 1024 * 1024

5 * 1024 * 1024
previewRowsnumber

The most cards a review lists at once; the counts and the import always cover every row.

default 100

100
sampleboolean

Offers a built-in sample file ("Use sample"). Default: true with the demo's fields, else false.

–
reportNamestring

The skipped rows file's name.

default 'import-errors.csv'

'import-errors.csv'
onDone() => void

Shows a Done button on the result; called when it is pressed.

–
onImport(rows: ImportRow[], context: ImportContext) => Promise<ImportResult | void> | ImportResult | void

Receives the rows to create or update. Resolve (optionally with the counts) when they're in; reject to stop with the message shown. Default: a short demo wait.

–
Other props pass through: extends ComponentProps<'section'>.

The CSV reader

The reader is the component's own and follows RFC 4180: quoted values may hold the delimiter, line breaks and doubled quotes, any line ending works, a byte-order mark is dropped and blank lines are skipped. The delimiter (comma, semicolon or tab) is found for you. Rows with too many or too few values are padded or cut and listed; a quote that never closes keeps you on the drop zone with the line named. Dates are read as 2026-03-14 or as day-first or month-first with slashes; numbers may carry thousands commas.

LabelsEvery string, in every built-in language

Labels

Every string is a core label (importMapper…) in every built-in language, so the provider's locale or labels (see Internationalization) rewrites them, and copy rewrites any of them for one instance. The noun in "Import 44 contacts" is importMapperItems (and importMapperDuplicatesFound), so rewrite those with copy for orders or SKUs. onDone adds a Done button to the result.

AccessibilityFocus, announcements, motion

Accessibility

  • The card is a section named by its title. A screen change (file, mapping, result) crossfades and moves focus to the first thing in the new screen; in the dialog variant, Continue and Back move focus to the control that replaces them. With reduced motion the screens swap at once.
  • Each column's field select is named by its column; a guessed match says "Check" in text, and a required field with no column is a status line naming it, with its own picker.
  • Counts, skipped rows and the import's progress are announced in a polite live region, and a failure is shown as text.
  • Review opens a sheet where each wrong value is an input with its error described on it (aria-invalid and aria-describedby).