Import Preview

Previews a CSV or spreadsheet import, finds the real heading row and maps columns, so a bad file doesn't create junk records.

StopsBad CSV import created junk records

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/import-preview.json

The files land in your project. They're yours to edit.

Usage

import-preview-usage.tsx
import { ImportPreview, type ImportField } from "@/components/ui/import-preview"

<ImportPreview
  fileName="contacts-oct.csv"
  rows={rows}
  fields={fields}
  uniqueKey="email"
  isExisting={(key) => existing.has(key)}
  onImport={async (records) => {
    await new Promise((r) => setTimeout(r, 600))
    setDone(records.length)
  }}
/>

What it catches

  • Title lines above the real headings, and headings about to be imported as a record
  • Columns matched by heading or by their values, with a confidence
  • Required fields left unmapped: import is held
  • Duplicates in the file and against existing records
  • Repeated headings and cells that fail a field's check

Props

ImportPreview props
PropTypeDefault
rows* (required)

The parsed file, one array of cells per line, as CSV/XLSX parsers return it.

string[][]—
fields* (required)

The fields you import into.

ImportField[]—
fileName

File name shown in the header.

string—
uniqueKey

Field that identifies a record, for duplicate checks (compared trimmed and case-insensitively).

string—
isExisting

Whether a record with this (lower-cased) key already exists.

(key: string) => boolean—
onImport

Receives the clean records. Return a promise to show a pending state.

(records: Record<string, string>[]) => void | Promise<void>—
importLabel

Templates: `{count}` and `{skip}` are filled in.

string"Import {count}"
skipLabel

Template for the skip count; {skip} is filled in.

string"skip {skip}"
notMappedLabel

Option text for a column that won't be imported.

string"Don't import"
requiredText

Shown while a required field is unmapped.

string"{field} is required. Pick the column that holds it."

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Each column mapping select has a label in a labelled list
  • Status and counts are in polite live regions; unmapped required fields use role=alert
  • Skipped-line groups expand with aria-expanded

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Validate rows and enforce uniqueness constraints on the server during the import.

  • Needs rows already parsed; it doesn't read CSV or XLSX itself
  • Value-based matching is a guess; check low-confidence columns
  • Duplicate check uses one key field, trimmed and case-insensitive

What it stops

Bad CSV import created junk records

Also known as

csv import · excel upload · bulk upload · column mapping · import contacts · xlsx · papaparse · data import

Field type

Dependencies

Pairs well with