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.