NebulaKit is designed to stay fast as your app grows. Components do only the work they need, styling runs without a runtime and the bundle stays small. This page covers how that is achieved and what it means for your app.
Every component renders through Box, so layout, responsive props, colors and interaction states work the same way everywhere.
An improvement to Box reaches every component at once, nothing is optimized in one component and missed in another.
Its cost is measured in production builds, the same code your app ships.
A non-responsive prop value renders with the element and needs no extra work after render.
Only responsive values are applied after render, only when a value or the breakpoint changes.
A component without a responsive value never re-renders when the screen crosses a breakpoint.
Colors and surfaces are drawn only on elements that need them. Layout-only elements, such as stacks and grids, skip the color rules entirely.
Color transitions run only on interactive elements. Ripple listeners attach only to elements that show a ripple.
All NebulaKit components share one set of media query listeners, with no resize listener.
Components hear only about breakpoint changes, never about every pixel of a resize.
The breakpoint is read safely under concurrent rendering, so all components in a render see the same one.
Styles are compiled at build time into one stylesheet. Nothing is generated or injected while rendering.
The browser caches the stylesheet like any static CSS file.
Components describe their appearance through data attributes and CSS custom properties, the stylesheet does the rest.
Tones, variants and interaction states are resolved by the stylesheet.
Hover, active and other state colors are derived in the browser with OKLCH. No color is computed in JavaScript.
Switching the theme or brand recolors your app through the stylesheet in one update.
Color transitions pause during the switch, so your app recolors at once instead of fading every element. An OS switch under the system theme works the same way.
Transitions turn on only after the first paint, so nothing animates while your app loads.
Packages ship as tree-shakable ES modules. Only the stylesheet is marked as a side effect, so bundlers drop the components an app doesn't use.
Only the icons the library uses ship, not the whole Lucide set.
The Pro bundle loads its shared code from Core, so an app using both ships it once.
The whole Core bundle is 29.0 kB of JavaScript minified and gzipped, its stylesheet 20.0 kB gzipped.
Browser-only work is guarded, so every component renders on the server.
Optimizations are checked with benchmarks in a production build before they ship.
Component and logic specs cover the behavior, real-browser tests verify the computed styles of themes, brands and colors.
Every component is audited for accessibility in every form it can take, such as open, selected, disabled or showing an error.
Pass a plain value when it doesn't change by breakpoint. p="16px" costs nothing extra, p={{ base: '16px' }} makes the component follow the screen.
Use tone and variant props instead of custom colors, they come with every interaction state and theme from the stylesheet.
Switch themes freely, a switch is one recolor without a page-wide animation.
Import components from the package root, your bundler keeps only the ones you use.