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

@psync/curved-bottom-bar

v1.0.0

Published

A high performance, beautiful and fully customizable curved bottom navigation bar for React Native.

Readme

@psync/curved-bottom-bar

A high performance, beautiful and fully customizable curved bottom navigation bar for React Native. Implemented using react-native-svg and @react-navigation/bottom-tabs.

If you love this library, give us a star, you will be a ray of sunshine in our lives :)

Free React Native Boilerplate

React Native Template with a beautiful UI.

Getting started

npm install @psync/curved-bottom-bar --save

or

bun add @psync/curved-bottom-bar

Now we need to install the required peer dependencies:

npm install @react-navigation/native @react-navigation/bottom-tabs react-native-screens react-native-safe-area-context react-native-svg

or

yarn add @react-navigation/native @react-navigation/bottom-tabs react-native-screens react-native-safe-area-context react-native-svg

React Navigation v7 & v8 — this library works with both @react-navigation/bottom-tabs v7 and the v8 pre-release.

  • v7 requires @react-navigation/native, react-native-screens ≥ 4, and react-native-safe-area-context ≥ 4.
  • v8 raises the minimum versions: react ≥ 19.2, react-native ≥ 0.83 (New Architecture only), react-native-screens ≥ 4.25, and react-native-safe-area-context ≥ 5.5.

The curved bar always renders through React Navigation's JavaScript-based tab bar, so the visual result is identical on both versions. See React Navigation v7 vs v8 below for the details. Follow the React Navigation getting started guide for additional native setup steps.

Demo

CurvedBottomBar.Navigator

| Props | Params | isRequire | Description | |--------------------|-----------------------------------------------------------------|-----------|-------------------------------------------------------------------------------------| | initialRouteName | String | Yes | The name of the route to render on first load of the navigator | | renderCircle | ({ routeName, selectedTab, navigate }) => React.ReactElement | Yes | Function that returns a React element to display as the center tab item | | type | 'DOWN' or 'UP' | No | Type of the center tab item, downward curve or upward curve. Default: 'DOWN' | | circlePosition | 'CENTER' or 'LEFT' or 'RIGHT' | No | Position of circle button. Default: 'CENTER' | | tabBar | ({ routeName, selectedTab, navigate }) => React.ReactElement | No | Function that returns a React element to display as a tab bar icon | | circleWidth | Number | No | Customize width of the center tab item. Minimum is 50px and Maximum is 60px | | style | ViewStyle | No | Styling for container view | | shadowStyle | ViewStyle | No | Styling for shadow view. Not supported in Expo — use CurvedBottomBarExpo instead | | width | Number | No | Customize width for container view | | height | Number | No | Customize height for container view. Minimum is 50px and Maximum is 90px | | borderTopLeftRight | Boolean | No | Border radius top left and top right of container view | | borderColor | String | No | Border color | | borderWidth | Number | No | Border width | | bgColor | String | No | Background color of container view | | id | String | No | Optional navigator ID. React Navigation v7 only — removed in v8 (ignored). | | screenOptions | BottomTabNavigationOptions or function | No | Default options for all screens | | backBehavior | 'firstRoute' | 'initialRoute' | 'order' | 'history' | 'none' | No | Behaviour of back button. Default: 'firstRoute' | | implementation | 'custom' | 'native' | No | Tab bar rendering implementation. Default: 'custom'. Only honored by React Navigation v8; ignored by v7. Keep 'custom' to render the curved bar. | | enableGlassEffect | Boolean | No | Opt-in to Apple's Liquid Glass material on iOS 26+. See "Liquid Glass" below. | | glassEffectStyle | 'regular' | 'clear' | No | Liquid Glass material variant. Default: 'regular' |

CurvedBottomBar.Screen

| Props | Params | isRequire | Description | |-----------|---------------------------------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------| | name | String | Yes | Name of the route | | position | 'LEFT' or 'RIGHT' or 'CIRCLE' or 'CENTER' | Yes | Position of the tab icon. Use 'CIRCLE' or 'CENTER' when the circle button should itself be a navigable tab view | | component | React.ComponentType | Yes | Component to render for this screen | | options | BottomTabNavigationOptions | No | Screen-level navigation options |

API

| Function | Params | Description | | ------------------ | ----------------------------- | ----------------------------------------------------------------------------------------- | | setVisible | Boolean | Used to hide/show the tab bar. Ex: ref.current.setVisible(false) |

React Navigation v7 vs v8

This library is compatible with both @react-navigation/bottom-tabs v7 and the v8 pre-release. The peer dependency range allows ^7.0.0 || ^8.0.0-alpha.

Compatibility is achieved without breaking v7. The single behavioural change v8 introduces for this library is that v8 renders native bottom tabs by default, which would bypass the custom curved SVG tab bar. To prevent that, the navigator always passes implementation="custom" to React Navigation, which forces the JavaScript-based tab bar on v8. The implementation prop does not exist in v7 and is simply ignored there, so existing v7 apps are unaffected and the curved bar looks identical on both versions.

Things to be aware of when running on v8:

  • Minimum versions are higher. v8 requires react ≥ 19.2, react-native ≥ 0.83 (New Architecture only — the old architecture is no longer supported), react-native-screens ≥ 4.25, and react-native-safe-area-context ≥ 5.5. If you cannot meet these, stay on v7.
  • The id prop is removed in v8. React Navigation v8 dropped the navigator id prop (use a parent screen name with navigation.getParent('ScreenName') instead). On v7 the id prop still works; on v8 it is ignored.
  • liquid glass material. v8 ships its own iOS 26 liquid-glass support via @callstack/liquid-glass. This library's separate enableGlassEffect prop (backed by expo-glass-effect) remains independent and optional.

If you do not pass implementation, it defaults to 'custom', which is the correct value for the curved bar on both v7 and v8. Passing implementation="native" on v8 will render React Navigation's native tab bar and bypass the curved design, so only do that intentionally.

Use in Expo

import React from 'react';
import {
  Alert,
  Animated,
  StyleSheet,
  TouchableOpacity,
  View,
} from 'react-native';
import { CurvedBottomBarExpo } from '@psync/curved-bottom-bar';
import Ionicons from '@expo/vector-icons/Ionicons';
import { NavigationContainer } from '@react-navigation/native';

const Screen1 = () => {
  return <View style={styles.screen1} />;
};

const Screen2 = () => {
  return <View style={styles.screen2} />;
};

export default function App() {
  const _renderIcon = (routeName, selectedTab) => {
    let icon = '';

    switch (routeName) {
      case 'title1':
        icon = 'ios-home-outline';
        break;
      case 'title2':
        icon = 'settings-outline';
        break;
    }

    return (
      <Ionicons
        name={icon}
        size={25}
        color={routeName === selectedTab ? 'black' : 'gray'}
      />
    );
  };
  const renderTabBar = ({ routeName, selectedTab, navigate }) => {
    return (
      <TouchableOpacity
        onPress={() => navigate(routeName)}
        style={styles.tabbarItem}
      >
        {_renderIcon(routeName, selectedTab)}
      </TouchableOpacity>
    );
  };

  return (
    <NavigationContainer>
      <CurvedBottomBarExpo.Navigator
        type="DOWN"
        style={styles.bottomBar}
        shadowStyle={styles.shawdow}
        height={55}
        circleWidth={50}
        bgColor="white"
        initialRouteName="title1"
        borderTopLeftRight
        renderCircle={({ selectedTab, navigate }) => (
          <Animated.View style={styles.btnCircleUp}>
            <TouchableOpacity
              style={styles.button}
              onPress={() => Alert.alert('Click Action')}
            >
              <Ionicons name={'apps-sharp'} color="gray" size={25} />
            </TouchableOpacity>
          </Animated.View>
        )}
        tabBar={renderTabBar}
      >
        <CurvedBottomBarExpo.Screen
          name="title1"
          position="LEFT"
          component={() => <Screen1 />}
        />
        <CurvedBottomBarExpo.Screen
          name="title2"
          component={() => <Screen2 />}
          position="RIGHT"
        />
      </CurvedBottomBarExpo.Navigator>
    </NavigationContainer>
  );
}

export const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  shawdow: {
    shadowColor: '#DDDDDD',
    shadowOffset: {
      width: 0,
      height: 0,
    },
    shadowOpacity: 1,
    shadowRadius: 5,
  },
  button: {
    flex: 1,
    justifyContent: 'center',
  },
  bottomBar: {},
  btnCircleUp: {
    width: 60,
    height: 60,
    borderRadius: 30,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#E8E8E8',
    bottom: 30,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 1,
    },
    shadowOpacity: 0.2,
    shadowRadius: 1.41,
    elevation: 1,
  },
  imgCircle: {
    width: 30,
    height: 30,
    tintColor: 'gray',
  },
  tabbarItem: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  img: {
    width: 30,
    height: 30,
  },
  screen1: {
    flex: 1,
    backgroundColor: '#BFEFFF',
  },
  screen2: {
    flex: 1,
    backgroundColor: '#FFEBCD',
  },
});

Use in RN CLI

import React from 'react';
import {
  Alert,
  Animated,
  StyleSheet,
  TouchableOpacity,
  View,
} from 'react-native';
import { CurvedBottomBar } from '@psync/curved-bottom-bar';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { NavigationContainer } from '@react-navigation/native';

const Screen1 = () => {
  return <View style={styles.screen1} />;
};

const Screen2 = () => {
  return <View style={styles.screen2} />;
};

export default function App() {
  const _renderIcon = (routeName, selectedTab) => {
    let icon = '';

    switch (routeName) {
      case 'title1':
        icon = 'ios-home-outline';
        break;
      case 'title2':
        icon = 'settings-outline';
        break;
    }

    return (
      <Ionicons
        name={icon}
        size={25}
        color={routeName === selectedTab ? 'black' : 'gray'}
      />
    );
  };
  const renderTabBar = ({ routeName, selectedTab, navigate }) => {
    return (
      <TouchableOpacity
        onPress={() => navigate(routeName)}
        style={styles.tabbarItem}
      >
        {_renderIcon(routeName, selectedTab)}
      </TouchableOpacity>
    );
  };

  return (
    <NavigationContainer>
      <CurvedBottomBar.Navigator
        type="UP"
        style={styles.bottomBar}
        shadowStyle={styles.shawdow}
        height={55}
        circleWidth={50}
        bgColor="white"
        initialRouteName="title1"
        borderTopLeftRight
        renderCircle={({ selectedTab, navigate }) => (
          <Animated.View style={styles.btnCircleUp}>
            <TouchableOpacity
              style={styles.button}
              onPress={() => Alert.alert('Click Action')}
            >
              <Ionicons name={'apps-sharp'} color="gray" size={25} />
            </TouchableOpacity>
          </Animated.View>
        )}
        tabBar={renderTabBar}
      >
        <CurvedBottomBar.Screen
          name="title1"
          position="LEFT"
          component={() => <Screen1 />}
        />
        <CurvedBottomBar.Screen
          name="title2"
          component={() => <Screen2 />}
          position="RIGHT"
        />
      </CurvedBottomBar.Navigator>
    </NavigationContainer>
  );
}

export const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  shawdow: {
    shadowColor: '#DDDDDD',
    shadowOffset: {
      width: 0,
      height: 0,
    },
    shadowOpacity: 1,
    shadowRadius: 5,
  },
  button: {
    flex: 1,
    justifyContent: 'center',
  },
  bottomBar: {},
  btnCircleUp: {
    width: 60,
    height: 60,
    borderRadius: 30,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#E8E8E8',
    bottom: 18,
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 1,
    },
    shadowOpacity: 0.2,
    shadowRadius: 1.41,
    elevation: 1,
  },
  imgCircle: {
    width: 30,
    height: 30,
    tintColor: 'gray',
  },
  tabbarItem: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  img: {
    width: 30,
    height: 30,
  },
  screen1: {
    flex: 1,
    backgroundColor: '#BFEFFF',
  },
  screen2: {
    flex: 1,
    backgroundColor: '#FFEBCD',
  },
});

Use with expo-router

This package ships an optional entry point for expo-router. expo-router is not a hard dependency — it is loaded dynamically and only required if you import the entry below.

// app/(tabs)/_layout.tsx
import { CurvedTabs } from '@psync/curved-bottom-bar/expo-router';
import { Ionicons } from '@expo/vector-icons';
import { TouchableOpacity } from 'react-native';

export default function TabLayout() {
  return (
    <CurvedTabs
      initialRouteName="home"
      type="DOWN"
      bgColor="white"
      height={60}
      circleWidth={50}
      renderCircle={({ navigate }) => (
        <TouchableOpacity onPress={() => navigate('camera')}>
          <Ionicons name="apps-sharp" size={25} color="gray" />
        </TouchableOpacity>
      )}
      tabBar={({ routeName, selectedTab, navigate }) => (
        <TouchableOpacity onPress={() => navigate(routeName)}>
          <Ionicons
            name={routeName === 'home' ? 'home-outline' : 'settings-outline'}
            size={25}
            color={routeName === selectedTab ? 'black' : 'gray'}
          />
        </TouchableOpacity>
      )}
    >
      <CurvedTabs.Screen name="home" position="LEFT" />
      <CurvedTabs.Screen name="camera" position="CIRCLE" />
      <CurvedTabs.Screen name="settings" position="RIGHT" />
    </CurvedTabs>
  );
}

Liquid Glass (iOS 26+)

Apple introduced the Liquid Glass material in iOS 26. Pass enableGlassEffect on the navigator to opt in. The library uses the optional peer dependency expo-glass-effect to render the material; on Android, on iOS < 26, or when expo-glass-effect is not installed the prop is a no-op and the bar renders normally.

npx expo install expo-glass-effect
<CurvedBottomBarExpo.Navigator
  enableGlassEffect
  glassEffectStyle="regular"   // or 'clear'
  bgColor="transparent"        // important: do not occlude the glass material
  // ...other props
/>