npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

expo-glass-tabs

v0.1.1

Published

Floating liquid-glass tab bar for Expo Router — minimize-on-scroll, sliding highlight, finger scrubbing with haptics, and progressive edge blur.

Readme

expo-glass-tabs

A floating liquid-glass tab bar for Expo Router — the Revolut-style bottom bar.

npm version npm downloads TypeScript license


Why

iOS 26's native tab bar can minimize on scroll — but it collapses to a single icon. Revolut's bar keeps all tabs visible and just drops the labels, shrinking the pill in both dimensions. That behavior isn't reachable from the native UITabBar, so this package rebuilds it on top of Expo Router's headless tabs — with real native materials.

Features

  • 🪟 Real liquid glass — iOS 26 UIGlassEffect via expo-glass-effect: true squircle corners, rim refraction, content lensing. Solid fallback on older iOS and Android.
  • 📉 Minimize on scroll — scroll down and the pill shrinks in both dimensions while labels collapse; every icon stays visible. Scroll up to expand. Critically-damped springs, no flicker (rubber-band overscroll is filtered out).
  • 🛝 Sliding highlight — the active-tab pill physically travels between tabs on an interruptible, transform-only spring.
  • 👆 Finger scrubbing — drag along the bar: the highlight tracks your finger 1:1, icons light up as you pass them, haptic ticks fire at each boundary, and navigation happens on release — screens never jump mid-drag.
  • 🌫️ Progressive edge blur — content dissolves gradually behind the bar with no hard blur line. The same component works for top bars.
  • 🎞️ Subtle screen transitions — fade + micro-scale between tabs via a TabSlot renderFn.
  • UI-thread everything — all animation runs in Reanimated worklets with native gesture recognizers; a blocked JS thread doesn't drop a frame.
  • 🧩 Pure TypeScript — no custom native code. Works in Expo Go and any dev build.

Installation

npx expo install expo-glass-tabs expo-blur expo-glass-effect expo-haptics expo-symbols react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-screens

Wrap your app root in GestureHandlerRootView (Expo Router does not do this for you):

// app/_layout.tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function RootLayout() {
  return <GestureHandlerRootView style={{ flex: 1 }}>{/* ... */}</GestureHandlerRootView>;
}

Quick start

import { useRouter } from 'expo-router';
import { Tabs, TabList, TabSlot, TabTrigger } from 'expo-router/ui';
import {
  GlassTabBar,
  GlassTabButton,
  TabBarMinimizeProvider,
  renderFadingTabScreen,
  type GlassTabItem,
} from 'expo-glass-tabs';

const ITEMS: (GlassTabItem & { href: string })[] = [
  { name: 'index', href: '/', label: 'Home', icon: 'house.fill' },
  { name: 'invest', href: '/invest', label: 'Invest', icon: 'chart.line.uptrend.xyaxis' },
  { name: 'payments', href: '/payments', label: 'Payments', icon: 'arrow.left.arrow.right' },
  { name: 'crypto', href: '/crypto', label: 'Crypto', icon: 'bitcoinsign' },
];

export default function AppTabs() {
  const router = useRouter();
  return (
    <TabBarMinimizeProvider>
      <Tabs>
        <TabSlot style={{ height: '100%' }} renderFn={renderFadingTabScreen} />
        <TabList asChild>
          <GlassTabBar onIndexSelected={(i) => router.navigate(ITEMS[i].href as never)}>
            {ITEMS.map(({ href, ...item }, index) => (
              <TabTrigger key={item.name} name={item.name} href={href as never} asChild>
                <GlassTabButton item={item} index={index} />
              </TabTrigger>
            ))}
          </GlassTabBar>
        </TabList>
      </Tabs>
    </TabBarMinimizeProvider>
  );
}

Then attach the scroll hook in every screen that should minimize the bar:

import Animated from 'react-native-reanimated';
import { useMinimizeOnScroll } from 'expo-glass-tabs';

export default function HomeScreen() {
  const onScroll = useMinimizeOnScroll();
  return (
    <Animated.ScrollView onScroll={onScroll} scrollEventThrottle={16}>
      {/* content */}
    </Animated.ScrollView>
  );
}

Custom icons

Use renderIcon when an SF Symbol won't do — a brand logo, for example. It's called once per tint layer, so the active/inactive crossfade stays on the UI thread:

import { Image } from 'expo-image';

const home: GlassTabItem = {
  name: 'index',
  label: 'Home',
  renderIcon: ({ tint }) => (
    <Image source={require('./logo.png')} tintColor={tint} style={{ width: 18, height: 17 }} />
  ),
};

Theming

<GlassTabBar
  theme={{
    activeTint: '#FFFFFF',
    inactiveTint: '#9E9EA6',
    highlight: 'rgba(255,255,255,0.14)',   // sliding pill
    glassTint: 'rgba(10,10,12,0.55)',      // tint over the liquid glass
    solidFallback: 'rgba(18,18,20,0.94)',  // pre-iOS 26 / Android background
  }}
  haptics // scrub tick (iOS), default true
/>

Progressive blur for your own edges

ProgressiveBlur is exported on its own — drop it behind a transparent header:

import { ProgressiveBlur } from 'expo-glass-tabs';

<ProgressiveBlur
  direction="top"
  style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 160 }}
/>;

API

| Export | Kind | Purpose | | --- | --- | --- | | GlassTabBar | component | The floating pill — use via TabList asChild | | GlassTabButton | component | One trigger — use via TabTrigger asChild | | TabBarMinimizeProvider | component | Wrap the Tabs tree once | | useMinimizeOnScroll() | hook | Scroll handler for Animated.ScrollView | | useTabBarMinimized() | hook | Raw 0..1 minimize progress, for custom UI | | renderFadingTabScreen | function | TabSlot renderFn with fade + micro-scale | | ProgressiveBlur | component | Gradient blur anchored to a screen edge | | MINIMIZE_SPRING | constant | The spring config, if you want to match it |

How it works

The bar's structure is declared in JS (Expo Router headless tabs), but everything you see and feel at runtime is native:

  • MaterialsUIGlassEffect, UIVisualEffectView, SF Symbols, UIFeedbackGenerator.
  • Motion — Reanimated worklets on the UI thread; the sliding highlight and scrub are transform-only (GPU-composited, no layout work per frame).
  • Gestures — native recognizers via react-native-gesture-handler; a Pan (scrub) races a Tap, so taps stay forgiving while drags feel attached to the finger.

The corner geometry is rendered by the glass view itself (its native corner configuration), not an RN clipping mask — that's what preserves the true squircle and the rim lighting through the whole minimize animation.

License

MIT © David Mokos