Inventory
An inventory grid for React: square slots with a rarity glow, a count in the corner and a cooldown sweep, moved through with the arrow keys.
Install
# once per project: where @zeroui items come frompnpm dlx shadcn@latest registry add @zeroui=https://zeroui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zeroui/inventoryExamples
A cargo hold
Sealed crate · 2 in the hold · rare
import { useState } from "react";import { CellIcon, ChipIcon, CrateIcon, FlareIcon, KeyIcon, KitIcon,} from "@/components/zero/icons";import { Inventory, InventorySlot } from "@/components/zero/inventory";const cargo = [ { name: "Sealed crate", icon: CrateIcon, rarity: "rare", count: 2 }, { name: "Power cell", icon: CellIcon, rarity: "uncommon", count: 6 }, { name: "Signal flare", icon: FlareIcon, rarity: "common", count: 12 }, { name: "Gate key", icon: KeyIcon, rarity: "legendary", count: 1 }, { name: "Route chip", icon: ChipIcon, rarity: "epic", count: 1 }, { name: "Repair kit", icon: KitIcon, rarity: "uncommon", count: 3 }, { name: "Spare cell", icon: CellIcon, rarity: "common", count: 2 },] as const;export default function InventoryHold() { const [chosen, setChosen] = useState(0); const item = cargo[chosen]; return ( <div className="grid gap-4"> <Inventory columns={6} aria-label="Cargo hold"> {cargo.map((thing, index) => ( <InventorySlot key={thing.name} rarity={thing.rarity} count={thing.count} selected={index === chosen} cooldown={thing.name === "Repair kit" ? 0.6 : undefined} aria-label={`${thing.name}, ${thing.count}`} onClick={() => setChosen(index)} > <thing.icon /> </InventorySlot> ))} {Array.from({ length: 11 }, (_, index) => ( // biome-ignore lint/suspicious/noArrayIndexKey: empty slots have nothing else to tell them apart <InventorySlot key={index} aria-label="Empty" /> ))} </Inventory> <p className="font-medium font-zero-text text-zero-sm text-zero-text-2"> <span className="font-bold font-zero text-zero-text italic"> {item.name} </span>{" "} · {item.count} in the hold · {item.rarity} </p> </div> );}Behaviour
A slot is a black well with a quiet line. rarity lights its bottom edge and a glow rising from it in the tier's colour; selected frames it in the accent; cooldown (what is left, 0–1) darkens that share of a turn, like a clock; an empty slot shows the hatching. A count over one sits in the corner.
The grid is one stop on Tab. The arrow keys move between slots in two dimensions, Home and End to the ends of a row, with Ctrl to the ends of the bag; Tab comes back to the slot last visited. Each slot is a button: give it an aria-label that says what it holds, and the bag one that names it. The slot size is --zero-slot.
The item art in the demo is zeroUI's own item icons: crate, cell, flare, key, chip and kit.
Props
Inventory
| Prop | Type | Default | Description |
|---|---|---|---|
| columns | number | 6 | Slots in a row; the arrow keys move by it. |
InventorySlot
| Prop | Type | Default | Description |
|---|---|---|---|
| rarity | "common" | "uncommon" | "rare" | "epic" | "legendary" | — | Lights the bottom edge and a glow in the tier's colour. |
| selected | boolean | — | Frames it in the accent (aria-pressed). |
| count | number | — | Shown in the corner when more than one. |
| cooldown | number | — | What is left of a cooldown, 0–1. |
| children | ReactNode | — | What it holds; empty shows the hatching. |
Also takes every prop of Base UI's Button.