zeroUI

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

terminal
# 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/inventory

Examples

A cargo hold

Sealed crate · 2 in the hold · rare

inventory-hold.tsx
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

PropTypeDefaultDescription
columnsnumber6Slots in a row; the arrow keys move by it.

InventorySlot

PropTypeDefaultDescription
rarity"common" | "uncommon" | "rare" | "epic" | "legendary"—Lights the bottom edge and a glow in the tier's colour.
selectedboolean—Frames it in the accent (aria-pressed).
countnumber—Shown in the corner when more than one.
cooldownnumber—What is left of a cooldown, 0–1.
childrenReactNode—What it holds; empty shows the hatching.

Also takes every prop of Base UI's Button.