The Interactive Pets component renders a small bounded playground with a cat. It can be dragged, clicked for a random message, and fed with a bone.
Beta: Interactive Pets is still in beta. Richer idle animations—such as moving tails, blinking eyes, and other small reactions—are coming soon.
Installation
Usage
import { InteractivePets } from "@/components/interactive-pets";"use client";
export default function InteractivePetsDemo() {
return (
<div className="w-full max-w-xl">
<InteractivePets
pets={[{ id: "cat", name: "Mochi" }]}
instructionText="Click Mochi to chat or tap the bone to feed."
/>
</div>
);
}Examples
Cat — Mochi
Mochi the cat, with a bone as the feeding action.
Custom Messages
Override any pet's name, starting position, or messages. Pets you omit are left out entirely.
<InteractivePets
pets={[
{
id: "cat",
name: "Nimbus",
idleMessage: ["hello.", "still watching."],
fedMessage: "an acceptable offering.",
fullMessage: "i'm full now.",
},
]}
/>
After the third feeding, the pet shows fullMessage instead of fedMessage.
Custom Position
initialPosition sets a pet's starting position in pixels from the playground's top-left corner.
<InteractivePets
pets={[
{ id: "cat", initialPosition: { x: 24, y: 40 } },
]}
/>
The bone sits along the bottom by default. Use the data-food-controls styling hook when you need a different position:
<InteractivePets playgroundClassName="[&_[data-food-controls]]:top-6 [&_[data-food-controls]]:bottom-auto" />
Show a Pet Across the Entire Site
Mount InteractivePets inside app/layout.tsx with a fixed, transparent, full-viewport playground. The wrapper ignores pointer events while the pet and bone remain interactive, so the page underneath still works.
import { InteractivePets } from "@/components/interactive-pets";
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body>
{children}
<InteractivePets
pets={[
{
id: "cat",
name: "Mochi",
initialPosition: { x: 24, y: 80 },
idleMessage: ["Hello!", "Welcome back."],
fedMessage: "Thank you!",
},
]}
showInstructions={false}
className="pointer-events-none fixed inset-0 z-40"
playgroundClassName="h-dvh rounded-none border-0 bg-transparent sm:h-dvh"
/>
</body>
</html>
);
}
Because it lives in the root layout, the pet stays visible and draggable across page navigation. Change initialPosition to choose where it starts.
Event Callbacks
onPetMove fires after a drag ends or a keyboard move, with the pet's position in pixels relative to the playground. onPetFeed fires when a food button is pressed.
<InteractivePets
onPetMove={(pet, position) => console.log(`${pet} moved to`, position)}
onPetFeed={(pet) => console.log(`${pet} was fed`)}
/>
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
pets | PetConfig[] | cat | The pets to render. idleMessage accepts one string or an array of randomized click messages. |
className | string | — | Additional classes for the outer wrapper. |
playgroundClassName | string | — | Additional classes for the playground area (e.g. a custom height). |
showInstructions | boolean | true | Whether to show the instruction line below the playground. |
instructionText | string | The office. Drag them anywhere… | Custom instruction text. |
onPetMove | (pet: PetType, position: { x: number; y: number }) => void | — | Called after a pet is dropped or moved with the keyboard. |
onPetFeed | (pet: PetType) => void | — | Called when a pet's food button is pressed. |
Accessibility
- Each pet is focusable and exposes an accessible label. Enter or Space shows a random message.
- Focused pets move with the arrow keys (8px per press, 24px with Shift) and stay inside the playground.
- Food controls are native buttons with descriptive
aria-labels. - Feeding reactions are announced through a polite
aria-liveregion. - A visible focus ring is shown on pets and food buttons.