Guides / Tutorials
A dog breed card with Tailwind CSS
By the Dog Image API team. Updated .
A breed card is a stage, a name and a credit. Put the transparent preview on a tinted square, add the breed name as a link to its page, and keep the Dog Image API credit link visible. This guide builds that card in HTML, then as a React component.
The idea
Dog Image API previews are transparent WebP files. A transparent image shows whatever is behind it, so the stage color is the card's whole background story. One tinted square behind the dog makes every card look alike, whatever the breed's coat color.
The card has four parts:
- A square stage with a tint, holding the image.
- The breed name, linking to the breed page.
- A short line saying what the picture is.
- The credit link.
If you have not set up Tailwind yet, add it to a Vite project with the official plugin: npm install tailwindcss @tailwindcss/vite, register tailwindcss() in vite.config, and put @import "tailwindcss"; at the top of your CSS file. In Tailwind 4, dark mode follows the visitor's system setting by default, which is what we use here.
The card in HTML
<article
class="overflow-hidden rounded-2xl border border-stone-200 bg-white shadow-sm
dark:border-stone-700 dark:bg-stone-900"
>
<div
class="flex aspect-square items-center justify-center bg-amber-50
dark:bg-stone-800"
>
<img
src="https://dogimage.dev/api/v1/images/pug/default/front-left?size=512"
alt="Pug, AI-generated illustration, front three-quarter view"
width="512"
height="512"
loading="lazy"
class="h-full w-full object-contain"
/>
</div>
<div class="space-y-1 p-4">
<h3 class="text-lg font-semibold text-stone-900 dark:text-stone-50">
<a
href="https://dogimage.dev/dogs/pug"
class="hover:underline focus-visible:underline"
>
Pug
</a>
</h3>
<p class="text-sm text-stone-600 dark:text-stone-300">
AI-generated illustration of an adult dog.
</p>
<a
href="https://dogimage.dev/dogs/pug"
class="inline-block text-xs text-stone-500 underline underline-offset-2
dark:text-stone-400"
>
Pug image: Dog Image API
</a>
</div>
</article>
Why these classes:
aspect-squarematches the square canvas of every preview, so the stage never changes shape while the image loads.object-containkeeps the whole image inside the stage. The dog fills at most 80 percent of its canvas, so a margin is already built in.overflow-hiddenwithrounded-2xlclips the stage to the card's corners.- The
dark:classes change only colors. The image is untouched.
The name link and the credit link point to the same breed page here, which is the form the site gives for the credit. The visible credit text is what the free license asks for: a link to Dog Image API next to the image or on the same page. See the license.
The same card as a React component
Cards repeat, so make the name and slug into props:
type DogBreedCardProps = {
slug: string;
name: string;
view?: string;
};
const API = "https://dogimage.dev";
export function DogBreedCard({ slug, name, view = "front-left" }: DogBreedCardProps) {
const page = `${API}/dogs/${slug}`;
return (
<article className="overflow-hidden rounded-2xl border border-stone-200 bg-white shadow-sm dark:border-stone-700 dark:bg-stone-900">
<div className="flex aspect-square items-center justify-center bg-amber-50 dark:bg-stone-800">
<img
src={`${API}/api/v1/images/${slug}/default/${view}?size=512`}
alt={`${name}, AI-generated illustration, ${view.replace("-", " ")} view`}
width={512}
height={512}
loading="lazy"
className="h-full w-full object-contain"
/>
</div>
<div className="space-y-1 p-4">
<h3 className="text-lg font-semibold text-stone-900 dark:text-stone-50">
<a href={page} className="hover:underline focus-visible:underline">
{name}
</a>
</h3>
<p className="text-sm text-stone-600 dark:text-stone-300">
AI-generated illustration of an adult dog.
</p>
<a
href={page}
className="inline-block text-xs text-stone-500 underline underline-offset-2 dark:text-stone-400"
>
{name} image: Dog Image API
</a>
</div>
</article>
);
}
Every class is written out in full. Tailwind finds classes by scanning your source for complete strings, so avoid building them from pieces such as `bg-${color}-50`, which the scanner would not see.
A responsive grid
const breeds = [
{ slug: "pug", name: "Pug" },
{ slug: "golden-retriever", name: "Golden Retriever" },
{ slug: "german-shepherd", name: "German Shepherd" },
{ slug: "beagle", name: "Beagle" },
{ slug: "dachshund", name: "Dachshund" },
{ slug: "samoyed", name: "Samoyed" },
];
export function BreedGrid() {
return (
<section className="mx-auto max-w-5xl bg-stone-50 p-4 dark:bg-stone-950">
<ul className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{breeds.map((b) => (
<li key={b.slug}>
<DogBreedCard slug={b.slug} name={b.name} />
</li>
))}
</ul>
</section>
);
}
One column on a phone, two from the sm breakpoint, three from lg. To fill the grid from the live catalog instead of a fixed list, fetch GET /api/v1/breeds, which needs no key and allows cross-origin reads. The React picker guide shows that request.
Dark mode, checked
Switch your operating system to dark mode and reload. The card flips to stone-900, the stage to stone-800, and the text to light colors. Dark coats stay readable against stone-800 in most cases, but look at your own breeds. If a black dog sinks into the stage, lighten the dark stage to stone-700.
To control the theme with a toggle instead of the system setting, Tailwind 4 lets you redefine the dark variant with @custom-variant in your CSS. The Tailwind documentation covers it.
Other angles
Pass view="head" for a head portrait that fits small cards in a list, or view="left" for a side profile. The left side gallery shows the whole catalog from that angle, and the docs list every view id.
Questions
Why put the dog on a tinted stage?
The preview has a transparent background, so it takes the color behind it. A soft tint gives the dog an even backdrop and keeps every card in a grid looking the same.
Do I need to change anything for dark mode images?
No file changes. The image stays transparent, so only the stage color changes. The dog is an illustration with its own colors, so check that dark coats such as black Poodles still read on your dark stage.
Which Tailwind version is this written for?
Tailwind CSS 4, where you add one CSS import and the classes work. Every class used here also exists in version 3.
Next steps
- API docs: endpoints, keys, limits and errors.
- All dog breeds, each with nine views.
- Pricing for full-size transparent PNG files.
- A dog breed gallery in SvelteKit
- A dog breed picker in Angular
- A dog breed selector in Vue 3
- Add dog breed images to a Next.js app