The make/model picker you stopped hand-maintaining. make-model-dropdown turns two <select> elements into a dependent make → model dropdown, populated live from the VehiclesDB API: 14,000+ models across 850+ makes, six vehicle kinds — cars, motorcycles (motorbikes), mopeds, vans, trucks and buses — reconciled from official national vehicle registers and refreshed monthly. One kind option turns the same widget into a car picker, a motorcycle picker or a truck picker.
Zero dependencies. No build step. Works with plain HTML forms, React, Vue, Rails, anything that renders a <select>.
Get a free publishable API key at vehiclesdb.com (it is safe to ship in page source; lock it to your domain in Settings), then:
<select id="make"></select>
<select id="model"></select>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script>
VehiclesDropdown.attach({
key: "vdb_pk_your_key",
make: "#make",
model: "#model"
})
</script>That is the whole integration. The make list loads once (~8 KB gzipped), the model list loads when a make is picked, and both selects submit with your form like any other field.
npm install make-model-dropdownimport VehiclesDropdown from "make-model-dropdown"
VehiclesDropdown.attach({ key: "vdb_pk_your_key", make: "#make", model: "#model" })VehiclesDropdown.attach({
key: "vdb_pk_…", // required — your publishable key
make: "#make", // required — <select> element or selector
model: "#model", // optional — the dependent model <select>
year: "#year", // optional — the model-year <select> (needs `model`)
kind: "car", // only one kind: car, motorcycle, moped, van, truck, bus
country: "de", // only vehicles evidenced in a register country
order: "popular", // models by real-world registrations (default) or "alphabetical"
topMakes: 40, // only the N most-registered makes, best first (default: all)
initialMake: "audi", // preselect a make on load (slug or name)
initialModel: "a3", // preselect a model once its make's models load
initialYear: 2019, // preselect a year once its model's years fill
yearFallback: "range", // a model with no register span: "range" (default) or "none"
values: "slug", // option values: "slug" (default) or "name"
includeOther: true, // append "Other / not listed" — pickers should never dead-end
otherLabel: "Other / not listed", // its text, to translate; the value is always "other"
search: true, // searchable combobox instead of a plain select
logos: true, // make logos in the list (search mode; free key feature)
placeholderMake: "Make",
placeholderModel: "Model",
placeholderYear: "Year",
attribution: true, // the CC-BY credit line (keep it on — see below)
onChange: ({ make, model, year }) => {}, // `year` only when a year select is attached
onError: (error) => {}
})attach returns an instance with .value ({ make, model }, plus year with a year select) and .destroy().
Booking and quote forms ask for the year, so the widget can fill a third <select>:
VehiclesDropdown.attach({ key: "vdb_pk_your_key", make: "#make", model: "#model", year: "#year" })Picking a model fills the year select from that model's span, newest first (most bookings are for recent cars); changing the make clears model and year together. onChange then reports { make, model, year }, where year is a number, null when nothing is picked, or "other" for the includeOther escape hatch, exactly like make and model. Without year, nothing changes: the payload stays { make, model }.
The years are register years, not production years: the span runs from the first to the latest year the official registers have the model on record, which can differ from the factory's own dates.
About one model in eight has no span on record (and an "Other / not listed" model never has one). yearFallback decides what those models get:
"range"(default): the current year back to 40 years ago. The year field never dead-ends, so a customer whose model the registers cannot date can still book."none": no years, for forms that would rather not offer years the registers cannot back up. WithincludeOtherthe select stays enabled with the placeholder and Other, so that choice can still be made; without it there is nothing to choose, so the select is disabled. The widget only ever undoes its own disabling and never overrides adisabledyour page sets.
A year the host supplies (initialYear, or a server-rendered selected option in an edit form) is never dropped: if it falls outside the model's span it is added to the list in its place, selected, and logged once with console.warn.
No extra request is made: the spans come with the model list the widget already loads.
With logos: true (in search mode) each make renders its badge next to its name in the list, and the selected make's badge sits inside the input, the way a phone input wears its country flag. Coverage is partial by design and any marque can be withdrawn at any time, so the library always falls back to an initial-letter chip — build nothing that assumes a logo exists. Logos are third-party trademarks served for referential display; no license is granted with them.
The combobox input inherits your <select>'s CSS classes, so it dresses like the rest of your form with no extra styling.
The data is the point. Every model carries a global popularity decile from real registration counts (order: "popular" puts the Golf first, not the 412), a per-country availability record (country: "de" drops what Germany has never registered), and a kind: kind: "motorcycle" gives you a motorbike picker with displacement-level names ("CBR600RR", not "CBR"), because that is how riders and registers both speak; kind: "truck" or kind: "van" gives fleet and logistics forms the commercial-vehicle picker they never had; kind: "bus" and kind: "moped" round out the six.
The vehicle identity data is CC-BY 4.0 — free for anything, with visible credit. The widget renders the credit line for you; leave attribution: true and you are compliant without reading a license. Paid plans can waive display attribution.
The widget speaks the documented Dropdown Data endpoints: GET /api/v1/makes, GET /api/v1/makes/{slug}/models, and make logos. (GET /api/v1/search is part of the same free surface if you build your own typeahead.) They cost 0 credits and accept publishable keys, so the dropdown never spends your quota. Public keys are deliberately refused everywhere else. Use a private vdb_sk_… key from your backend for the rest of the VehiclesDB API: resolving free-text vehicle strings, full records, imagery, and license-plate validation.
npm ci
npm testThe test suite runs the published UMD artifact in a real DOM implementation and covers dependent loading, the year step and its fallbacks, credential-scoped caches, combobox keyboard/blur behavior, labels, callback isolation, and object-URL cleanup.
MIT for this library. Data: identity layer CC-BY 4.0, enrichment proprietary — see vehiclesdb.com/attribution.
- Bump
versioninpackage.jsonAND theversion:string inmake-model-dropdown.js, keep them equal. - Re-vendor the dist into the website and verify the two files are byte-for-byte identical. The website's system test drives that copy against the real API, so a broken dist fails its CI.
npm publish(owner ritual, like gem releases).