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

@promise-inc/ui-states

v0.1.4

Published

Auto-generated skeleton loading states from real DOM — zero config React component

Readme

@promise-inc/ui-states

Auto-generated skeleton loading states from real DOM — zero config React component.

Why?

Building skeleton loading states is tedious and fragile:

  • You have to manually create skeleton components that mirror real layouts
  • Every UI change means updating skeletons too
  • Most skeleton libraries require explicit configuration per component
  • Inconsistent loading states across the app

ui-states generates accurate skeletons automatically from your real DOM — zero manual work.

Features

  • Auto-skeleton: Generates skeleton loading states based on your actual component DOM structure
  • Zero config: Just wrap your component — no manual skeleton building
  • TanStack Query support: Pass query objects directly
  • Smart caching: Cache skeleton trees in sessionStorage with viewport-aware invalidation
  • Dark mode: Built-in dark mode support via Tailwind dark: variants
  • Accessible: Proper aria-busy, role="status", and aria-label attributes
  • Tree-shakeable: Import only what you need
  • Lightweight: No dependencies besides React

Install

npm install @promise-inc/ui-states

Peer dependencies: react >= 18, react-dom >= 18

Note: This library outputs Tailwind CSS classes (animate-pulse, bg-neutral-200/60, rounded-*, flex, grid, etc). Make sure your Tailwind config scans this package:

// tailwind.config.js
content: [
  // ...your paths
  './node_modules/@promise-inc/ui-states/dist/**/*.{js,cjs}',
]

Quick Start

import { UIStates } from '@promise-inc/ui-states';

function ProductsPage() {
  const [products, setProducts] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchProducts()
      .then(setProducts)
      .catch(setError)
      .finally(() => setLoading(false));
  }, []);

  return (
    <UIStates data={products} loading={loading} error={error}>
      <ProductList products={products} />
    </UIStates>
  );
}

Usage with TanStack Query

import { UIStates } from '@promise-inc/ui-states';
import { useQuery } from '@tanstack/react-query';

function ProductsPage() {
  const query = useQuery({ queryKey: ['products'], queryFn: fetchProducts });

  return (
    <UIStates query={query}>
      <ProductList products={query.data} />
    </UIStates>
  );
}

Custom Empty and Error States

<UIStates
  data={data}
  loading={isLoading}
  error={error}
  emptyState={<MyCustomEmptyView />}
  errorState={(error, retry) => (
    <MyCustomError error={error} onRetry={retry} />
  )}
>
  <ProductList />
</UIStates>

Caching

Enable skeleton caching to avoid re-measuring the DOM on subsequent loads:

<UIStates
  query={query}
  enableCache
  cacheKey="products-page"
>
  <ProductList products={query.data} />
</UIStates>

Cache is stored in sessionStorage and auto-invalidates when:

  • TTL expires (5 minutes)
  • Viewport size changes significantly (> 50px difference)

API Reference

<UIStates> Props

| Prop | Type | Description | |------|------|-------------| | children | ReactNode | The content to render on success | | data | unknown | Data to check for empty state | | loading | boolean | Manual loading flag | | error | unknown | Manual error value | | query | QueryLike | TanStack Query result object | | emptyState | ReactNode | Custom empty state component | | errorState | ReactNode \| (error, retry?) => ReactNode | Custom error state | | emptyCheck | (data: unknown) => boolean | Custom empty data checker | | enableCache | boolean | Enable skeleton caching (default: false) | | cacheKey | string | Cache key for sessionStorage | | className | string | Class for the wrapper div | | skeletonClassName | string | Class for the skeleton container |

Exported Hooks

| Hook | Description | |------|-------------| | useUIState | Resolves current UI state from props or query | | useSkeletonTree | Coordinates DOM measurement and skeleton generation | | useResizeObserver | ResizeObserver with debounce |

Exported Components

| Component | Description | |-----------|-------------| | UIStates | Main orchestrator component | | SkeletonRenderer | Renders a skeleton tree | | ErrorRenderer | Default error state | | EmptyRenderer | Default empty state | | FallbackSkeleton | Generic fallback skeleton |

How It Works

  1. When loading is active, children are rendered with visibility: hidden and position: absolute for DOM measurement
  2. The DOM Walker recursively traverses the hidden tree, measuring each element's bounding rect and computed styles
  3. The Skeleton Generator maps the measured tree into SkeletonNode[] with dimensions, layout info (flex/grid), gaps, and border-radius
  4. The Skeleton Renderer renders the tree as div elements with Tailwind classes (animate-pulse, bg-neutral-200/60)
  5. When loading ends, the skeleton is removed and real children are shown

How to report bugs

To report a bug, please first read our guide on opening issues.

How to contribute code

To open a pull request, please first read our guide on opening pull requests, which outlines our process for RFCs and pull requests.

Also by Promise Inc.

| Package | Description | |---------|-------------| | @promise-inc/ai-guard | Detect AI-generated code patterns | | @promise-inc/ps-guard | Lighthouse-based performance guard | | @promise-inc/fs-guard | Validate project folder and file structure | | @promise-inc/devlog | Logger with automatic context (file + line) | | @promise-inc/dev-reel | Animated SVG previews for READMEs |


Developed by Promise Inc.

License

MIT © Promise Inc.