Performance

Hollows UI is designed to add minimal overhead to your application.

Bundle Size

The Hollows UI runtime is lightweight. The core library and framework adapters are designed to minimize your bundle footprint.

~2.1 kB

Core runtime (gzipped)

~0.8 kB

React adapter (gzipped)

~0.3 kB

Per illustration (avg)

The generated registry JSON is typically under 1 kB for 10 components. Illustrations are the largest part of the payload, but can be lazy-loaded to avoid impacting initial page load.

Tree-shaking

Hollows UI is fully tree-shakeable. Only the components and illustrations you actually use end up in your production bundle.

tsx
// Only the Hollow component and the 'empty-inbox' illustration// are included in the bundleimport { Hollow } from 'hollows-ui/react'<Hollow name="user-inbox" empty={!data}>  <InboxList items={data} /></Hollow>

The CLI generates a registry that only references the illustrations your app actually needs. Unused illustrations from the built-in set are never bundled.

What gets tree-shaken

  • Unused illustrations (SVG code never enters the bundle)
  • Unused framework adapters (only your framework is included)
  • Unused themes (only the active theme ships)
  • Unused locale data (only configured locales are bundled)

Lazy Illustration Loading

By default, illustrations are inlined as SVG strings in the registry. For applications with many empty states, enable lazy loading to defer illustration fetching until the empty state is actually displayed.

hollows.config.ts
ts
import { defineConfig } from 'hollows-ui'export default defineConfig({  performance: {    lazyIllustrations: true,  },})

When enabled, the build step outputs illustrations as separate files in src/hollows/illustrations/ and the registry references them by path. The runtime loads them on demand using dynamic imports.

tsx
// With lazy loading enabled, illustrations are code-split// The SVG is only fetched when the empty state renders<Hollow name="user-inbox" empty={!data}>  <InboxList items={data} /></Hollow>// ^ If data exists, the illustration is never loaded

Tip: Lazy loading adds a brief flash when the empty state first appears. Set preload: true on critical empty states to prefetch their illustration during idle time.

Incremental Builds

The CLI tracks which components have changed since the last build. On subsequent runs, only modified or new components are re-processed.

Terminal
bash
# First build — processes all componentsnpx hollows-ui build# Built 12 empty states in 340ms# After editing one componentnpx hollows-ui build# Built 1 empty state in 45ms (11 cached)

The cache is stored in node_modules/.hollows-cache/. To force a full rebuild, use the --force flag.

bash
# Force full rebuildnpx hollows-ui build --force# Watch mode — rebuilds on file changesnpx hollows-ui build --watch

Runtime Performance

At runtime, the Hollow component is essentially a conditional renderer. When data is present, it renders your children with zero overhead. When empty, it renders a static SVG and text -- no animation libraries, no heavy DOM operations.

Performance characteristics

MetricValue
Initial render (empty state)< 1ms
Re-render on data change< 0.5ms
DOM nodes (empty state)8-15 nodes
Memory overhead per instance< 2 KB
CSS-only responsive behaviorNo JS resize listeners

Measuring Impact

Use the built-in analyze command to see a breakdown of what Hollows adds to your bundle.

Terminal
bash
npx hollows-ui analyze# Output:# Bundle Analysis# ─────────────────────────────────# Core runtime:        2.1 kB (gzip)# React adapter:       0.8 kB (gzip)# Registry (12 items): 0.9 kB (gzip)# Illustrations (8):   2.4 kB (gzip)# Theme (minimal):     0.3 kB (gzip)# ─────────────────────────────────# Total:               6.5 kB (gzip)