ui-architecture

UI component patterns for the Nums game client — Radix primitives, elements, containers, theming, Storybook conventions. Use when creating or modifying UI components, adding storybook stories, or working with the design system.

9 stars

Best use case

ui-architecture is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

UI component patterns for the Nums game client — Radix primitives, elements, containers, theming, Storybook conventions. Use when creating or modifying UI components, adding storybook stories, or working with the design system.

Teams using ui-architecture should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/ui-architecture/SKILL.md --create-dirs "https://raw.githubusercontent.com/cartridge-gg/nums/main/.agents/skills/ui-architecture/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ui-architecture/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ui-architecture Compares

Feature / Agentui-architectureStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

UI component patterns for the Nums game client — Radix primitives, elements, containers, theming, Storybook conventions. Use when creating or modifying UI components, adding storybook stories, or working with the design system.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# UI Architecture

React/TypeScript frontend in `client/src/` using Tailwind CSS, Radix UI, CVA (class-variance-authority), and Storybook.

## Component Hierarchy

```
client/src/components/
├── icons/          # SVG icon components (see ui-architecture-icon skill)
├── ui/             # Radix UI primitives (Button, Select, Tabs, etc.)
├── elements/       # Game-specific UI elements (Stat, Balance, PowerUp, etc.)
├── containers/     # Page-level compositions (Header, Games, Leaderboard, etc.)
├── animations/     # Motion components (Countup, Countdown)
└── layouts/        # Layout wrappers
```

## Shared Utilities

- **`cn()`** from `@/lib/utils` — merges Tailwind classes safely (clsx + tailwind-merge)
- **`iconVariants()`** from `@/components/icons` — CVA variants for icon sizing
- **`useAudio()`** from `@/context/audio` — sound effects for interactive components

---

# Git Hygiene (MANDATORY)

## Always pull before branching

Before creating a new feature branch, **always pull and rebase the base branch** to ensure you start from the latest state. Stale branches cause merge conflicts and wasted effort.

```bash
# CORRECT: Always do this before creating a feature branch
git checkout main && git pull --rebase origin main && git checkout -b feat/my-feature

# WRONG: Creating a branch from a stale main
git checkout -b feat/my-feature  # main might be behind origin
```

---

# Barrel Export Rule (MANDATORY)

## Every new component MUST be exported from its directory's `index.ts`

Every component directory has a barrel `index.ts` file that re-exports all components. When creating a new component, **always add an export line** to the directory's `index.ts`. A component that isn't exported is invisible to the rest of the app.

### Export pattern

```ts
// client/src/components/elements/index.ts
export * from "./my-new-component";
```

### Where to export

| Component tier | Barrel file                                       |
| -------------- | ------------------------------------------------- |
| elements       | `client/src/components/elements/index.ts`         |
| containers     | `client/src/components/containers/index.ts`       |
| icons          | `client/src/components/icons/<category>/index.ts` |
| animations     | `client/src/components/animations/index.ts`       |
| ui             | `client/src/components/ui/` (individual imports)  |

### Checklist (BLOCKING — component is NOT done without these)

- [ ] Component file created (`my-component.tsx`)
- [ ] Export added to directory `index.ts` (`export * from "./my-component"`)
- [ ] Storybook file created (`my-component.stories.tsx`)
- [ ] `pnpm format` and `pnpm lint:check` pass (see Validation section)

---

# Validation Rules (MANDATORY)

## Always format and lint after every change

After **every modification** to `client/` files, run:

```bash
pnpm format
pnpm lint:check
```

- **`pnpm format`** — auto-fixes formatting (Prettier). Run first.
- **`pnpm lint:check`** — reports lint errors (ESLint). Fix any errors before moving on.

This applies to ALL client changes: new components, prop updates, import changes, storybook files — everything. No exceptions. Do NOT batch these to the end; run them after each logical change.

---

# Storybook Rules (MANDATORY)

## Every component MUST have a storybook

When creating a new component, always create a `.stories.tsx` file alongside it. When modifying a component (new props, new variants, changed behavior), always update its storybook to cover the changes.

## Match existing storybook style

Before writing a storybook, **read 2-3 existing `.stories.tsx` files in the same directory** to match the exact code style. The patterns vary by component tier:

### Element storybook pattern

File: `client/src/components/elements/<component>.stories.tsx`

```tsx
import type { Meta, StoryObj } from "@storybook/react-vite";
import { MyComponent } from "./my-component";

const meta = {
  title: "Elements/My Component",
  component: MyComponent,
  parameters: {
    layout: "centered",
  },
  globals: {
    backgrounds: {
      value: "dark",
    },
  },
  argTypes: {
    myProp: {
      control: "text",
      description: "Description of the prop",
    },
    variant: {
      control: "select",
      options: ["default", "secondary"],
      description: "The visual variant",
    },
  },
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

// Keep it minimal — one Default story with representative data is enough
export const Default: Story = {
  args: {
    myProp: "value",
  },
};
```

### Container storybook pattern

Containers often need mock data or providers. Follow existing container stories for the wrapping pattern.

### Key conventions

- **`satisfies Meta<typeof Component>`** — always use `satisfies`, not `as`
- **`globals.backgrounds.value: "dark"`** — always dark background
- **`parameters.layout: "centered"`** — always centered layout
- **`argTypes`** — document every controllable prop with `control` and `description`
- **Keep stories minimal** — one `Default` story with representative mixed data is usually sufficient. Storybook controls (`argTypes`) let users explore other states interactively. Only add extra stories for **distinct CVA variants** (e.g. `variant: "secondary"`), not for different data permutations.
- **Use `fn()` from `storybook/test`** for callback props (`onClick`, `onChange`, etc.)
- **Title format**: `"Tier/Component Name"` — use spaces for compound names (e.g., `"Elements/Game Icon"`, `"Containers/Game Over"`, `"Elements/Staking Reward"`)

## When to update storybooks

| Change                   | Storybook Action                                               |
| ------------------------ | -------------------------------------------------------------- |
| New component            | Create `.stories.tsx` with one `Default` story                 |
| New CVA variant added    | Add one story for the new variant                              |
| New prop added           | Add `argTypes` entry (no new story needed — controls cover it) |
| Prop renamed/removed     | Update `argTypes` and affected stories                         |
| Behavior change          | Verify existing stories still make sense                       |
| Visual change only (CSS) | No storybook change needed (visual regression is automatic)    |

---

# Component Patterns

## Universal CVA Pattern (MANDATORY for all tiers)

**Every component** across elements, containers, animations, and covers MUST use `cva` + `VariantProps` + `cn()`. This is the universal pattern — no exceptions, even with a single `default` variant. Consistency and extensibility are non-negotiable.

### Standard pattern (elements, containers)

```tsx
import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";

export interface MyComponentProps
  extends
    React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof myComponentVariants> {
  // custom props
}

const myComponentVariants = cva("base-classes", {
  variants: {
    variant: {
      default: "default-classes",
    },
  },
  defaultVariants: {
    variant: "default",
  },
});

export const MyComponent = ({
  variant,
  className,
  ...props
}: MyComponentProps) => {
  return (
    <div className={cn(myComponentVariants({ variant, className }))} {...props}>
      {/* content */}
    </div>
  );
};
```

### ForwardRef pattern (ui primitives only)

`ui/` components additionally use `forwardRef` and named exports:

```tsx
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const myComponentVariants = cva("base-classes", {
  variants: {
    variant: {
      default: "default-classes",
      secondary: "secondary-classes",
    },
    size: {
      md: "size-md-classes",
      lg: "size-lg-classes",
    },
  },
  defaultVariants: {
    variant: "default",
    size: "md",
  },
});

export interface MyComponentProps
  extends
    React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof myComponentVariants> {}

const MyComponent = React.forwardRef<HTMLDivElement, MyComponentProps>(
  ({ className, variant, size, ...props }, ref) => (
    <div
      className={cn(myComponentVariants({ variant, size, className }))}
      ref={ref}
      {...props}
    />
  ),
);
MyComponent.displayName = "MyComponent";

export { MyComponent, myComponentVariants };
```

### Exceptions (do NOT use as precedent)

These components intentionally skip CVA because they are not styled components:

| Component                                           | Reason                                                                |
| --------------------------------------------------- | --------------------------------------------------------------------- |
| `ui/` Radix wrappers (select, tabs, dropdown, etc.) | Thin forwardRef wrappers around Radix primitives, no custom variants  |
| `og/*`                                              | OG image generation — uses inline `React.CSSProperties`, not Tailwind |
| `elements/toaster.tsx`                              | Sonner config wrapper, not a styled component                         |
| `elements/sound-controls.tsx`                       | Logic-only utility, no styled output                                  |

## Audio Integration

Interactive components integrate sound via `useAudio()`:

```tsx
import { useAudio } from "@/context/audio";

const MyButton = () => {
  const { playClick } = useAudio();
  return (
    <button
      onClick={() => {
        playClick(); /* action */
      }}
    >
      Click
    </button>
  );
};
```

---

# Theming

## Design Tokens

CSS variables in `client/src/themes/default.css` — 30+ color families with 9-level opacity scales (100-900).

Game-specific colors: `purple`, `mauve`, `yellow`, `red`, `green`, `pink`, `blue`, `brown`, `black`, `gray`, `white`.
Power colors: `double`, `down`, `reroll`, `wildcard`, `swap`, `halve`, `up`, `low`, `foresight`, `high`.
Trap colors: `ribbon`, `gem`, `magnet`, `windy`, `lucky`, `slots`, `bomb`, `ufo`, `glitchbomb`.

All mapped to Tailwind utilities via `client/src/themes/preset.ts`:

```
bg-purple-100, text-yellow-200, border-red-500, etc.
```

## Fonts

| Font        | CSS Class        | Use                     |
| ----------- | ---------------- | ----------------------- |
| PixelGame   | `font-pixelgame` | Primary retro aesthetic |
| PPNeueBit   | `font-ppneuebit` | Bold secondary          |
| DMMono      | `font-dmmono`    | Monospace data          |
| Circular-LL | `font-circular`  | Modern sans-serif       |

## Custom Animations

Defined in `client/src/index.css`:

- `reward-diff` — floating text (2s)
- `shimmer-reflect` — shiny text effect (6s)
- `multiplier-fire` — fire effect (1.75s)
- `pulse-border` — pulsing outline (3s)
- `toast-progress` — progress bar (4s)

---

# UI Primitives (Radix)

All in `client/src/components/ui/`. Use CVA for variants, `forwardRef` pattern, and integrate `useAudio()` for click sounds.

| Component | File                | Variants                                                                                    |
| --------- | ------------------- | ------------------------------------------------------------------------------------------- |
| Button    | `button.tsx`        | default, destructive, outline, secondary, muted, ghost, link × sm, lg, icon, sound, balance |
| Select    | `select.tsx`        | Radix Select with audio                                                                     |
| Tabs      | `tabs.tsx`          | Radix Tabs                                                                                  |
| Carousel  | `carousel.tsx`      | Embla-based                                                                                 |
| Slider    | `slider.tsx`        | Radix Slider                                                                                |
| Toggle    | `toggle.tsx`        | Radix Toggle                                                                                |
| Dropdown  | `dropdown-menu.tsx` | Radix DropdownMenu                                                                          |
| Tooltip   | `tooltip.tsx`       | Radix Tooltip                                                                               |

---

# Dependencies

| Package                    | Purpose                      |
| -------------------------- | ---------------------------- |
| `@radix-ui/*`              | Accessible UI primitives     |
| `class-variance-authority` | Type-safe variant management |
| `tailwind-merge`           | Smart class merging          |
| `clsx`                     | Conditional classes          |
| `framer-motion`            | Animations                   |
| `embla-carousel-react`     | Carousel                     |
| `lucide-react`             | Fallback icons               |
| `sonner`                   | Toast notifications          |
| `recharts`                 | Charts                       |
| `@storybook/react-vite`    | Component documentation      |

Related Skills

ui-architecture-icon

9
from cartridge-gg/nums

Add SVG icons to the Nums game client — convert SVG, create component, export, update storybook. Use when adding, modifying, or removing icon components.

dojo-architecture

9
from cartridge-gg/nums

Shinigami architecture for fully onchain Dojo games — Elements, Types, Models, Components, Systems, Helpers, Store, Events, Interfaces. Use when structuring a new game, adding modules, understanding the codebase hierarchy, or implementing new game mechanics.

ui-ux-pro-max

9
from cartridge-gg/nums

UI/UX design intelligence for web and mobile. Includes 50+ styles, 161 color palettes, 57 font pairings, 161 product types, 99 UX guidelines, and 25 chart types across 10 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui, and HTML/CSS). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, and check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, and mobile app. Elements: button, modal, navbar, sidebar, card, table, form, and chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, and flat design. Topics: color systems, accessibility, animation, layout, typography, font pairing, spacing, interaction states, shadow, and gradient. Integrations: shadcn/ui MCP for component search and examples.

shadcn

9
from cartridge-gg/nums

Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".

render-daily-replay

9
from cartridge-gg/nums

Auto-render a Remotion video replay of a Nums game on Mainnet. Resolves the target `gameId` from Torii (best reward of the day OR best score of the day, OR a specific gameId the user provides), silently auto-fetches the current NUMS price from Ekubo, then runs `pnpm remotion:render:game` with the right props. Use when the user asks to render today's top game, the daily winner, the biggest reward, the highest score, or any specific gameId, without having to assemble the render command by hand.

remotion-best-practices

9
from cartridge-gg/nums

Best practices for Remotion - Video creation in React

nums-remotion-replay

9
from cartridge-gg/nums

Project-specific skill for the Nums Remotion package that generates game replay videos by reusing the existing client React components. Covers the cross-package webpack setup, client-component overrides, Torii data fetching, font loading, render flow quirks, and hosting. Use when working on `remotion/` or the `SlidingNumber` component, adding new compositions, debugging font/animation issues, or setting up hosting for the Remotion Studio. Pairs with the generic `remotion-best-practices` skill.

dojo

9
from cartridge-gg/nums

Dojo Engine framework patterns — World, Systems, Models, Events, Components, Store, permissions, testing with spawn_test_world, and deployment with sozo.

dojo-world

9
from cartridge-gg/nums

Manage world permissions, namespaces, resource registration, and access control. Use when configuring world ownership, setting up authorization policies, or managing resource permissions.

Cartridge VRF Integration

9
from cartridge-gg/nums

Integrate Cartridge's Verifiable Random Function (VRF) for provably fair, atomic randomness in Dojo games.

dojo-token

9
from cartridge-gg/nums

Implement, deploy, and index ERC20 and ERC721 tokens in Dojo. Use when adding token contracts, deploying them, or configuring Torii to index balances and transfers.

dojo-test

9
from cartridge-gg/nums

Write tests for Dojo models and systems using spawn_test_world, cheat codes, and assertions. Use when testing game logic, verifying state changes, or ensuring system correctness.