@motorhub.ai/escudito
v0.6.0
Published
Car make badges for React and Astro
Readme
🛡️ escudito
Car and motorcycle brand badges for React and Astro.
One component. 340 brands. Light and dark themes. Fully typed.
Why escudito
- 🎨 340 brands, 341 badges — hand-cleaned SVGs, no wordmarks where the mark stands on its own.
- 🌗 Light and dark — 102 brands ship dedicated dark artwork; the rest fall back gracefully.
- 🔤 Case-insensitive — pass
"FORD"or"ford". MotorHubBrandenum values drop straight in. - 📦 Pay for what you use — badges are lazily code-split, and in Astro they render to inline SVG with zero client JS.
- 🧠 Typed end to end —
MakeandVariantare unions, so your editor autocompletes every brand.
Install
bun add @motorhub.ai/escuditonpm install @motorhub.ai/escudito
pnpm add @motorhub.ai/escudito
yarn add @motorhub.ai/escuditoPeer dependency: react >= 18. In Astro, add the React integration once with npx astro add react.
Quick start
⚛️ React
import { Badge } from "@motorhub.ai/escudito";
export function Car() {
return <Badge make="ford" />;
}🚀 Astro
---
import { Badge } from "@motorhub.ai/escudito";
---
<Badge make="ford" />Astro renders the badge on the server: the SVG lands in the HTML and no JavaScript reaches the browser. Leave off any client: directive — a badge is static, it has nothing to hydrate.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| make | Make \| Uppercase<Make> | required | The brand. Case-insensitive, so "ford", "Ford" and "FORD" all resolve to the same badge. |
| variant | Variant \| Uppercase<Variant> | "logo" | Which artwork to use. Almost every brand only has "logo" — see Variants. |
| theme | "light" \| "dark" | "light" | Picks the dark artwork when the brand has one. See Theming. |
| size | number | 64 | Width in px. Height follows the SVG's own aspect ratio. See Sizing. |
| className | string | — | Forwarded straight to the root <svg>. Note it is className, not class, in Astro too. |
Nothing else is forwarded to the DOM — style the badge through className.
<Badge make="PORSCHE" variant="logo" theme="dark" size={96} className="shrink-0 opacity-90" />Theming
<Badge make="eagle" theme="light" />
<Badge make="eagle" theme="dark" />theme="dark" swaps in a separate, hand-made artwork — not a CSS filter — so black outlines become legible on dark surfaces instead of disappearing.
102 of the 340 brands currently have dark artwork. For the rest, theme="dark" renders the light artwork unchanged: it never throws and never blanks out, it just doesn't recolour. Most of those are brands whose logo is already colourful enough to read on a dark background.
Driving it from your own theme state is just a prop:
const theme = useTheme(); // "light" | "dark"
<Badge make={car.brand} theme={theme} />Sizing
size sets the width in pixels. Height is derived from each SVG's own viewBox, so badges are never stretched:
<Badge make="bmw" size={32} /> {/* 32 × 32 */}
<Badge make="ford" size={64} /> {/* 64 × 23 — Ford's oval is wide */}Because brands have wildly different aspect ratios, put the badge in a fixed box when you need a tidy grid:
<div className="flex h-16 w-16 items-center justify-center">
<Badge make={car.brand} size={64} />
</div>Brands
340 brands are bundled, from alfa_romeo to zotye, covering cars, trucks and motorcycles. Import the types to get autocompletion and compile-time checks:
import type { Make, Variant, Theme, BadgeProps } from "@motorhub.ai/escudito";Because make accepts either casing, a value straight out of the MotorHub API needs no transformation:
const { brand } = vehicle; // "MERCEDES_BENZ"
<Badge make={brand} size={40} />If a make / variant pair isn't in the package, Badge renders nothing — no exception, no broken-image box — so an unexpected brand from an API can never take a page down.
Missing a brand? Open an issue with the make and a source SVG.
Variants
Most brands have a single "logo" variant, which is the default. Brands with more than one:
| Make | Variants |
|---|---|
| lamborghini | logo (the shield) · text (the wordmark) |
<Badge make="lamborghini" variant="text" size={140} />Accessibility
Every badge renders as an image with an accessible name, so screen readers announce the brand instead of skipping an anonymous <svg>:
<svg role="img" aria-label="Ford badge" ...>If the badge sits next to the brand name in text, mark it decorative with aria-hidden on the wrapper to avoid reading the brand twice.
How it loads
React — each badge is its own dynamic import behind React.lazy, so your bundler splits them into separate chunks and the browser only downloads the brands actually rendered. Loading is wrapped in a Suspense boundary with an empty fallback: a badge that hasn't arrived yet occupies no space, then appears.
Astro — the same component renders on the server, so the SVG is inlined into the HTML and no JavaScript is shipped to the client. React is a build-time dependency there, not a runtime one.
License
MIT © MotorHub
