Project · Lakshay Mehandru · Sep 2026

tabpet

A small animal that sits on your iOS tab bar, runs to the tab you pick and chases your finger when you drag along the bar. It's open source, for React Native or Swift, and the bar below is a web version you can play with.

React NativeSwiftExpoOpen Source

Home

Animal

Tap a tab, or drag along the bar.

A pet on the tab bar

Why would anyone put a panda on a tab bar?

I did it for an app only I use. For a long time it felt like a spreadsheet with a nicer font. Then I gave it a panda. The panda sits on the tab bar, on whichever tab I'm looking at. Tap another tab and it runs over and plops down. That was the whole feature, and it changed how often I opened the app. I wanted to see what the panda was doing.

So I pulled it out into a package. It's called tabpet. It's open source, for React Native or Swift, and the code is at github.com/Lakshay1800/tabpet.

What it does

It sits where you are. It runs when you switch tabs. It chases your finger when you drag along the bar. It climbs off the end of the pill, runs upside down along the bottom, and climbs back up the other side.

On the iOS 26 floating bar: a panda running to each tapped tab, then chasing a drag along the bar while the pill slides with it.

Six animals ship with it: panda, cat, turtle, raccoon, bird, squirrel. Each one is three short video clips cut into sprite sheets, plus a profile of springs and speeds. The turtle never hops. The raccoon takes the long way round.

It comes two ways. An Expo app installs react-native-tabpet from npm. It needs Expo SDK 57+, the New Architecture and a dev build, and it is iOS only.

npx expo install react-native-tabpet react-native-reanimated react-native-gesture-handler react-native-worklets expo-image react-native-safe-area-context
npx expo prebuild --clean --platform ios
npx expo run:ios

A UIKit or SwiftUI app adds the repo as a Swift package and gets the same six animals. In Xcode, add https://github.com/Lakshay1800/tabpet, then TabPetUIKit and one product per animal, or TabPetAnimals for all six.

The part that's hard

React Native can't draw on the native tab bar. It can draw over it, but it has no idea where the icons are. On iOS 26 that got worse, because the tab bar is now a floating glass pill that's narrower than the screen, and the selection bubble keeps its own copies of the selected button.

I found that out the expensive way. My first measurement matched the wrong button class and returned nothing, so the code fell back to splitting the width evenly. The middle tab looked fine. The end tabs were off by ten points. Then I counted the bubble's copies as buttons, and every seat landed exactly one tab behind: Explore's panda sat on Home, Settings' on Activity. Three rebuilds to learn that a seat that's off by exactly one tab is an index bug, not a spring bug.

The fix is one small Swift module. It walks the UITabBar, skips anything named Lens, and reports each glyph's center and the pill's top edge. In the React Native package that's the only native code in the perch. Everything else is a PNG and a Reanimated worklet. The Swift package does the same measuring and then the rest too, with no Expo, no Reanimated and no JavaScript.

Two mechanisms

The sprite. Each pose is one sprite sheet inside a clipped view. A worklet slides the image one cell per frame on the UI thread, so playback never waits on JavaScript. Run, sit and idle stay mounted and cross-fade, so a pose change never waits on a texture load either. Sit is the resting pose. After the sit-down clip the pet holds its last frame, because sheets cut from different clips almost never share a seam. In the Swift package the animal and its sprite run on one CADisplayLink instead.

The run. A tab change plans a run from the old seat to the new one and drives it with a spring. The finger chase uses a shorter, stiffer spring and a leash, so the pet runs toward your finger and never past it. Every spring completion is checked against a generation counter. Reanimated 4 can report a spring as finished seconds after it visually stopped, and a stale completion must not re-seat a pet that has already left.

The long way round

The raccoon and the squirrel are small and fast, and it bothered me that a tap from the first tab to the last just ran them across the top like everyone else. So they don't. They run off the end of the pill, roll around the corner, run upside down along the underside with their feet on the glass, and climb the far end.

An early version stopped at every corner to turn, and it read like a robot. Another pivoted on the pill's bounding box, so on a rounded pill the feet floated off the glass for most of the wall. The one that shipped is a single path along the pill's real outline, including a half-ellipse around each end cap, run at one constant speed, with the sprite rotating about its own feet. Tap again halfway and it turns around on the glass instead of cutting through it. I verified it from frame stacks of the recording, because no screenshot can tell you whether feet stayed on a curve. The Swift package takes the same route.

What dogfooding found

Before opening it up I put it into two more apps. Each one broke something the tests couldn't see.

The podspec said iOS 16.4 because the app I copied it from said so. The second app targeted 15.1, and CocoaPods skipped the pod silently. The perch rendered, the finger chase didn't, and nothing said why until I grepped the Podfile.lock.

Installing from a local folder made the package a symlink, Metro followed the symlink out of the project, and the app booted a second copy of Reanimated. In Release that crashed with an error about insets that had nothing to do with the cause.

The third app wanted one cat and got all six animals' sheets, about thirteen megabytes of them, because the root import registered everything. Now there's a bare entry and one module per animal, and in Swift one product per animal.

None of those are clever bugs. They're the bugs you only find by being the second user of your own package.

What's next

The code is at github.com/Lakshay1800/tabpet, for React Native and for Swift. The seventh animal is yours: three short clips from Sora, Seedance or Grok Imagine, one shell script, and an afternoon. The art pipeline in the docs has the prompts and the cut recipes.

Every app I've used for years is a tool. The one with the panda in it is the only one that feels like somewhere I go. I want more apps like that. If you put a pet on your tab bar, send me the video.

How it's built

The sprite player

Each pose is one PNG in a clipped viewport. In React Native a Reanimated worklet slides it one cell per frame on the UI thread, so playback never touches the JS thread; in Swift one CADisplayLink drives it. Sit holds its last frame instead of dissolving to idle, because sheets cut from different clips rarely share a seam frame. The bar on this page is a web port of the same player: the run frame follows distance travelled, so the feet stay planted whatever the easing.

The tab-bar measurer

One small Swift module walks the UITabBar and reports each item's glyph center and the bar's top edge. It skips the selection lens, which keeps copies of the selected button, and if no bar has laid out yet it retries for a bounded number of frames and snaps the seat when it lands.

Springs and the generation counter

A tab change plans a run from the last seat to the new one and drives it with a duration-parametrized spring. The finger chase uses a shorter, stiffer spring and a leash, so the animal runs toward the finger and never past it. Every spring completion is checked against a generation counter, because Reanimated 4 can report a spring as finished seconds late.

The art pipeline

Each animal is three short clips (run, sit, idle) from Sora, Seedance or Grok Imagine, cut into 240px sprite-sheet cells with a shell script. A painted ground shadow has to come off before a sheet sits on a dark tab bar, so the repo carries the cleaning tools too. The art pipeline doc has the prompts and the recipes.

Licensing

Code is MIT. The sprite sheets are CC BY 4.0: use them in anything, commercial apps included, and credit "tabpet" somewhere in your app or repo. The panda, raccoon and cat on this page are tabpet's art, CC BY 4.0.