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:
- Rejected locally -- charset, length, or check digit failed; no network.
- Transport / upstream failure -- timeout, 429, 5xx, unparseable body.
- 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,
};
}
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>
);
}
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:
- All primary fields present, most secondary missing.
- Only ModelYear present.
- All shown fields missing (decoded but empty catalog row)--still
Decoded, with copy that no attributes were returned. - Local validation error (no decode card at all).
- 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)