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

@sito/ui

v0.10.2

Published

Low-level React UI primitives for Sito packages and apps.

Readme

@sito/ui

Low-level React UI primitives for Sito packages and apps.

Install Styles

Styles are optional. Import the full stylesheet when you want the default theme and component styles:

import "@sito/ui/styles.css";

Import only the theme variables when you want to provide component CSS yourself:

import "@sito/ui/theme.css";

Public API

import {
  Button,
  BUTTON_COLOR_VARIANTS,
  BUTTON_SIZES,
  BUTTON_VARIANTS,
  ContextMenu,
  ContextMenuItem,
  ContextMenuSeparator,
  Dialog,
  DialogActions,
  DIALOG_INITIAL_FOCUS,
  Icon,
  IconButton,
  ICON_BUTTON_SIZES,
  Spinner,
  Toast,
  TOAST_COLORS,
  TOAST_PLACEMENTS,
  ToastProvider,
  Tooltip,
  useContextMenu,
  useDialog,
  useToast,
} from "@sito/ui";

The runtime constants BUTTON_COLOR_VARIANTS, BUTTON_VARIANTS, BUTTON_SIZES, ICON_BUTTON_SIZES, DIALOG_INITIAL_FOCUS, TOAST_COLORS, and TOAST_PLACEMENTS expose the supported values for public component contracts.

Exported types include ButtonProps, ButtonSize, IconButtonProps, DialogProps, DialogActionsProps, DialogState, IconProps, IconButtonSize, and SpinnerProps, UseDialogReturn, ToastOptions, ToastFunction, ToastProviderProps, plus the corresponding context-menu props and hook return types.

Spinner

Spinner is the shared indeterminate-progress primitive used by Button loading states and standalone feedback. Provide label when the spinner owns the accessible loading announcement; omit it when surrounding content already provides that context.

<Spinner label="Loading messages" />
<Spinner />

Context Menu

ContextMenu owns viewport clamping, focus restoration, outside dismissal and keyboard navigation. Consumers own the menu's actions and wording:

const menu = useContextMenu<string>();

<button
  type="button"
  onContextMenu={(event) => {
    event.preventDefault();
    menu.openAt(event.clientX, event.clientY, "item-id");
  }}
>
  Item
</button>

<ContextMenu
  open={menu.open}
  position={menu.position}
  onClose={menu.close}
  ariaLabel="Item actions"
>
  <ContextMenuItem onClick={menu.close}>Open</ContextMenuItem>
  <ContextMenuSeparator />
  <ContextMenuItem disabled>Delete</ContextMenuItem>
</ContextMenu>;

Apps with an existing overlay or hotkey scope can keep dismissal in that layer with closeOnEscape={false}, closeOnTab={false} and closeOnPointerDownOutside={false}. The component forwards its menu element ref for adapters that need compatible positioning or containment checks. Those adapters may also use clampToViewport={false} when their existing state layer already owns clamping. Closing menus remain mounted for --sito-ui-motion-duration-normal, restore focus to the trigger when closing starts, and ignore pointer and keyboard input during the exit. The built-in fade and scale motion is disabled when reduced motion is preferred.

Button Sizes

Button supports size="sm" | "md" | "lg" for padding and height. md is the default size.

<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

Icon

Icon renders any glyph (SVG, icon-font element, text) and blurs between glyphs when it changes. IconButton renders its icon through Icon, so toggles such as star/unstar animate with no extra code.

<Icon icon={<FontAwesomeIcon icon={starred ? faStar : faStarOutline} />} />
<IconButton aria-label="Star" icon={<StarIcon filled={starred} />} />

A swap happens when the glyph's element type, key or props change (compared deeply, so re-rendering the same icon does not animate). Pass iconKey (on Icon or IconButton) to set the identity yourself: then only a new iconKey animates. Icon is decorative (aria-hidden) unless it gets a label.

The old glyph fades out with blur while the new one fades in, over --sito-ui-icon-swap-duration (200ms), blurring by --sito-ui-icon-swap-blur (4px) and scaling from --sito-ui-icon-swap-scale (0.8). With prefers-reduced-motion: reduce, the glyph swaps at once.

Icon Button Sizes

IconButton supports size="sm" | "md" | "lg" for the button container. md is the default size.

<IconButton aria-label="Add" icon="+" size="sm" />
<IconButton aria-label="Add" icon="+" size="md" />
<IconButton aria-label="Add" icon="+" size="lg" />

The container sizes are 28px, 40px, and 48px; their default icon sizes are 16px, 20px, and 24px. Override a specific token (--sito-ui-size-icon-button-icon-sm, -md, or -lg) for theme-wide changes, or use iconSize / --sito-ui-icon-button-icon-size for one button.

Tooltip

Wrap a single trigger to show its visible label on pointer hover or keyboard focus. The tooltip is decorative and hidden from assistive technology, so keep an accessible name on icon-only controls:

<Tooltip label="Next slide" side="top" align="center">
  <IconButton aria-label="Next slide" icon={<NextIcon />} />
</Tooltip>

hotkey shows the control's keyboard shortcut after the label in a <kbd> (<Tooltip label="Next slide" hotkey="→">). Like the label, it is hidden from assistive technology: announce the shortcut with aria-keyshortcuts on the trigger.

side supports top and bottom; align supports start, center, and end. Hover is enabled only on devices that support hover. Press Escape to dismiss it while the trigger is focused. Tooltip surface, text, radius, shadow, font size, motion, and the hotkey's surface, border, and radius (--sito-ui-tooltip-hotkey-surface, --sito-ui-tooltip-hotkey-border, --sito-ui-radius-tooltip-hotkey) can be customized with --sito-ui-tooltip-* tokens. The tooltip stacks above context menus using --sito-ui-z-index-tooltip.

Toast

Wrap the app in ToastProvider once, then call useToast() anywhere below it:

<ToastProvider placement="bottom-right">
  <App />
</ToastProvider>;

const toast = useToast();

const id = toast({ message: "Saving…", duration: 0, dismissible: false });
toast.update(id, { message: "Saved", color: "success", duration: 3000 });
toast({ message: "Not saved", color: "error" });
toast({
  message: "Slide deleted",
  action: { label: "Undo", onClick: restore },
});
toast.dismiss(id);
  • color: default, success, error, or warning. icon adds a leading icon. variant can be accent (default) or filled; filled success, error and warning toasts use the matching --sito-ui-color-*-default background and --sito-ui-color-*-text foreground tokens. Toasts with color: "default" keep their surface appearance with either variant.
  • duration in ms; 0 or Infinity keeps the toast until it is dismissed. Defaults to the provider's duration (5000), or Infinity for errors.
  • toast.update(id, …) (or toast({ id, … }) with an existing id) replaces the toast in place and restarts its timer.
  • action renders a text button; it dismisses the toast after onClick unless dismiss: false. dismissible: false hides the close button.
  • Provider props: variant (accent by default or filled), placement (top-left, top-center, top-right, bottom-left, bottom-center, bottom-right), duration, max (default 3, the oldest leaves first), label (region name, default "Notifications"), closeLabel, closeIcon, viewportClassName, toastClassName, and portalContainer.

The viewport is a polite live region. Default, success and warning toasts use role="status"; errors use role="alert". Hovering or focusing a toast pauses its timer, and Escape dismisses the focused toast. Toasts animate in and out and skip the animation with prefers-reduced-motion. Tune them with --sito-ui-size-toast-width, --sito-ui-radius-toast, --sito-ui-shadow-toast, --sito-ui-z-index-toast, --sito-ui-toast-enter-duration, --sito-ui-toast-exit-duration, and --sito-ui-toast-accent-width (defaults to four thin borders). To remove the accent stripe with tokens, set its width to var(--sito-ui-border-width-thin) and --sito-ui-toast-accent-border: var(--sito-ui-color-border). The accent color follows the --sito-ui-color-*-default tokens.

Motion

Buttons shrink while pressed and return to their normal size on release. The press transition uses --sito-ui-motion-duration-press (100ms) and --sito-ui-motion-easing-press; Button uses --sito-ui-button-active-scale (0.97), while IconButton uses --sito-ui-icon-button-active-scale (0.9). Override these tokens to tune the feedback. With prefers-reduced-motion: reduce, the press scale and its transition are disabled. The spinner keeps spinning at its reduced-motion duration.

Pressable

Add sito-ui-pressable to any clickable element (cards, list rows, custom controls) to get the same press feedback:

<div className="sito-ui-pressable" role="button" tabIndex={0}>
  Open
</div>

It scales to --sito-ui-pressable-active-scale (0.97) while :active, skips :disabled and aria-disabled="true" elements, and turns off with prefers-reduced-motion: reduce. It uses the standalone scale property, so the element's own transform is kept. It sets transition; if the element declares its own, add scale var(--sito-ui-motion-duration-press) var(--sito-ui-motion-easing-press) to it.

Buttons as Links

Button and IconButton render a native <button> by default. Use as to render a link with the same classes, sizes, variants and colors:

import { Link } from "react-router";

<Button as="a" href="/report.pdf" download>Download PDF</Button>
<IconButton as="a" href="/docs" target="_blank" rel="noreferrer" aria-label="Docs" icon={<DocsIcon />} />
<IconButton as={Link} to="/" aria-label="Exit" icon={<ExitIcon />} />

Props follow the rendered element: anchor attributes with as="a", the component's props with as={Link}. Use links for navigation and downloads so users keep new-tab, copy-link and the URL preview; keep <button> for actions.

  • type defaults to "button" only for <button>.
  • disabled (and loading) on a link sets aria-disabled="true", tabIndex={-1} and blocks click and middle-click navigation. Router links that skip navigation on event.defaultPrevented (React Router does) stay put.
  • ref points to the rendered element.

Development

Run the component test suite with:

pnpm test:run

Start Storybook with:

pnpm storybook

Dialog Layout

Dialogs are min(100%, var(--sito-ui-dialog-max-width)) wide, whatever their content. The token defaults to 32rem; override it at :root or any theme scope. mobileFullScreen still takes the full viewport on small screens.

When a dialog is taller than the viewport, it scrolls itself while the header (title and close button) stays at the top and DialogActions stays at the bottom. Keep DialogActions as the dialog's last content so it can stick.

Dialog Exit Transitions

Dialog fades in with a small rise and scale, then fades out when closed. It stays mounted for the exit animation before restoring focus and releasing body scroll. Set exitDurationMs to override the default 180 ms exit duration; 0 unmounts immediately:

<Dialog open={open} onClose={close} exitDurationMs={220}>
  Dialog content
</Dialog>

The --sito-ui-dialog-enter-duration, --sito-ui-dialog-exit-duration, and --sito-ui-dialog-enter-transform theme tokens tune the built-in animation. Setting both durations to 0ms disables it. Reduced-motion preferences disable the animation and exit delay automatically.

The built-in animation uses unlayered CSS, so it overrides consumer animation utilities on the dialog and backdrop. If you provide your own dialog animation, remove those utilities or set both dialog duration tokens to 0ms. Focus is restored after the exit animation only when focus remains in the closing dialog or has fallen back to the document body.

The close button uses the neutral text color by default. Set closeButtonColor="error" on Dialog when a red close button is preferred.

DialogActions accepts primaryProps and cancelProps to pass button props, including as and link attributes. Omit cancelText to leave out the cancel action:

<DialogActions
  primaryText="Sign in"
  primaryProps={{ as: Link, to: "/sign-in/" }}
/>

Scope

This package only exposes generic primitives. It does not include dashboard workflows, forms, translations, icon packs, routing, storage, app providers, or data fetching. ToastProvider is the one provider, and it only holds the toast queue.