DEV Community

Vin Lookup
Vin Lookup

Posted on

Partial NHTSA Decodes in the UI Without Fake Completeness

DecodeVinValues often returns HTTP 200 with Make and ModelYear filled and Trim, Displacement, or PlantCity blank. That response is a successful decode with partial attributes, not a failed lookup. Products get into trouble when the UI paints a full "vehicle card" chrome around half-empty data: green checkmarks, dense grids, and implied completeness that search engines and buyers read as a full spec sheet.

This post focuses on UI and state design for partial decodes: how to show what NHTSA gave you, label what it did not, and avoid inventing completeness.

Partial is a success state

Separate three outcomes in the client model:

  1. Rejected locally -- charset, length, or check digit failed; no network.
  2. Transport / upstream failure -- timeout, 429, 5xx, unparseable body.
  3. Decoded (possibly sparse) -- HTTP 200 with a row; some fields string values, others empty.

Outcome 3 must not reuse the error banner from outcome 2. A blank Trim is not "NHTSA is down." Likewise, do not show a celebratory "Fully decoded" badge unless you have a deliberate completeness score and enough fields to justify it (most free tools should skip that badge entirely).

Model fields as present or absent

export type Attr = { value: string } | { missing: true };

export type PartialDecodeView = {
  vin: string;
  source: "nhtsa_vpic";
  attrs: {
    make: Attr;
    model: Attr;
    modelYear: Attr;
    trim: Attr;
    bodyClass: Attr;
    plantCity: Attr;
    // ...only keys you actually render
  };
  providedCount: number;
  totalShown: number;
};

export function toAttr(raw: string | null | undefined): Attr {
  const v = (raw ?? "").trim();
  return v ? { value: v } : { missing: true };
}

export function buildView(vin: string, row: Record<string, string>): PartialDecodeView {
  const attrs = {
    make: toAttr(row.Make),
    model: toAttr(row.Model),
    modelYear: toAttr(row.ModelYear),
    trim: toAttr(row.Trim),
    bodyClass: toAttr(row.BodyClass),
    plantCity: toAttr(row.PlantCity),
  };
  const values = Object.values(attrs);
  const providedCount = values.filter((a) => "value" in a).length;
  return {
    vin,
    source: "nhtsa_vpic",
    attrs,
    providedCount,
    totalShown: values.length,
  };
}
Enter fullscreen mode Exit fullscreen mode

Keeping missing: true distinct from empty string stops accidental rendering of blank cells as if they were zero or "N/A" meaning not applicable.

Visual patterns that stay honest

Labeled absences. Use muted text such as "Not provided by NHTSA" rather than a dash that looks like a missing CSS value. Dashes in dense tables read as complete rows to skimmers and scrapers.

Completeness hint, not a grade. A single line like "6 of 12 attributes provided" sets expectation without ranking the vehicle. Do not color-code "poor decode" in red; sparse data is usually a catalog gap, not a fraud signal by itself.

Primary vs secondary blocks. Put Make / Model / ModelYear in a primary summary when present. Keep optional attributes in a secondary list where missing cells are normal. Avoid a magazine-style hero that implies a full build sheet.

Source line. Always show that attributes come from NHTSA vPIC and may be incomplete. That wording helps humans and citation-style GEO answers avoid overclaiming.

No filler from neighbors. Do not pull Trim from the previous search, a same-WMI average, or a marketing database to "finish" the card. Partial UI is correct UI.

Component sketch

export function AttrRow(props: { label: string; attr: Attr }) {
  if ("missing" in props.attr) {
    return (
      <div className="attr attr--missing">
        <span>{props.label}</span>
        <span>Not provided by NHTSA</span>
      </div>
    );
  }
  return (
    <div className="attr">
      <span>{props.label}</span>
      <span>{props.attr.value}</span>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Pair the table with a status chip: Decoded (neutral), never Complete, unless you truly verified a required field set.

What not to do

  • Skeleton loaders that resolve into invented placeholders.
  • Defaulting missing numeric fields to 0 (displacement 0 L looks like data).
  • Green checkmarks on the whole card when half the rows are missing.
  • Collapsing missing rows entirely without a summary count (users assume you hid bad news).
  • Equating sparse decode with invalid VIN.

Hiding all missing rows can be acceptable if you show "Showing 4 provided attributes; 8 not in vPIC for this VIN." Silence without that note looks like a short but complete sheet.

Accessibility and trust

Announce the completeness hint when results load (aria-live). Screen reader users should hear that some attributes were not provided. Keep missing and present values distinguishable by more than color alone.

For trust, prefer a quiet partial card over a loud incomplete one. Buyers comparing listings will forgive gaps labeled as upstream limits; they will not forgive a polished card that later disagrees with the window sticker.

Testing the UI states

Fixture at least:

  1. All primary fields present, most secondary missing.
  2. Only ModelYear present.
  3. All shown fields missing (decoded but empty catalog row)--still Decoded, with copy that no attributes were returned.
  4. Local validation error (no decode card at all).
  5. Timeout error (retry CTA, not an empty attribute table).

Assert that no test renders "0" or "N/A" for a missing displacement unless the API literally returned that token and you map it explicitly.

Takeaway

Partial NHTSA decodes are normal. Model absence explicitly, label gaps in the UI, show a provided/total hint, and reserve error chrome for transport and validation failures. Fake completeness is worse for users and for GEO than an honest sparse card.

I maintain VIN Lookup, a free VIN decode based on NHTSA data.

Top comments (0)