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

@slithy/base-ui

v0.4.0

Published

UI components for @slithy, built on Base UI.

Readme

@slithy/base-ui

Compound UI components built on Base UI. Tooltip, Menu, and Popover all use a global singleton architecture: each trigger is a plain <button> with zero Base UI overhead, and a single renderer mounted at the app root owns the positioning and rendering.

Setup

Mount all renderers once at the app root. Timing props on TooltipRenderer are global defaults — all tooltips share them.

import { MenuRenderer, PopoverRenderer, TooltipRenderer } from "@slithy/base-ui";

function App() {
  return (
    <>
      <MenuRenderer />
      <PopoverRenderer />
      <TooltipRenderer delay={600} closeDelay={300} timeout={300} />
      {/* rest of your app */}
    </>
  );
}

TooltipRenderer

| Prop | Type | Default | Description | |------|------|---------|-------------| | delay | number | 600 | Delay before opening in ms. | | closeDelay | number | 300 | Delay before closing in ms. | | timeout | number | 300 | Warm-up window in ms — if a tooltip closed within this window, the next opens instantly. |

Tooltip

import { Tooltip } from "@slithy/base-ui";

<Tooltip.Root>
  <Tooltip.Trigger>Hover me</Tooltip.Trigger>
  <Tooltip.Portal>
    <Tooltip.Popup>
      <Tooltip.Arrow />
      Tooltip content
    </Tooltip.Popup>
  </Tooltip.Portal>
</Tooltip.Root>

Parts: Root, Trigger, Portal, Popup, Arrow

Controlled open

Control the tooltip's open state externally with open and onOpenChange:

function ControlledTooltip() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen((o) => !o)}>
        {open ? "Hide" : "Show"} tooltip
      </button>
      <Tooltip.Root open={open} onOpenChange={setOpen}>
        <Tooltip.Trigger>Target</Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Popup>Controlled tooltip</Tooltip.Popup>
        </Tooltip.Portal>
      </Tooltip.Root>
    </>
  );
}

Detached trigger

When the trigger and tooltip content need to live in different parts of the tree, provide a matching id on both Root and Trigger:

function DetachedTooltip() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Tooltip.Trigger id="help-tip">Help</Tooltip.Trigger>

      <Tooltip.Root id="help-tip" open={open} onOpenChange={setOpen}>
        <Tooltip.Portal>
          <Tooltip.Popup>Detached tooltip content</Tooltip.Popup>
        </Tooltip.Portal>
      </Tooltip.Root>

      <button onClick={() => setOpen(true)}>Show help</button>
    </>
  );
}

Detached triggers require controlled mode (open/onOpenChange) since the trigger is outside Root's context.

Props

Root

| Prop | Type | Default | Description | |------|------|---------|-------------| | id | string | — | Explicit trigger id. Required for detached triggers; auto-generated otherwise. | | open | boolean | — | Controlled open state. | | defaultOpen | boolean | — | Open on first render (uncontrolled). | | disabled | boolean | false | Prevent the tooltip from opening. | | side | "top" \| "bottom" \| "left" \| "right" | "top" | Which side of the trigger to place the popup. | | sideOffset | number | 6 | Distance between trigger and popup in pixels. | | align | "start" \| "center" \| "end" | "center" | Alignment relative to the trigger. | | alignOffset | number | 0 | Offset along the alignment axis in pixels. | | collisionPadding | number \| Partial<Record<Side, number>> | 5 | Padding from viewport edges for collision detection. | | onOpenChange | (open: boolean) => void | — | Called when the tooltip opens or closes. | | onOpenChangeComplete | (open: boolean) => void | — | Called after open/close animation completes. |

Trigger

| Prop | Type | Default | Description | |------|------|---------|-------------| | render | ReactElement \| (props) => ReactElement | — | Replace the default <button> with a custom element. See Custom trigger element. |

Popover

Interactive hover card backed by Base UI's Popover with openOnHover. Unlike Tooltip (non-interactive, text-only), Popover supports rich interactive content — links, buttons, media — that stays open while the pointer is over the popup. Also supports click-triggered mode.

import { Popover } from "@slithy/base-ui";

<Popover.Root side="bottom" sideOffset={8}>
  <Popover.Trigger delay={300} closeDelay={200}>
    @username
  </Popover.Trigger>
  <Popover.Portal>
    <Popover.Popup>
      <Popover.Arrow />
      <Popover.Title>User Profile</Popover.Title>
      <Popover.Description>Hover card with interactive content.</Popover.Description>
    </Popover.Popup>
  </Popover.Portal>
</Popover.Root>

Parts: Root, Trigger, Portal, Popup, Arrow, Close, Title, Description

Per-trigger timing

Unlike Tooltip where timing is global, each Popover trigger can specify its own delay and closeDelay:

<Popover.Root delay={0} closeDelay={300}>
  <Popover.Trigger>Instant open</Popover.Trigger>
  ...
</Popover.Root>

<Popover.Root delay={600}>
  <Popover.Trigger>Slow open</Popover.Trigger>
  ...
</Popover.Root>

Click-triggered

Set openOnHover={false} to create a click-triggered popover. Use Popover.Close for a dismiss button:

<Popover.Root openOnHover={false}>
  <Popover.Trigger>Click me</Popover.Trigger>
  <Popover.Portal>
    <Popover.Popup>
      <Popover.Close>✕</Popover.Close>
      Click-triggered popover content
    </Popover.Popup>
  </Popover.Portal>
</Popover.Root>

To open on both hover and click, set both explicitly:

<Popover.Root openOnHover={true} openOnClick={true}>
  ...
</Popover.Root>

Controlled open

function ControlledPopover() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen((o) => !o)}>
        {open ? "Close" : "Open"}
      </button>
      <Popover.Root open={open} onOpenChange={setOpen}>
        <Popover.Trigger>Target</Popover.Trigger>
        <Popover.Portal>
          <Popover.Popup>Controlled popover</Popover.Popup>
        </Popover.Portal>
      </Popover.Root>
    </>
  );
}

Props

Root

| Prop | Type | Default | Description | |------|------|---------|-------------| | id | string | — | Explicit trigger id. Required for detached triggers; auto-generated otherwise. | | open | boolean | — | Controlled open state. | | defaultOpen | boolean | — | Open on first render (uncontrolled). | | disabled | boolean | false | Prevent the popover from opening. | | modal | boolean | false | Whether the popover is modal (locks scroll, traps focus). | | openOnHover | boolean | true | Whether the trigger opens on hover. Set to false for click-only. | | openOnClick | boolean | !openOnHover | Whether the trigger opens on click. Defaults to false when openOnHover is true; set both to true to open on hover and click. | | delay | number | 300 | Delay before opening on hover in ms. | | closeDelay | number | 0 | Delay before closing on hover in ms. | | side | "top" \| "bottom" \| "left" \| "right" | "bottom" | Which side of the trigger to place the popup. | | sideOffset | number | 8 | Distance between trigger and popup in pixels. | | align | "start" \| "center" \| "end" | "center" | Alignment relative to the trigger. | | alignOffset | number | 0 | Offset along the alignment axis in pixels. | | collisionPadding | number \| Partial<Record<Side, number>> | 5 | Padding from viewport edges for collision detection. | | onOpenChange | (open: boolean) => void | — | Called when the popover opens or closes. | | onOpenChangeComplete | (open: boolean) => void | — | Called after open/close animation completes. |

Trigger

| Prop | Type | Default | Description | |------|------|---------|-------------| | render | ReactElement \| (props) => ReactElement | — | Replace the default <button> with a custom element. See Custom trigger element. | | nativeButton | boolean | auto | Whether the rendered element is a native <button>. Auto-detected from render. | | openOnHover | boolean | — | Override openOnHover from Root. | | delay | number | — | Override delay from Root. | | closeDelay | number | — | Override closeDelay from Root. |

Menu

import { Menu } from "@slithy/base-ui";

<Menu.Root>
  <Menu.Trigger>Open menu</Menu.Trigger>
  <Menu.Portal>
    <Menu.Popup>
      <Menu.Item onClick={handleEdit}>Edit</Menu.Item>
      <Menu.Item onClick={handleDuplicate}>Duplicate</Menu.Item>
      <Menu.Separator />
      <Menu.Item onClick={handleDelete}>Delete</Menu.Item>
    </Menu.Popup>
  </Menu.Portal>
</Menu.Root>

Parts: Root, Trigger, Portal, Popup, Arrow, Item, Separator, Group, GroupLabel, CheckboxItem, CheckboxItemIndicator, RadioGroup, RadioItem, RadioItemIndicator, SubmenuRoot, SubmenuTrigger, SubmenuPortal, Positioner

Tooltip on trigger

Menu.Trigger can show a tooltip on hover/focus that is automatically dismissed when the menu opens:

<Menu.Trigger tooltip="Edit, duplicate, or delete">
  Actions
</Menu.Trigger>

Requires TooltipRenderer to be mounted. Touch interactions do not trigger tooltips.

Disabling the menu

Set disabled on Root to prevent the menu from opening while keeping the trigger interactive:

<Menu.Root disabled={isMobile}>
  <Menu.Trigger onClick={isMobile ? () => setModalOpen(true) : undefined}>
    Options
  </Menu.Trigger>
  <Menu.Portal>
    <Menu.Popup>
      <Menu.Item onClick={handleEdit}>Edit</Menu.Item>
    </Menu.Popup>
  </Menu.Portal>
</Menu.Root>

Nested menus

Use SubmenuRoot, SubmenuTrigger, and SubmenuPortal to nest a submenu inside a popup. Wrap the Positioner directly inside SubmenuPortal to control placement:

<Menu.Root>
  <Menu.Trigger>Actions</Menu.Trigger>
  <Menu.Portal>
    <Menu.Popup>
      <Menu.Item onClick={handleEdit}>Edit</Menu.Item>
      <Menu.SubmenuRoot>
        <Menu.SubmenuTrigger>Move to ›</Menu.SubmenuTrigger>
        <Menu.SubmenuPortal>
          <Menu.Positioner side="right" align="start" sideOffset={4}>
            <Menu.Popup>
              <Menu.Item onClick={() => moveTo("inbox")}>Inbox</Menu.Item>
              <Menu.Item onClick={() => moveTo("archive")}>Archive</Menu.Item>
            </Menu.Popup>
          </Menu.Positioner>
        </Menu.SubmenuPortal>
      </Menu.SubmenuRoot>
    </Menu.Popup>
  </Menu.Portal>
</Menu.Root>

Controlled open

Control the menu's open state externally with open and onOpenChange:

function ControlledMenu() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen((o) => !o)}>
        {open ? "Close" : "Open"}
      </button>
      <Menu.Root open={open} onOpenChange={setOpen}>
        <Menu.Trigger>Actions</Menu.Trigger>
        <Menu.Portal>
          <Menu.Popup>
            <Menu.Item>Edit</Menu.Item>
            <Menu.Item>Delete</Menu.Item>
          </Menu.Popup>
        </Menu.Portal>
      </Menu.Root>
    </>
  );
}

Detached trigger

When the trigger and menu content need to live in different parts of the tree, provide a matching id on both Root and Trigger:

function DetachedMenu() {
  const [open, setOpen] = useState(false);

  return (
    <>
      {/* Trigger rendered elsewhere */}
      <Menu.Trigger id="project-actions">Actions</Menu.Trigger>

      {/* Root elsewhere */}
      <Menu.Root id="project-actions" open={open} onOpenChange={setOpen}>
        <Menu.Portal>
          <Menu.Popup>
            <Menu.Item>Edit</Menu.Item>
            <Menu.Item>Delete</Menu.Item>
          </Menu.Popup>
        </Menu.Portal>
      </Menu.Root>

      {/* Programmatic open */}
      <button onClick={() => setOpen(true)}>Open it</button>
    </>
  );
}

Detached triggers require controlled mode (open/onOpenChange) since the trigger is outside Root's context.

Props

Root

| Prop | Type | Default | Description | |------|------|---------|-------------| | id | string | — | Explicit trigger id. Required for detached triggers; auto-generated otherwise. | | open | boolean | — | Controlled open state. | | defaultOpen | boolean | — | Open on first render (uncontrolled). | | disabled | boolean | false | Prevent the menu from opening. The trigger remains interactive. | | modal | boolean | true | Whether the menu is modal (locks scroll, inerts page). When false, the popup auto-dismisses when the trigger scrolls out of view. | | side | "top" \| "bottom" \| "left" \| "right" | "bottom" | Which side of the trigger to place the popup. | | sideOffset | number | 4 | Distance between trigger and popup in pixels. | | align | "start" \| "center" \| "end" | "center" | Alignment relative to the trigger. | | alignOffset | number | 0 | Offset along the alignment axis in pixels. | | collisionPadding | number \| Partial<Record<Side, number>> | 5 | Padding from viewport edges for collision detection. | | loopFocus | boolean | true | Wrap keyboard focus from last item back to first (and vice versa). | | highlightItemOnHover | boolean | true | Highlight items on pointer hover. Set to false to differentiate CSS :hover from keyboard data-highlighted. | | orientation | "vertical" \| "horizontal" | "vertical" | Arrow key direction for navigation. | | onOpenChange | (open: boolean) => void | — | Called when the menu opens or closes. | | onOpenChangeComplete | (open: boolean) => void | — | Called after open/close animation completes. |

Trigger

| Prop | Type | Default | Description | |------|------|---------|-------------| | render | ReactElement \| (props) => ReactElement | — | Replace the default <button> with a custom element. See Custom trigger element. | | nativeButton | boolean | auto | Whether the rendered element is a native <button>. Auto-detected from render; set explicitly when your custom component renders a <button> internally. | | tooltip | ReactNode | — | Tooltip content shown on hover/focus. Dismissed when the menu opens. Requires TooltipRenderer. | | tooltipArrow | boolean | false | Show a directional arrow on the trigger tooltip. |

Architecture

The problem

The standard approach with a headless component library is to co-locate a Tooltip.Root (or Menu.Root, Popover.Root) with each trigger that needs one. That works fine at small scale, but has a real cost when the same UI pattern repeats across a page:

  • A data table with 200 rows, each with a row-action menu, mounts 200 full Base UI hook trees — Floating UI, hover/focus management, ARIA state, a portal — even though only one menu is ever open at a time and the rest sit idle.
  • Tooltip triggers are especially common. Icon buttons, truncated cells, status badges: each one mounting its own positioning engine, delay timers, and DOM portal adds up fast.
  • Even when popups are closed, each mounted Root holds event listeners, context subscriptions, and Floating UI state in memory. The per-trigger overhead is small, but it multiplies.

The underlying observation is that these are mutually exclusive UI elements — only one tooltip, one menu, and one popover can meaningfully be open at once. Having per-trigger infrastructure is paying for N independent systems when only one is ever used at a time.

The solution

Tooltip, Menu, and Popover all use a global singleton pattern:

  • Triggers are O(n), renderers are O(1). Each trigger renders a plain <button> with ARIA attributes — no Base UI hooks, no Floating UI, no portal, no positioning logic. The expensive parts (Floating UI, portals, popup DOM, animations) live in a single renderer mounted once at the app root. Adding 200 triggers to a page adds 200 buttons and zero additional popup infrastructure.

  • One popup at a time. Only one tooltip, one menu, and one popover can be open simultaneously. The singleton renderer subscribes to a global store and re-anchors to whichever trigger activated it. Switching between triggers swaps the anchor and content — no mount/unmount of the popup tree.

  • Config is per-instance, rendering is shared. Each Root registers its configuration (positioning, callbacks) in a lightweight in-memory registry. When a trigger activates, the renderer looks up the config by trigger id and applies it. The popup DOM, portal, and positioning logic are reused across all instances.

  • Zero idle cost. When no popup is open, the renderer is dormant — no Floating UI computations, no scroll/resize listeners, no portal in the DOM. Cost scales with interactions, not with the number of triggers on the page.

Custom trigger element

Tooltip.Trigger, Menu.Trigger, and Popover.Trigger accept a render prop to replace the default <button> with a custom element.

Element form — pass a React element:

<Menu.Trigger render={<MyButton variant="ghost" />}>
  Open menu
</Menu.Trigger>

Function form — receive the full props object:

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

Your custom component must forward ref to its underlying DOM element.

Styling

Each component applies default class names (slithy-tooltip-*, slithy-menu-*) that can be overridden by passing your own className. Default styles use CSS custom properties for theming:

/* Tooltip */
--slithy-tooltip-bg
--slithy-tooltip-color
--slithy-tooltip-font-size
--slithy-tooltip-padding
--slithy-tooltip-radius
--slithy-tooltip-max-width

/* Popover */
--slithy-popover-bg
--slithy-popover-color
--slithy-popover-font-size
--slithy-popover-padding
--slithy-popover-radius
--slithy-popover-max-width
--slithy-popover-shadow
--slithy-popover-border
--slithy-popover-description-color

/* Menu */
--slithy-menu-bg
--slithy-menu-color
--slithy-menu-font-size
--slithy-menu-padding
--slithy-menu-radius
--slithy-menu-min-width
--slithy-menu-shadow
--slithy-menu-border
--slithy-menu-item-padding
--slithy-menu-item-highlighted-bg
--slithy-menu-separator-color
--slithy-menu-group-label-padding
--slithy-menu-group-label-color

Custom animations

Base UI exposes data-open, data-starting-style, and data-ending-style attributes on popup elements, so you can drive enter/leave animations from open state. The default styles use CSS transitions with these attributes.