Component layers
L0–L3 layering, data injection and composition.
Four layers
| Layer | Package path | Count | Examples |
|---|---|---|---|
| L0 | @tennantite/tokens, @tennantite/textures |
2 | colors, type, textures, icons |
| L1 | packages/ui/src/primitives |
13 | Button, Input, Tabs, Dialog |
| L2 | packages/ui/src/containers |
12 | Panel, Grid, Slot, ScreenPanel |
| L3 | packages/ui/src/screens |
41 | HUD 9, container screens 24, mod screens 5, menu screens 3 |
L1 depends only on Base UI and tokens; L2 composes L1; L3 composes L1–L2 and accepts data from the consumer.
Composition over black boxes
A full screen is not one giant component. Inventory, for example, uses ScreenPanel for a 176×166 absolute-coordinate base, then composes PlayerInventory and slots:
<InventoryScreen
mainItems={[{ count: 64, icon: "stone-block" }]}
selectedIndex={0}
onSlotClick={(area, index) => {
console.log(area, index);
}}
/>
For full control, skip InventoryScreen and assemble the same layout from ScreenPanel, Grid and Slot.
Data shapes
type ScreenArea =
| "armor" | "craft" | "craft-result" | "hotbar"
| "input" | "main" | "offhand" | "output" | "secondary";
interface ScreenItem {
icon?: PixelIconName;
count?: number;
src?: string;
tooltip?: string;
}
type ScreenSlot = ScreenItem | null | undefined;
padSlots(slots, length) truncates extras and fills gaps so fixed-length slot arrays never collapse the layout.
Interaction boundary
- Slot clicks share
onSlotClick(area, index); each component declares its areas. - Drag and drop is not supported. It needs pointer capture, cross-container state and placement rules, beyond a pure UI library.
- HUD layers are overlays: they do not join the focus order and do not intercept page input.
- Components read no saves, send no requests and contain no game rules.
RSC compatibility
Presentational components use no useEffect or browser APIs and can render as Server Components. Stateful modules opt into "use client"; useHasHover returns false on the server and reads matchMedia inside an effect. See SSR / RSC.