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

@limonify/zest-ui

v0.9.2

Published

Base UI for React Native — headless, unstyled, accessible primitive components.

Readme

@limonify/zest-ui

npm license docs

Base UI for React Native — headless, unstyled, accessible primitive components.

@limonify/zest-ui is a React Native port of MUI Base UI. Same compound parts (Dialog.Popup, Dialog.Backdrop), same controlled/uncontrolled patterns — adapted for touch and React Native accessibility APIs.

Zero style. Zero theme. Zero animation library. You own the look; zest owns behaviour, state, and accessibility. Style with StyleSheet, Uniwind, NativeWind, Tamagui, or anything else.

Docs: zestui.limonify.com

Why zest

  • Base UI API — familiar compound components and part names, not a Radix rename
  • Headless — every part renders a plain RN primitive (View, Pressable, Text, TextInput)
  • State as stylestyle and className accept a function of state (the RN counterpart of data-*)
  • Accessible — roles and states map to accessibilityRole / accessibilityState (and keep aria-* for RN-web)

Install

bun add @limonify/zest-ui
# or: npm install @limonify/zest-ui

Peers: React 19+, React Native 0.79+.

react-native-gesture-handler is a required peer — install it alongside zest. On Expo, use npx expo install react-native-gesture-handler so you get the version your SDK's Expo Go ships; a mismatch crashes at gesture attach rather than at build:

bun add react-native-gesture-handler

It is required rather than optional because the package root re-exports every component, and Metro does not tree-shake: importing anything from @limonify/zest-ui pulls in Slider, Drawer, Toast and NumberField, which import it statically. Reach for the per-component subpaths (@limonify/zest-ui/dialog) if you want a bundle without it.

Wrapping the app is separate, and only needed if you actually render one of the gesture-driven parts — Slider, Drawer, Toast, or NumberField.ScrubArea:

import { GestureHandlerRootView } from 'react-native-gesture-handler';

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

Anchored popups (Popover, Tooltip, Menu, Select, Combobox, …) use @floating-ui/react-native, which ships as a regular dependency — nothing extra to install.

Full install guide: zestui.limonify.com/docs/installation

Quick start

Style from state — no themes, no CSS variables:

import { StyleSheet } from 'react-native';
import { Switch } from '@limonify/zest-ui';

export function DarkModeToggle() {
  return (
    <Switch.Root style={(state) => [styles.track, state.checked && styles.trackOn]}>
      <Switch.Thumb style={(state) => [styles.thumb, state.checked && styles.thumbOn]} />
    </Switch.Root>
  );
}

const styles = StyleSheet.create({
  track: { width: 44, height: 26, borderRadius: 13, backgroundColor: '#d4d4d4', padding: 3 },
  trackOn: { backgroundColor: '#4c7a0b' },
  thumb: { width: 20, height: 20, borderRadius: 10, backgroundColor: '#fff' },
  thumbOn: { transform: [{ translateX: 18 }] },
});

Compound overlays use the same part tree as Base UI. Portals are React Native Modals:

import { Text, StyleSheet } from 'react-native';
import { Dialog } from '@limonify/zest-ui';

export function DeleteDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger style={styles.button}>
        <Text style={styles.buttonText}>Delete</Text>
      </Dialog.Trigger>

      <Dialog.Portal>
        <Dialog.Backdrop style={styles.backdrop} />
        <Dialog.Viewport style={styles.viewport}>
          <Dialog.Popup style={styles.card}>
            <Dialog.Title style={styles.title}>Delete file?</Dialog.Title>
            <Dialog.Description style={styles.body}>This can't be undone.</Dialog.Description>
            <Dialog.Close style={styles.button}>
              <Text style={styles.buttonText}>Cancel</Text>
            </Dialog.Close>
          </Dialog.Popup>
        </Dialog.Viewport>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

const styles = StyleSheet.create({
  button: {
    alignSelf: 'flex-start',
    backgroundColor: '#171717',
    borderRadius: 10,
    paddingVertical: 10,
    paddingHorizontal: 16,
  },
  buttonText: { color: '#fff', fontWeight: '600' },
  backdrop: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.5)' },
  viewport: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 },
  card: { width: '100%', maxWidth: 340, gap: 8, borderRadius: 16, backgroundColor: '#fff', padding: 20 },
  title: { fontSize: 17, fontWeight: '700' },
  body: { color: '#737373' },
});

Common props

Every part accepts the same small set on top of its own API:

| Prop | Role | | --- | --- | | style | RN style, or (state) => style | | className | String or (state) => string for Uniwind / NativeWind | | render | (props, state) => ReactNode — swap the host element, keep behaviour | | ref | Ref to the underlying element (React 19 — ref is a regular prop) |

<Dialog.Trigger render={(props) => <MyButton {...props}>Open</MyButton>} />

<Checkbox.Root
  className={(state) =>
    ['size-6 rounded border', state.checked && 'bg-lime-600'].filter(Boolean).join(' ')
  }
/>

Toast

Most components need no provider. Toast is the exception — it must not block the app, so it is not a Modal. Mount Toast.Provider and Toast.Viewport once at the root:

import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Toast } from '@limonify/zest-ui';

export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Toast.Provider timeout={4000} limit={3}>
        <RootNavigator />
        <Toast.Viewport style={{ paddingHorizontal: 16, paddingBottom: 48 }}>
          {/* map toasts → Toast.Root — see https://zestui.limonify.com/docs/components/toast */}
        </Toast.Viewport>
      </Toast.Provider>
    </GestureHandlerRootView>
  );
}

Skip GestureHandlerRootView if you use neither Slider nor Drawer; skip Toast setup if you don't use toasts.

Components

33 components. Import from @limonify/zest-ui:

import { Dialog, Switch, Toast } from '@limonify/zest-ui';

Forms & input

| Component | Parts | | --- | --- | | Button | — | | Input | — | | Checkbox | Root, Indicator | | CheckboxGroup | — | | Radio | Root, Indicator | | RadioGroup | — | | Switch | Root, Thumb | | Toggle | — | | ToggleGroup | — | | Slider | Root, Label, Value, Control, Track, Indicator, Thumb | | NumberField | Root, Group, Input, Increment, Decrement, ScrubArea | | OTPField | Root, Input, Separator | | Field | Root, Item, Label, Control, Description, Error, Validity | | Form | Form | | Fieldset | Root, Legend | | Select | Root, Label, Trigger, Value, Icon, Portal, Backdrop, Positioner, Arrow, Popup, List, Group, GroupLabel, Item, ItemText, ItemIndicator, Separator | | Combobox | Root, Trigger, Input, Chips, Chip, ChipRemove, Clear, Portal, Backdrop, Positioner, Popup, List, Group, GroupLabel, Collection, Item, ItemIndicator, Empty, Status, Value, Icon | | Autocomplete | Root, Trigger, Input, Clear, Portal, Backdrop, Positioner, Popup, List, Group, GroupLabel, Collection, Item, Empty, Status |

Disclosure

| Component | Parts | | --- | --- | | Collapsible | Root, Trigger, Panel | | Accordion | Root, Item, Header, Trigger, Panel | | Tabs | Root, List, Tab, Indicator, Panel |

Overlays

| Component | Parts | | --- | --- | | Dialog | Root, Trigger, Portal, Backdrop, Viewport, Popup, Title, Description, Close | | AlertDialog | same shape as Dialog | | Drawer | Root, Provider, Indent, IndentBackground, Trigger, Portal, Backdrop, Viewport, Popup, SwipeArea, Title, Description, Close | | Popover | Root, Trigger, Portal, Backdrop, Positioner, Popup, Arrow, Title, Description, Close | | Tooltip | Root, Trigger, Portal, Positioner, Popup, Arrow | | Menu | Root, Trigger, Portal, Backdrop, Positioner, Popup, Arrow, Item, LinkItem, CheckboxItem, RadioGroup, RadioItem, Group, GroupLabel, SubmenuRoot, SubmenuTrigger, Separator | | ContextMenu | Root, Trigger, Positioner, Arrow (+ Menu item family) |

Feedback & misc

| Component | Parts | | --- | --- | | Progress | Root, Track, Indicator, Value, Label | | Meter | Root, Track, Indicator, Label, Value | | Avatar | Root, Image, Fallback | | Separator | — | | Toast | Provider, Viewport, Root, Positioner, Arrow, Title, Description, Action, Close |

Popup families also support handles (createHandle, detached triggers, imperative open) — see the docs for each component.

Design notes

Portals are Modals. Every Portal (Dialog, Drawer, Popover, Tooltip, Menu, Select, Combobox, …) is a React Native Modal. That keeps context across the portal boundary and wires Android back / Escape to dismiss. Toast is the exception: an absolutely positioned View with pointerEvents="box-none" so the app underneath stays usable.

State, not data attributes. On the web, Base UI publishes data-* and CSS variables. In zest, state reaches you through the state object on style / className / renderstate.open, state.checked, state.height, and so on.

zest never animates. Animated parts publish transitionStatus and measured geometry on state; you drive Animated (or Reanimated) yourself. Exit animations need keepMounted (or the part’s equivalent, e.g. Toast’s removeOnClose), otherwise the tree unmounts as soon as it closes.

Not ported

These Base UI pieces stay web/desktop-only:

| Component | Why | | --- | --- | | PreviewCard | Hover card — no hover on touch | | Menubar | Desktop navigation pattern | | NavigationMenu | Desktop multi-level hover menu | | ScrollArea | RN already has ScrollView / FlatList | | Toolbar | Desktop toolbar / roving tabindex | | CSPProvider | Content Security Policy is web-only |

Form is ported, adapted: there is no <form> submission, so it does the parts that still matter — spreading a server's errors onto the fields they name, and validating every field on submit. Submission is imperative, through actionsRef.

Base UI's useFilter is ported — Combobox and Autocomplete filter through it, and it is exported for filtering your own lists. See the docs.

DirectionProvider is ported. Direction defaults to React Native's own I18nManager.isRTL; wrap a subtree to override it. It decides which edge align="start" anchors a popup to and which way a horizontal slider grows — React Native mirrors the layout itself. See the docs.

Monorepo

This repository is a bun + turborepo workspace:

| Path | Role | | --- | --- | | packages/zest-ui | The publishable library | | apps/example | Expo demo exercising every component | | apps/docs | Documentation site (zestui.limonify.com) |

bun install
bun run test        # typecheck + tests via turbo
bun run build
bun run dev         # Expo example

The full implementation plan, milestones, and architecture notes live in ARCHITECTURE.md. Contributor notes for porting components live in packages/zest-ui/CLAUDE.md.

License

MIT © Limonify