Interactive Pets

A playful draggable cat with feeding reactions and idle animations.

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

$ npx shadcn@latest add https://spark-ui-olive.vercel.app/r/interactive-pets.json

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

PropTypeDefaultDescription
petsPetConfig[]catThe pets to render. idleMessage accepts one string or an array of randomized click messages.
classNamestringAdditional classes for the outer wrapper.
playgroundClassNamestringAdditional classes for the playground area (e.g. a custom height).
showInstructionsbooleantrueWhether to show the instruction line below the playground.
instructionTextstringThe office. Drag them anywhere…Custom instruction text.
onPetMove(pet: PetType, position: { x: number; y: number }) => voidCalled after a pet is dropped or moved with the keyboard.
onPetFeed(pet: PetType) => voidCalled 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-live region.
  • A visible focus ring is shown on pets and food buttons.