NebulaKit aims to cover common interfaces with ready-made components, and the set keeps growing. Still, no library can cover every case. When your app needs a small piece that no component offers, you can build it from Box together with the components you already have. This page explains when that works well and what it leaves to you.
Small, mostly static pieces, such as a stat tile, a summary panel, or a row of actions on a surface of its own.
A simple interaction on a single element, such as a card that selects or opens something.
Not complex widgets with a keyboard model of their own, such as a combobox, a tree, or a drag and drop list. A component builds that behavior in; with Box all of it is yours to write.
Check the components before you start. When one fits, it is always the better choice.
Colors, surfaces, and lines from the styling engine, in every theme and inside StylingIsland, through color, tone, bg, border, surface, and line.
Hover, press, and focus states once it is interactive, its own look while selected or disabled, and a ripple with showRipple.
Flex and grid layout on the element itself, with responsive values.
Start and end props that follow the direction in right-to-left layouts.
Server rendering and hydration the same way as every component.
Semantics: Box renders a div unless elemTag says otherwise. Give an interactive Box a native element, such as elemTag="button", so it gets the role and keyboard support of that element. For navigation, wrap it in Link.
Keyboard: Box adds no role, no tabIndex, and no key handling of its own, so a div with onClick can't be reached or pressed from the keyboard.
States and names: interactive and selected change only the look. Expose the state yourself, such as aria-pressed, and name an element that has no visible text, both through elemAttrs. No warning tells you a name is missing.
Valid HTML: a button holds only inline content, so text inside it needs elemTag="span", and its content is laid out with flex on the Box itself instead of a Stack.
Size: Box has no scale. Take its spacing from NEB_LENGTH and match the scale of the components next to it.
Design: Box has no variants or defaults of its own, so every look you give it is a decision to keep consistent across your app.
Maintenance: the library keeps Box up to date, but what you build from it is your own code to test and maintain.
The guidelines on Best practices, such as soft surfaces for large interactive areas, apply to Box as much as to any component.
A static tile composed of Box, Stack, Text, and Button
import { Box, Button, NEB_LENGTH, Stack, Text } from '@nebula-kit/core' export const StorageTile = ({ used, limit, onUpgrade }) => ( <Box bg="soft" tone="lower" p={NEB_LENGTH.px_024}> <Stack alignX="start" gap={NEB_LENGTH.px_012}> <Text typography="caption">Storage</Text> <Text typography="h4" elemTag="p"> {used} of {limit} GB </Text> <Button scale="sm" onClick={onUpgrade}> Upgrade </Button> </Stack> </Box>)Avoid: a div that the keyboard can't reach and that never tells it is selected
<Box interactive selected={selected} bg="soft" tone="low" p={NEB_LENGTH.px_016} onClick={onSelect}> <Text bold>{name}</Text> <Text>{price}</Text></Box>Prefer: a native button that exposes its state
<Box elemAttrs={{ type: 'button', 'aria-pressed': selected }} elemTag="button" interactive selected={selected} bg="soft" tone="low" cursor="pointer" display="flex" flexDirection="column" gap={NEB_LENGTH.px_004} p={NEB_LENGTH.px_016} onClick={onSelect}> <Text elemTag="span" bold>{name}</Text> <Text elemTag="span">{price}</Text></Box>If you keep building the same piece from Box, it may be worth a component of its own. Request it on the Feedback page, so it can be considered for a future release.