# Ballmac UI documentation

> The full documentation of Ballmac UI (https://ui.ballmac.com/docs) as Markdown. The index of everything else, including every component, block and template, is at https://ui.ballmac.com/llms.txt.

<!-- Source: https://ui.ballmac.com/docs -->

# Introduction

Ballmac UI is a shadcn registry of accessible React and Tailwind components, blocks and templates that you and your AI agent install as code.

Ballmac UI is a collection of React and Tailwind CSS components, blocks and templates: desktop-style app surfaces, living backgrounds, text effects, device frames and AI interfaces.

You don't install it as a package. You add the items you need with the shadcn CLI, and the source lands in your project, ready to read and change.

```bash
npx shadcn@latest add @ballmac/button
```

Your AI coding agent can do the same through the [Ballmac MCP server](https://ui.ballmac.com/docs/get-started/connect-your-agent) (`@ballmac/mcp`) or the shadcn MCP server.

## Find what you need

- [Get started](https://ui.ballmac.com/docs/get-started/install): Install your first component, connect your agent, make it look like your product.
- [Guides](https://ui.ballmac.com/docs/guides/update-components): Steps for one task: update components, handle states, turn on right-to-left, set up Pro.
- [Reference](https://ui.ballmac.com/docs/reference/cli): Commands, MCP tools, settings, rules and messages, generated from the code.
- [Concepts](https://ui.ballmac.com/docs/concepts/how-ballmac-works): Why Ballmac UI works the way it does: ownership, motion, feels, states and quality.

## What makes it different

- **Updates that keep your edits.** [The ballmac CLI](https://ui.ballmac.com/docs/guides/update-components) merges new versions into your edited copies and installs the tests that ship with components.
- **Quality you can check.** Every item is loaded in a real browser and the [results are public](https://ui.ballmac.com/quality), including failures. [ballmac check](https://ui.ballmac.com/docs/guides/check-your-app) holds your own app to the same bar.
- **Native-app polish.** Springs, depth and hairlines from native apps, as app windows, docks, a live notch, menus and notifications that work in any product.
- **One design language.** Primitives, effects, AI interfaces and developer components share tokens, spacing, focus states and motion curves, and follow your theme in light and dark.
- **Effects with manners.** Globes, beams and particles pause off-screen, stop for reduced motion and keep your page accessible.
- **No collisions.** Everything installs into `components/ballmac`, so it never overwrites your shadcn/ui files. Dependencies are declared and checked on every build.
- **Made for AI agents.** Every item carries a description, when to use it, when not to, what it composes with and keyboard notes, through MCP and [llms.txt](https://ui.ballmac.com/llms.txt).
- **Verified installs.** Each release installs every item into a fresh Next.js app, then type-checks and builds it.

## Free and Pro

Every component, block, template and theme is free and MIT licensed. [Ballmac UI Pro](https://ui.ballmac.com/pricing) adds premium blocks, two complete starter apps and the Figma tokens, delivered through a private registry with a licence key. [Set up Pro](https://ui.ballmac.com/docs/guides/pro).

## Requirements

- React 19 and Tailwind CSS v4
- A project set up with `shadcn init`, which creates `components.json` and `lib/utils`

Stuck, or found a bug? [Contact support](https://ui.ballmac.com/support), or open an issue on [GitHub](https://github.com/vamsiy78/ballmac-ui/issues).

---

<!-- Source: https://ui.ballmac.com/docs/get-started/install -->

# Install your first component

Add Ballmac UI to a React and Tailwind v4 project and import your first component. It takes about a minute.

By the end of this page you have a Ballmac component running in your project.

### Set up shadcn

Skip this step if your project already has a `components.json`. Ballmac UI needs React 19 and Tailwind CSS v4.

```bash
npx shadcn@latest init
```

### Add components

`@ballmac` is listed in the official shadcn registry directory, so the CLI already knows it. There is nothing to configure.

```bash
npx shadcn@latest add @ballmac/button @ballmac/number-ticker
```

Files go to `components/ballmac/`, and the CLI installs the npm packages they need.

### Import and use it

```tsx title="app/page.tsx"
import { Button } from "@/components/ballmac/button"

export default function Page() {
  return <Button>Get started</Button>
}
```

Is your project on a Base UI style? Components that use Radix UI also come in a Base UI version, with the same props. [Switch to it](https://ui.ballmac.com/docs/guides/base-ui) when you install.

## Add the Ballmac theme

Every component uses the standard shadcn CSS variables, so it follows your existing theme. To match the Ballmac look (neutral light mode, ink-navy dark mode, blue focus ring), add the theme:

```bash
npx shadcn@latest add @ballmac/theme
```

## If the CLI doesn't know @ballmac

Recent versions of the shadcn CLI resolve `@ballmac` on their own. If yours doesn't, or you want to pin the address for a mirror or a preview deployment, add the registry once per project:

```bash
npx shadcn@latest registry add @ballmac=https://ui.ballmac.com/r/{name}.json
```

It adds this to your `components.json`:

```json title="components.json"
{
  "registries": {
    "@ballmac": "https://ui.ballmac.com/r/{name}.json"
  }
}
```

> **Note:** The shadcn MCP server only reads `components.json`, so it needs this entry too.

You can also install straight from a URL:

```bash
npx shadcn@latest add https://ui.ballmac.com/r/button.json
```

## Next steps

- [Connect your AI agent](https://ui.ballmac.com/docs/get-started/connect-your-agent): Let Claude Code, Cursor or VS Code search and install for you.
- [Make it yours](https://ui.ballmac.com/docs/get-started/make-it-yours): Apply a theme and set your brand color.
- [Update components](https://ui.ballmac.com/docs/guides/update-components): Merge new versions without losing your edits.
- [Registry reference](https://ui.ballmac.com/docs/reference/registry): Addresses, file locations and the JSON format.

---

<!-- Source: https://ui.ballmac.com/docs/get-started/build-a-dashboard -->

# Build a sales dashboard

Add the dashboard block, feed it your data, handle loading and errors, and match your brand. It takes about ten minutes.

By the end of this page you have a dashboard that loads your own numbers, shows a skeleton while it waits, and offers a retry when the request fails.

You need a project that already has Ballmac UI set up. If you don't, start with [Install your first component](https://ui.ballmac.com/docs/get-started/install).

### Add the block

```bash
npx shadcn@latest add @ballmac/dashboard-1
```

The block goes to `components/ballmac/blocks/dashboard-1/`. The CLI also adds what it builds on, such as the chart, the state view and `recharts`.

### Render it with demo data

With no props the block draws a built-in sales curve, so you can see the page before you connect anything.

```tsx title="app/dashboard/page.tsx"
import { Dashboard1 } from "@/components/ballmac/blocks/dashboard-1/dashboard-1"

export default function Page() {
  return <Dashboard1 />
}
```

You get a date-range switch, four KPI cards with change and trend, a revenue chart against the previous period, revenue by channel and a recent-orders table. Every number is worked out from the series you pass.

### Pass your own numbers

`getSeries` returns one row per day for the chosen range. `channels` and `orders` are plain lists.

```tsx title="app/dashboard/page.tsx"
import { Dashboard1 } from "@/components/ballmac/blocks/dashboard-1/dashboard-1"

const week = [
  { label: "Mon", revenue: 8200, previous: 7600, orders: 61 },
  { label: "Tue", revenue: 9400, previous: 8100, orders: 70 },
  { label: "Wed", revenue: 8800, previous: 8700, orders: 66 },
]

export default function Page() {
  return (
    <Dashboard1
      title="This week"
      defaultRange="7d"
      getSeries={() => week}
      currency="EUR"
      channels={[{ name: "Web", share: 64 }, { name: "Marketplace", share: 36 }]}
      orders={[{ id: "ORD-2201", customer: "Fjord Labs", email: "pay@fjord.no", status: "paid", total: 1840, date: "2026-09-30" }]}
    />
  )
}
```

### Load the data from your API

Fetch with `useResource`. It turns a request into the `state` the block takes, aborts a request that is replaced, and retries on demand.

```tsx title="components/sales-overview.tsx"
"use client"

import * as React from "react"

import { Dashboard1, type Dashboard1Range } from "@/components/ballmac/blocks/dashboard-1/dashboard-1"
import { useResource } from "@/lib/ballmac/use-resource"

export function SalesOverview() {
  const [range, setRange] = React.useState<Dashboard1Range>("7d")
  const overview = useResource(
    (signal) => fetch(`/api/overview?range=${range}`, { signal }).then((r) => r.json()),
    { deps: [range], isEmpty: (o) => o.series.length === 0 }
  )
  return (
    <Dashboard1
      state={overview.state} // "loading" | "ready" | "empty" | "error"
      onRetry={overview.retry}
      defaultRange={range}
      onRangeChange={setRange}
      getSeries={() => overview.data?.series ?? []}
      channels={overview.data?.channels}
      orders={overview.data?.orders}
    />
  )
}
```

Changing the range fetches again, and the block shows its skeleton while it waits. `defaultRange={range}` keeps the switch on the range you chose when the block comes back.

### Add a test endpoint

If you don't have an API yet, this route returns the same shape the component expects.

```ts title="app/api/overview/route.ts"
export function GET(request: Request) {
  const range = new URL(request.url).searchParams.get("range") ?? "7d"
  const days = range === "90d" ? 90 : range === "30d" ? 30 : 7
  const series = Array.from({ length: days }, (_, i) => ({ label: `Day ${i + 1}`, revenue: 8000 + (i % 7) * 900, previous: 7500 + (i % 5) * 700, orders: 60 + (i % 9) * 4 }))
  return Response.json({
    series,
    channels: [{ name: "Web", share: 64 }, { name: "Marketplace", share: 36 }],
    orders: [{ id: "ORD-1", customer: "Fjord Labs", email: "pay@fjord.no", status: "paid", total: 1840, date: "2026-09-30" }],
  })
}
```

### See every state

Open the block's page and use the **State** switch above the preview to see loading, empty and error, or link straight to one with `/blocks/dashboard-1?state=error`. In your own page, stop the endpoint (or make it return a 500) and the block shows an error with a retry button. Focus is never moved for you, and screen readers hear the loading and error messages.

### Match your brand

The chart and the trend lines use your theme's `--chart-1` color, so changing it changes the dashboard.

```css title="app/globals.css"
:root {
  --chart-1: oklch(0.62 0.19 262);
}
```

For a whole theme, see [Make it yours](https://ui.ballmac.com/docs/get-started/make-it-yours).

### Check your work

Run the quality check on your own page. It reports missing states, unnamed controls, hard-coded colors and layouts that break on a phone.

```bash
npx @ballmac/cli check
```

## Next steps

- [Handle loading, empty and error states](https://ui.ballmac.com/docs/guides/states): The `state` prop on every data block and form block.
- [Dashboard 1](https://ui.ballmac.com/blocks/dashboard-1): All props, examples and the quality scorecard.
- [Update components without losing your edits](https://ui.ballmac.com/docs/guides/update-components): Merge new versions into the copy you changed.
- [Connect your AI agent](https://ui.ballmac.com/docs/get-started/connect-your-agent): Let your agent add the next block for you.

---

<!-- Source: https://ui.ballmac.com/docs/get-started/connect-your-agent -->

# Connect your AI agent

Connect Claude Code, Cursor, VS Code, Windsurf or Codex to Ballmac UI with the @ballmac/mcp server. No account or key needed.

By the end of this page your AI assistant can search Ballmac UI, read props and examples, and give you install commands.

`@ballmac/mcp` knows the whole catalog, including blocks and multi-page templates, and can plan a page from blocks. It is read-only and needs no account. It needs Node.js 20 or later.

### Add the server to your client

Pick your client.

```bash title="Claude Code"
claude mcp add ballmac -- npx -y @ballmac/mcp
```

```json title="Cursor (.cursor/mcp.json), Claude Desktop, Windsurf"
{
  "mcpServers": {
    "ballmac": {
      "command": "npx",
      "args": ["-y", "@ballmac/mcp"]
    }
  }
}
```

```json title="VS Code (.vscode/mcp.json)"
{
  "servers": {
    "ballmac": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@ballmac/mcp"]
    }
  }
}
```

```toml title="Codex (~/.codex/config.toml)"
[mcp_servers.ballmac]
command = "npx"
args = ["-y", "@ballmac/mcp"]
```

### Ask for something

Try one of these:

- "Show me the Ballmac UI components for AI chat interfaces"
- "Add @ballmac/prompt-input and @ballmac/ai-message and build a chat panel with them"
- "Which Ballmac UI component should I use for an animated stat, and how do I install it?"
- "Build a pricing section using Ballmac UI blocks"
- "Is there a Ballmac template for an online store? Install it"

### Ask it to check its work

Tell your agent: "before you finish, run `audit_project` and fix every error." The tool reads your source, read-only, and returns findings worst first, each with its fix. The rules are in the [check rules reference](https://ui.ballmac.com/docs/reference/check-rules).

## Next steps

- Add the [Ballmac UI skill](https://ui.ballmac.com/docs/guides/agent-skill) so your agent also knows the method: how to pick, install and check.
- See every tool, resource and prompt in the [MCP tools reference](https://ui.ballmac.com/docs/reference/mcp-tools).
- Give the server your licence key to read Pro items: [Set up Pro](https://ui.ballmac.com/docs/guides/pro#use-pro-with-the-mcp-server).
- Prefer the official server? [Use the shadcn MCP server](https://ui.ballmac.com/docs/guides/shadcn-mcp).

---

<!-- Source: https://ui.ballmac.com/docs/get-started/make-it-yours -->

# Make it yours

Apply a theme, set your brand color and check dark mode. Ballmac components read the standard shadcn CSS variables, so they take on your brand automatically.

By the end of this page your components use your brand color in light and dark.

### Install a theme

The [theme builder](https://ui.ballmac.com/themes) has twelve ready-made themes: Graphite, Ocean, Indigo, Violet, Rose, Ember, Amber, Forest, Teal, Sand, Mono and Midnight. Each is a full set of light and dark tokens, with contrast checked for text, focus rings and charts.

```bash
npx shadcn@latest add @ballmac/theme-ocean
```

Themes change the variables only. Installing a second theme replaces the first, and your components keep working because they read the same tokens.

### Set your brand color

Focus rings, selections and accents come from `--ring` and `--chart-1`. Change them in one place:

```css title="app/globals.css"
:root {
  --ring: oklch(0.62 0.19 262);
  --chart-1: oklch(0.62 0.19 262);
}
```

### Check dark mode

Components read tokens only, so dark mode is whatever your `.dark` class defines. With the Ballmac theme, dark mode is an ink navy rather than pure black.

> **Tip:** Want color, typeface, platform feel and motion in one command? Install a [preset](https://ui.ballmac.com/docs/guides/presets). Prefer to tune the theme on a live preview? The [theme builder](https://ui.ballmac.com/themes) changes hue, intensity, radius, density and font, checks contrast, and gives you the result to copy.

## Next steps

- See every token a component reads in the [theme tokens reference](https://ui.ballmac.com/docs/reference/theme-tokens).
- Get a whole design system, with typeface and platform feel, from a [preset](https://ui.ballmac.com/docs/guides/presets).
- Change how a product moves in [Use theme motion in your own code](https://ui.ballmac.com/docs/guides/motion).
- Make every component handle like macOS, iOS or Windows with [a platform feel](https://ui.ballmac.com/docs/guides/platform-feels).

---

<!-- Source: https://ui.ballmac.com/docs/guides/update-components -->

# Update components without losing your edits

Install with the ballmac CLI, see what is behind, and merge new versions into your edited copies.

`ballmac` sits next to the shadcn CLI. shadcn still writes the files and installs packages. `ballmac` runs it for you and records what landed. Run it in a project that already has a `components.json`.

## Install items with ballmac

```bash
npx @ballmac/cli add dialog --with-tests
```

That installs `dialog` and the items it needs, then writes `ballmac.lock.json` and a `.ballmac` folder that holds an untouched copy of each file as it was installed.

> **Warning:** Commit both. The untouched copy is what makes merging possible, and your teammates and CI need it too.

## See what is behind

```bash
npx @ballmac/cli outdated
```

```text title="Output"
ITEM    INSTALLED  LATEST  YOUR CHANGES          WHAT CHANGED
badge   1.0.0      1.1.0   none                  Slightly wider horizontal padding so short labels do not look cramped.
button  1.0.0      1.1.0   yes (will be merged)  The lg size is taller and larger so it matches the lg input and select.
dialog  1.0.0      1.1.0   yes (will be merged)  The overlay is a little lighter and uses a standard blur.

3 of 4 behind. Update with: ballmac update  (add --dry-run to preview)
```

Every item has a version and a changelog written for someone who customised the file: what behaviour changed, and whether props were added, renamed or removed. Breaking changes are marked. The same notes are on each component page under **What changed**.

## Update, keeping your edits

```bash
npx @ballmac/cli update --dry-run
npx @ballmac/cli update
```

The first command shows what would happen and changes nothing. For each file, `ballmac` compares three versions: the one you installed, yours now, and the new one.

- **You never touched it:** replaced with the new version.
- **You edited it, and Ballmac changed other lines:** merged. Both sets of changes are kept.
- **You and Ballmac changed the same lines:** the file gets git-style conflict markers, because only you know which one you want. Search for `<<<<<<<`, choose, and run your tests.
- **A file you deleted:** stays deleted. New files and new dependencies a version adds are installed and tracked.

```text title="Output"
button  1.0.0 → 1.1.0
  1.1.0: The lg size is taller and larger so it matches the lg input and select.
  merged   src/components/ballmac/button.tsx  your changes kept
  updated  src/components/ballmac/__tests__/button.test.tsx

dialog  1.0.0 → 1.1.0
  CONFLICT src/components/ballmac/dialog.tsx  1 conflict(s): search for <<<<<<< and choose

2 item(s) updated. 1 file(s) have conflicts to resolve.
Originals are in .ballmac/backup/2026-10-11T10-54-02-743Z/
```

Before anything is written, every file it might change is copied to `.ballmac/backup/` (ignored by git), and if shadcn fails halfway your files are put back. Files that shadcn shares between items, such as your own edits to the i18n helper, are never overwritten.

## See what you changed

```bash
npx @ballmac/cli diff button
```

This shows your changes against the version you installed, nothing else. Use it before an update, and to remember why a file differs.

## Fail CI when something is behind

`ballmac outdated --exit-code` fails when something is behind, and `--json` prints the same data for scripts. Pro items work the same way: set `BALLMAC_LICENSE_KEY` in your environment or `.env.local` (see [Set up Pro](https://ui.ballmac.com/docs/guides/pro)). Without it only free items are listed.

## Know the limits

- Merging is line based and needs `git` on your PATH (it uses `git merge-file`; no repository is needed). Without git, edited files get the `.ballmac-new` treatment described in [Track items you already installed](https://ui.ballmac.com/docs/guides/track-installed-items).
- A merge that applies cleanly can still be wrong in meaning. Run the tests, and read the changelog note.
- Themes are CSS variables, not files, so they are not merged. Re-apply a theme with the shadcn CLI.
- Examples and demos are not tracked, only the items you install. It needs Node.js 20 or later and a project with a `components.json`.

The agent side of this is the MCP tools `get_changes` and `get_tests` in the [MCP tools reference](https://ui.ballmac.com/docs/reference/mcp-tools). All commands are in the [CLI reference](https://ui.ballmac.com/docs/reference/cli). Something wrong? [Tell us](https://ui.ballmac.com/support).

---

<!-- Source: https://ui.ballmac.com/docs/guides/track-installed-items -->

# Track items you already installed

Adopt Ballmac items you installed with plain shadcn, so ballmac update can merge new versions into them.

Installed with plain `shadcn add`? Adopt the items:

```bash
npx @ballmac/cli track
npx @ballmac/cli track dialog button
```

The first command tracks every Ballmac item found in the project. The second tracks only the items you name.

## How it records your files

A file identical to what shadcn writes today is recorded as untouched. One that differs is recorded **without a base**, because it may be edited or from an older version and there is no way to tell.

For those files, an update puts the new version beside your file as `<file>.ballmac-new` instead of guessing.

## Declare a file current

Once you have compared the two files, tell `ballmac` your file is current:

```bash
npx @ballmac/cli track --rebase button
```

From then on, `ballmac update` merges new versions into it. See [Update components without losing your edits](https://ui.ballmac.com/docs/guides/update-components).

---

<!-- Source: https://ui.ballmac.com/docs/guides/tests -->

# Install the tests that ship with components

Add the vitest tests Ballmac runs on a component, keep them updated with it, and run them in your project.

Most free components come with the test we run on them: keyboard use, states and ARIA, written with vitest and Testing Library.

## Add tests when you install

```bash
npx @ballmac/cli add dialog --with-tests
```

## Add tests for items you already have

```bash
npx @ballmac/cli test add button
```

## Run every Ballmac test

```bash
npx @ballmac/cli test
```

## What gets installed

Tests go in `__tests__` next to the components, with one shared `setup.ts`. The tool installs the packages they need at versions known to work together (vitest, jsdom, Testing Library, the React plugin) and, if you have no vitest config yet, creates one with your `@` alias.

Pages for components that have a test say so. A test is yours once installed, and it is updated with the component: your additions are merged like any other file.

## Close the loop with an agent

After an AI agent edits a component, it can run the test and fix what it broke. The MCP tool `get_tests` returns the test for an item.

> **Note:** Pro blocks are tested in batches rather than one by one, so they don't ship per-item tests yet.

---

<!-- Source: https://ui.ballmac.com/docs/guides/check-your-app -->

# Check your own app

Run ballmac check on your source or your running app, tune it for your project, and gate CI and AI agents on it.

`ballmac check` reads your source and reports missing loading and error states, images and buttons without names, hard-coded colors, endless decorative motion and layouts that break on a phone, each with the fix.

## Run it

```bash
npx @ballmac/cli check
```

Check one folder, or list every rule:

```bash
npx @ballmac/cli check src/app
npx @ballmac/cli check --rules
```

It is read-only, needs no account and no network (the rule list is bundled; it only looks up which of your components have states if you are online), and uses the TypeScript version your project already has. Your own `components/ui` and Ballmac files are skipped unless you pass `--include-library`.

```text title="Output"
ballmac check  ·  41 files

error a11y/control-name ×1  A button or link has an icon but no accessible name
  src/app/toolbar.tsx:18:7
    <button> contains only an icon and has no accessible name.
      18 │ <button onClick={() => setOpen(!open)} className="p-2">
  fix: Add aria-label="What it does" to the control, or include visually hidden text such as <span className="sr-only">Close</span>.
  https://ui.ballmac.com/docs/check#a11y-control-name
```

## Check the running app

Reading source can't see what the browser renders. Add `--url` and the tool opens each route in a real browser:

```bash
npx @ballmac/cli check --url http://localhost:3000 --routes /,/pricing
```

It uses the Playwright and axe-core that are already in your project (it never installs anything), in light and dark at 390 and 1280 pixels wide. It reports serious or critical axe violations, sideways scrolling on a phone, and console errors. If Playwright isn't installed it says so and still runs the source checks.

## Tune it for your project

Put settings in `.ballmacrc.json`, or a `ballmac` key in `package.json`:

```json title=".ballmacrc.json"
{
  "rules": { "layout/fixed-wide": "warn", "tokens/hardcoded-color": "off", "rtl/physical-direction": "warn" },
  "ignore": ["src/legacy/**", "**/*.stories.tsx"],
  "rtl": true
}
```

Silence one finding where it is, with a reason a teammate can read:

```tsx
{/* ballmac-ignore-next-line a11y/control-name: the label is added by the wrapper */}
<IconButton onClick={close} />

// ballmac-ignore-line
// ballmac-ignore-file
```

## Fail the build in CI

```yaml title=".github/workflows/quality.yml"
- run: npx @ballmac/cli check --format github --fail-on error
```

`--format github` prints GitHub Actions annotations, and `--fail-on` sets when the command exits with 1. All flags are in the [CLI reference](https://ui.ballmac.com/docs/reference/cli#check).

## Gate an AI agent on it

The [Ballmac MCP server](https://ui.ballmac.com/docs/get-started/connect-your-agent) has `get_quality_rules`, so an agent reads the rules before it writes a component, and `audit_project`, which runs this check and returns the findings worst first, each with its fix. Tell your agent: "before you finish, run `audit_project` and fix every error."

## Related

- [What the check is, and is not](https://ui.ballmac.com/docs/concepts/quality)
- [Every rule](https://ui.ballmac.com/docs/reference/check-rules)

---

<!-- Source: https://ui.ballmac.com/docs/guides/verify-your-registry -->

# Verify your registry

Score any shadcn registry with ballmac verify, add a badge, gate CI with the GitHub Action, and get listed on the leaderboard.

`ballmac verify` reads a registry's source and scores it on accessibility, theme tokens, declared dependencies, states, motion and right-to-left support. It works on any registry that follows the shadcn schema, not only ours, and our own registry is scored with the same command on the [Ballmac Verified](https://ui.ballmac.com/verified) page.

## Run it

Point it at a URL, a `registry.json`, or the folder that holds one:

```bash
npx @ballmac/cli verify https://acme.com
npx @ballmac/cli verify ./public/r/registry.json
```

It needs TypeScript 5 or 6 in the folder you run it from (`npm i -D typescript@5`; version 7 has no JavaScript API), because it reads your components the way a compiler does. It writes nothing to your registry and sends nothing anywhere: items are read, then audited in a temporary folder that is deleted afterwards.

```text title="Output"
acme  84/100  grade B  62 of 71 items scored

  Accessibility            ██████████   100%  58/58
  Valid item               ██████████   100%  62/62
  Dependencies declared    █████████░  93.5%  58/62
  Theme tokens             ███████░░░  71.0%  44/62
  ...

Start here
  hero-video     65  imports motion but does not list it in dependencies
```

The list at the end is the items that lost the most points, each with the first thing to fix.

## Where it finds your items

A catalog entry may or may not carry its files. When it does not, `verify` fetches each item's JSON from the same folder as `registry.json` (`https://acme.com/r/button.json`). If yours live somewhere else, say where:

```bash
npx @ballmac/cli verify https://acme.com/r/registry.json --item-url "https://acme.com/r/{name}.json"
```

An item that cannot be read counts as invalid, and the report says how many there were.

## What is scored

Ten checks, each across the items it applies to. A check that does not apply (a hook has no colors) is left out for that item rather than counted as a pass. The score is the average of the checks weighted by how much they matter to people using your components, and accessibility counts most.

| Check | Weight | Passes when |
| --- | --- | --- |
| Accessibility | 25 | No unlabelled controls, missing alt text, clickable non-interactive elements, removed focus outlines or hand-made dialogs |
| Valid item | 15 | A kebab-case name, a known type, and files with a path and content |
| Dependencies declared | 15 | Every imported package is in `dependencies`, and every shared registry component is in `registryDependencies` |
| Theme tokens | 10 | Colors come from theme tokens, not hex or palette values |
| States | 10 | Data views handle loading, empty and error |
| Motion | 5 | No endless decorative animation without a reduced-motion switch |
| Right to left | 5 | Logical classes (`ms-`, `pe-`, `text-start`) instead of physical ones |
| Described | 5 | A title and a description of 20 characters or more |
| Documented | 5 | A `docs` note, or at least one example item |
| Attributed | 5 | An `author` or `license` on the item |

These are static checks on the source. They cannot see contrast, focus order or a broken layout on a phone. For those, run [`ballmac check --url`](https://ui.ballmac.com/docs/guides/check-your-app) on an app that uses your components. A flagged item is a place to look, not proof of a defect: the rules make judgment calls, and the report shows each one so you can decide.

## Add a badge

```bash
npx @ballmac/cli verify ./public/r/registry.json --badge public/verified.svg --out public/scorecard.json
```

Commit the SVG and link it from your README:

```md
[![Ballmac Verified](https://acme.com/verified.svg)](https://ui.ballmac.com/verified)
```

The badge says the score and grade in its text for screen readers, and its colors keep a contrast ratio of at least 4.5 to 1.

## Gate CI

Fail a pull request when the score drops:

```bash
npx @ballmac/cli verify ./public/r/registry.json --min-score 80
```

Or use the GitHub Action, which also writes a summary to the job:

```yaml title=".github/workflows/verified.yml"
- uses: vamsiy78/ballmac-ui/actions/verify@main
  with:
    registry: ./public/r/registry.json
    min-score: 80
    badge: public/verified.svg
```

The Action has outputs for `score`, `grade` and `scorecard`, and inputs for `item-url`, `cli-version` and `node-version`. Build your registry before the step so each item's JSON sits next to `registry.json`.

## Get listed

The [leaderboard](https://ui.ballmac.com/verified#board-h) lists registries that have published a scorecard. Run the command with `--out`, name the file after your registry, and open a pull request that adds it to `apps/www/content/verified/submissions`. The scorecard has to come from a public `https://` address so anyone can run the command and get the same score. We re-run it before merging, and an entry whose score does not follow from its checks is rejected.

## Related

- [Check your own app](https://ui.ballmac.com/docs/guides/check-your-app)
- [Every rule](https://ui.ballmac.com/docs/reference/check-rules)
- [CLI reference](https://ui.ballmac.com/docs/reference/cli)

---

<!-- Source: https://ui.ballmac.com/docs/guides/agent-skill -->

# Install the Ballmac UI skill

Teach Claude Code, Cursor, Codex and other coding agents how to pick, install and check Ballmac UI, with one command and no server to run.

The skill is a folder of instructions your coding agent loads when it builds UI. It tells the agent how to find the right item, how to install it, which rules to build to and how to check its work. It reads your project first, so it knows your shadcn style, whether you use Radix or Base UI, and which Ballmac items you already have.

It works next to the [MCP server](https://ui.ballmac.com/docs/get-started/connect-your-agent). The skill carries the method and the catalogue reference; the server answers live questions. Use either, or both.

## Install it

```bash
npx skills add vamsiy78/ballmac-ui
```

The command copies `skills/ballmac-ui` into the skills folder of each agent you pick. It works with Claude Code, Codex, Cursor, Windsurf and the other agents the skills command supports. Add `--skill ballmac-ui` to skip the picker, or `-g` to install it for every project.

You can also install it with the shadcn command you already use. It writes the same files to `.claude/skills/ballmac-ui` in your project:

```bash
npx shadcn@latest add @ballmac/skill
```

## What the agent does with it

1. **Reads the project.** It runs a small script that prints your shadcn style, base library, React, Next.js and Tailwind versions, whether Cache Components is on, and the Ballmac items you have installed.
2. **Finds the item.** It looks in the catalogue reference, or asks the MCP server if you connected it. It picks a block over assembling the same section from parts.
3. **Installs it** with `npx shadcn@latest add @ballmac/<name>` and tracks it so later versions merge into your edits.
4. **Builds to the rules.** Loading, empty and error states, theme tokens instead of colours, logical direction classes, names on every control, motion that respects reduced-motion settings, and the Next.js rules for prerendering.
5. **Checks its work** with `npx @ballmac/cli check` and fixes every error. See [Check your own app](https://ui.ballmac.com/docs/guides/check-your-app).

## What is in the folder

| File | What it holds |
| --- | --- |
| `SKILL.md` | The workflow and the rules, short enough to load in full |
| `scripts/context.mjs` | Prints the project facts as JSON. Read-only, no dependencies |
| `references/pages.md` | Which blocks make a landing page, dashboard, sign-in, settings, AI product or desktop app |
| `references/blocks.md`, `references/components/` | Every free block and component, one line each, with its exports and states |
| `references/templates.md`, `references/helpers.md`, `references/themes.md` | Templates, shared helpers, themes, motion and platform feels |
| `references/rules.md` | The quality rules with a bad and a good example for each |

Everything under `references/` except `pages.md` is generated from the catalogue and the check rules, and a check fails the build when it is out of date, so the skill cannot describe an item that does not exist.

## Update it

Run the install command again. The skill changes when the catalogue does.

## Related

- [Connect your AI agent](https://ui.ballmac.com/docs/get-started/connect-your-agent)
- [Check your own app](https://ui.ballmac.com/docs/guides/check-your-app)
- [MCP tools reference](https://ui.ballmac.com/docs/reference/mcp-tools)

---

<!-- Source: https://ui.ballmac.com/docs/guides/states -->

# Handle loading, empty and error states

Drive a data block or a form block from your own fetching with the state prop, and wrap your own views in StateView.

## Show a data block while it loads

Lists, boards, dashboards, calendars and chats take `state`, `onRetry`, `emptyState` and `errorState`. `useResource` turns a fetch into the state to pass:

```tsx title="app/inbox.tsx"
import { Mail1 } from "@/components/ballmac/blocks/mail-1/mail-1"
import { useResource } from "@/lib/ballmac/use-resource"

export function Inbox() {
  const mail = useResource((signal) => fetch("/api/mail", { signal }).then((r) => r.json()))
  return (
    <Mail1
      state={mail.state} // "loading" | "ready" | "empty" | "error"
      messages={mail.data}
      onRetry={mail.retry}
      emptyState={{ title: "Inbox zero", description: "Nothing new. Enjoy it." }}
      errorState={{ description: "Mail is taking a break. Your messages are safe." }}
    />
  )
}
```

`useResource` aborts a request that is replaced or unmounted, never lets a slow old answer overwrite a newer one, and goes round again on `retry()`.

## Use TanStack Query or SWR instead

If you already use TanStack Query or SWR, map their result:

```tsx
import { stateOf } from "@/lib/ballmac/use-resource"

const query = useQuery({ queryKey: ["mail"], queryFn: fetchMail })
<Mail1 state={stateOf(query)} messages={query.data} onRetry={query.refetch} />
```

## Drive a form block

Sign-in, sign-up, contact, newsletter, settings and the rest already run their own submit flow. `state` lets you drive it from your own async code, or show one stage on its own: `submitting` locks the fields, `success` shows the confirmation, `error` shows the failure. `errorState.description` sets the message.

```tsx
const [phase, setPhase] = useState<"ready" | "submitting" | "success" | "error">("ready")

<Signup1
  state={phase}
  errorState={{ description: "That email is already registered." }}
  onSubmit={async (values) => {
    setPhase("submitting")
    try { await createAccount(values); setPhase("success") } catch { setPhase("error") }
  }}
/>
```

Leave `state` out and the form decides by itself, as before. Several forms also take `defaultValues` or `defaultEmail` to prefill.

## Wrap your own view

`StateView` is the part the blocks are built on. Wrap anything that loads:

```tsx
<StateView
  state={invoices.state}
  loading={<InvoiceSkeleton />} // the same shape as the real list
  empty={{ title: "No invoices", action: <Button>Create one</Button> }}
  error={{ title: "Invoices are unavailable", details: String(invoices.error) }}
  onRetry={invoices.retry}
>
  <InvoiceList items={invoices.data} />
</StateView>
```

The props of `StateView` and `useResource` are in the [building blocks reference](https://ui.ballmac.com/docs/reference/building-blocks).

## Related

- [Why every block has states](https://ui.ballmac.com/docs/concepts/states)
- [Which blocks show states](https://ui.ballmac.com/docs/reference/block-states)

---

<!-- Source: https://ui.ballmac.com/docs/guides/charts -->

# Add accessible charts

Install a chart, give it your data, and get a table view, patterns for color blindness, a written summary and loading, empty and error states without extra code.

Ballmac UI has seven chart components built on [Recharts](https://recharts.org) and the shadcn chart: area, bar, line, pie, radar, radial and scatter. Each one draws inside a **chart frame** that adds what a chart needs to work for everyone. Browse them all, live, on the [charts page](https://ui.ballmac.com/charts).

## Install a chart

```bash
npx shadcn@latest add @ballmac/chart-area
```

The command also installs the frame and the base chart, and adds `recharts` to your project. To start from a finished example instead, install it by name, for example `@ballmac/chart-area-stacked`.

## Draw one

Pass rows of data, the key that holds each row's category, and the series to draw:

```tsx title="app/page.tsx"
import { ChartArea } from "@/components/ballmac/chart-area"

const data = [
  { month: "Jan", desktop: 4210, mobile: 2480 },
  { month: "Feb", desktop: 4860, mobile: 3120 },
  { month: "Mar", desktop: 4540, mobile: 3790 },
]

export default function Page() {
  return (
    <ChartArea
      title="Visitors"
      description="Desktop and mobile"
      data={data}
      xKey="month"
      series={[
        { key: "desktop", label: "Desktop" },
        { key: "mobile", label: "Mobile" },
      ]}
    />
  )
}
```

Colors come from your theme's chart colors in order. Give a series its own with `color: "var(--chart-3)"` or any CSS color.

## Which chart

| You want to show | Use |
| --- | --- |
| A trend, or parts of a whole over time | `chart-area` (stacked, expanded to 100%) |
| An exact trend, forecast against actual, lines that cross | `chart-line` (dashed series, labels on the lines) |
| A comparison, a ranking, groups | `chart-bar` (columns or flat bars, stacked, sorted) |
| A few parts of one whole | `chart-pie` (donut, half ring, total in the middle) |
| A goal, or progress rings | `chart-radial` |
| A profile across five to eight dimensions | `chart-radar` |
| A relationship between two numbers | `chart-scatter` (groups, bubbles) |

## What every chart does for you

- **Table view.** A switch above the chart turns it into a table of the same numbers, with a **Download CSV** button. Pass `tableView={false}` to hide the switch.
- **Legend.** Each legend item is a button that switches its series off and on. At least one always stays on. Use `legend="static"` for a plain legend or `legend="none"`.
- **Patterns.** Add `patterns="toggle"` for a switch that fills series with stripes, dots and hatching as well as color, or `patterns` to always do it. Line charts use dash styles.
- **A summary for screen readers.** The chart is a named figure, and its description is written from your data: first to last, highest and lowest, or a ranking. Pass `summary="Mobile caught up in March."` to say what it means instead.
- **States.** Pass `state="loading"`, `"empty"` or `"error"` and an `onRetry`. The loading skeleton has the shape of the chart. An empty `data` array shows the empty state by itself.
- **Reduced motion.** Chart animations stop for people who ask for less motion.
- **Right to left.** Axes reverse and move to the other side when the page reads right to left.

```tsx
import { useResource } from "@/lib/ballmac/use-resource"

const visits = useResource((signal) => fetch("/api/visits", { signal }).then((r) => r.json()))

<ChartArea title="Visits" data={visits.data ?? []} xKey="day" series={series} state={visits.state} onRetry={visits.retry} />
```

## Write numbers your way

`valueFormatter` writes a number in the tooltip, the table, the labels and the summary. `categoryFormatter` writes a category, for example a date. The axis has its own formatter, because ticks are usually shorter:

```tsx
<ChartBar
  valueFormatter={(v) => `$${v.toLocaleString("en-US")}`}
  axisFormatter={(v) => `$${v / 1000}k`}
  categoryFormatter={(v) => dateFormat.format(new Date(`${v}T00:00:00Z`))}
  …
/>
```

Axes get round ticks (0, 500, 1K) instead of whatever the data ends on. Fix a scale with `yDomain` (area, line, scatter) or `domain` (bar).

## Use the frame with any Recharts chart

For a chart these components do not cover, wrap your own Recharts chart in `ChartFrame` and use what it hands you:

```tsx
import { ChartFrame } from "@/components/ballmac/chart-frame"

<ChartFrame title="Response time" data={data} xKey="week" series={series}>
  {(chart) => (
    <LineChart data={data} accessibilityLayer>
      <XAxis dataKey="week" reversed={chart.rtl} />
      {series.map((s) => (
        <Line key={s.key} dataKey={s.key} stroke={chart.color(s.key)} hide={chart.isHidden(s.key)} isAnimationActive={chart.animate} />
      ))}
    </LineChart>
  )}
</ChartFrame>
```

`chart.fill(key)` gives a series' fill, which is its pattern when patterns are on. Put `<defs>{chart.defs}</defs>` inside the chart so the patterns exist. For a chart whose data is not one column per series, pass your own `table={{ columns, rows }}`.

## Dashboards

Four blocks put the charts to work: [Charts 1](https://ui.ballmac.com/blocks/charts-1) is an analytics overview, [Charts 2](https://ui.ballmac.com/blocks/charts-2) a revenue report, [Charts 3](https://ui.ballmac.com/blocks/charts-3) small multiples and [Charts 4](https://ui.ballmac.com/blocks/charts-4) a scorecard. Each works out its figures from your data.

## Related

- [Handle loading, empty and error states](https://ui.ballmac.com/docs/guides/states)
- [Right-to-left layouts](https://ui.ballmac.com/docs/guides/right-to-left)
- [Quality, measured](https://ui.ballmac.com/quality)

---

<!-- Source: https://ui.ballmac.com/docs/guides/presets -->

# Install a preset

Give your app a whole design system with one command: color, shape, typeface, platform feel and motion. Pick one of fifteen, or make your own and share it as a 16-character code.

A preset is a design system in one install. It sets your colors (light and dark, contrast checked), corner radius and density, typeface, platform feel and motion together, so they suit each other.

A [theme](https://ui.ballmac.com/docs/get-started/make-it-yours) only sets color, corners and motion. A preset adds the typeface and the platform feel, and it has a code.

## Install one

Browse the [presets](https://ui.ballmac.com/presets), pick one, and run its command:

```bash
npx shadcn@latest add @ballmac/preset-ink
```

The command writes the tokens into your global CSS and, when the preset has an installable typeface, adds the font. Your components keep working because they read the same tokens.

Run it again with another name to switch. Presets replace each other.

## Make your own and share the code

The [builder](https://ui.ballmac.com/themes) changes every setting on a live preview of real components. The panel shows a **preset code**:

```text
2gf1-6sax-5gj7-5xdq
```

The code is the design: color, shape, typeface, feel and motion packed into 16 characters. Nothing is stored on a server. Anyone can install it:

```bash
npx shadcn@latest add @ballmac/preset-2gf1-6sax-5gj7-5xdq
```

Paste a code into **Load a code** in the builder, or open `/themes?code=2gf1-6sax-5gj7-5xdq`, and the design loads so you can keep tuning it. A code that has a typo is rejected, not guessed at.

## Install only some parts [#only]

Add `only` to take parts of a preset and keep the rest of your design. The parts are `colors`, `shape`, `font`, `motion` and `feel`. Use the URL form, and quote it:

```bash
npx shadcn@latest add "https://ui.ballmac.com/r/preset-ink.json?only=colors,font"
```

The builder's **What to install** boxes write this command for you. The URL form also works with a code.

| Part | What it sets |
| --- | --- |
| `colors` | Light and dark colors, including the five chart colors |
| `shape` | Corner radius and density |
| `font` | The typeface |
| `motion` | Speed, bounce, travel, hover, stagger and the reduced-motion fallback |
| `feel` | Control sizes, focus ring, shadows and materials for the platform feel |

## Typefaces [#typefaces]

A preset can name one of these open-source variable fonts, or a system font stack that costs nothing to load. The shadcn CLI installs a named font from [Fontsource](https://fontsource.org), so the files are served from your own app and not from a third-party host. On Next.js it uses `next/font`.

| Font | Kind | Good for | Install on its own |
| --- | --- | --- | --- |
| Inter | sans | Neutral, built for screens. The safe default. | `@ballmac/font-inter` |
| Geist | sans | Crisp and technical. Developer tools, dashboards. | `@ballmac/font-geist` |
| DM Sans | sans | Low contrast, geometric, friendly. | `@ballmac/font-dm-sans` |
| Manrope | sans | Modern and a little wide. Fintech, SaaS. | `@ballmac/font-manrope` |
| Plus Jakarta Sans | sans | Warm geometric forms. Consumer apps. | `@ballmac/font-jakarta` |
| Figtree | sans | Friendly and open. Health, education, community. | `@ballmac/font-figtree` |
| Instrument Sans | sans | Slightly condensed, editorial. Studios and brands. | `@ballmac/font-instrument-sans` |
| Space Grotesk | sans | Quirky, technical. Crypto, hardware, creative tools. | `@ballmac/font-space-grotesk` |
| Bricolage Grotesque | sans | Characterful and bold. Marketing, portfolios. | `@ballmac/font-bricolage` |
| Fraunces | serif | A soft, expressive serif. Editorial, food, lifestyle. | `@ballmac/font-fraunces` |
| Newsreader | serif | A reading serif. Publications, docs, long text. | `@ballmac/font-newsreader` |
| Source Serif 4 | serif | Sturdy and neutral. Finance, legal, research. | `@ballmac/font-source-serif` |
| JetBrains Mono | mono | Made for code. Terminals, developer products. | `@ballmac/font-jetbrains-mono` |
| Geist Mono | mono | Clean monospace to pair with Geist. | `@ballmac/font-geist-mono` |

Install a font without a preset, for example `npx shadcn@latest add @ballmac/font-fraunces`. It sets `--font-sans`.

## How the code works

- Each setting is rounded to the step of its slider, then all of them are packed into one number and written in 32 letters and digits that cannot be confused with each other. The last character is a check.
- You can type it in any case, with or without dashes. `o` reads as `0`, and `i` and `l` read as `1`.
- The format has a version. A code made today keeps meaning the same design, because new fonts are only ever added at the end of the list.
- Every value is clamped by the theme engine, so a made-up code can only produce a safe theme.

## Use it from an agent or a script

The preset address is plain JSON, so a script can read it: `https://ui.ballmac.com/r/preset-<code or name>.json`. The [Ballmac skill](https://ui.ballmac.com/docs/guides/agent-skill) tells a coding agent when to use a preset, and the [MCP server](https://ui.ballmac.com/docs/get-started/connect-your-agent) can make one from a description.

## Related

- [Make it yours](https://ui.ballmac.com/docs/get-started/make-it-yours)
- [Pick a platform feel](https://ui.ballmac.com/docs/guides/platform-feels)
- [Use theme motion in your own code](https://ui.ballmac.com/docs/guides/motion)

---

<!-- Source: https://ui.ballmac.com/docs/guides/base-ui -->

# Use Base UI instead of Radix UI

Every Ballmac UI component built on Radix UI also comes in a Base UI version with the same props, the same keyboard behavior and the same tests. Install it by URL, or let a Base UI style pick it.

Most interactive Ballmac UI components (dialogs, menus, tabs, sliders, tooltips and more) are built on [Radix UI](https://www.radix-ui.com). Each of them also has a version built on [Base UI](https://base-ui.com), the headless library behind shadcn's `base-*` styles. You choose one per project, and you can switch later.

Both versions have the same props, the same exports and the same `data-slot` names. Pages and code that use a component keep working when you change which one is installed.

## Which one do you need

Check your `components.json`. The `style` says which library your project uses:

- A `base-*` style, such as `base-nova`, uses Base UI.
- Any other style, such as `radix-nova` or `new-york-v4`, uses Radix UI.

If you start a new project with `shadcn init`, you get Base UI unless you ask for Radix. Match the Ballmac version to it, so you ship one headless library, not two.

## Install a Base UI version

Every component page with a Base UI version has a **Radix UI | Base UI** switch above the install command. Pick Base UI, and the command becomes:

```bash
npx shadcn@latest add https://ui.ballmac.com/r/base/popover.json
```

The command installs the component, the Ballmac items it builds on (their Base UI versions where they have one), and the `@base-ui/react` package. It leaves Radix UI out.

`@ballmac/base-popover` is the same file under a name that needs no URL, so `npx shadcn@latest add @ballmac/base-popover` works too.

A component with nothing to swap, such as a chart or a card, has one version that works with both.

### Let the style choose

The address `https://ui.ballmac.com/r/{style}/popover.json` answers with the version that fits the style. The shadcn CLI replaces `{style}` with your project's style, so pin `@ballmac` to it once:

```bash
npx shadcn@latest registry add @ballmac=https://ui.ballmac.com/r/{style}/{name}.json
```

After that, the usual command installs the right version for the project:

```bash
npx shadcn@latest add @ballmac/popover
```

A `base-*` style gets the Base UI version, and any other style gets the Radix UI version. If a component has no Base UI version, every style gets the one version it has.

## What stays the same

The Base UI versions keep the Radix UI API, so your code does not change. These all work as they do in the Radix UI version:

- `onSelect` on menu items, and `preventDefault()` in a close button's `onClick` to keep a dialog open while a request finishes.
- `onOpenAutoFocus` and `onCloseAutoFocus` on dialogs, alert dialogs and sheets, to place focus yourself.
- `asChild` on Button, Badge, Item and the sidebar menu button.
- `value`, `defaultValue` and `onValueChange` on the menubar, and `type` on the scroll area.
- Checkbox `"indeterminate"`, toggle group and accordion `type`, and select items written as children.
- Right-to-left: the arrow keys follow `<html dir>`, as before.

The same test suite runs against both versions in CI, so a behavior that differs fails the build.

## What differs

These are the places where the two libraries behave differently, and what the Base UI version does about it:

| | Radix UI | Base UI |
| --- | --- | --- |
| Styling hooks | `data-state="open"`, `"checked"`, `"active"` | `data-open`, `data-checked`, `data-active`, and `data-popup-open` on a trigger. Ballmac's classes already use them. |
| Rendering as your own element | `asChild` | A `render` prop on the part. For example, a menu item that is a link: `render={<a href="#profile" />}`. |
| Checkbox, switch and radio | A native button | An element with the right `role`. A disabled one has `aria-disabled` and `data-disabled`, not the `disabled` attribute. Inside a `fieldset disabled`, Ballmac's versions still disable themselves. |
| Slider | The thumb is the `slider` | The `slider` is a hidden input inside the thumb. Name it with `aria-label`, `aria-labelledby` or `thumbLabels`. |
| Accordion keys | Arrow keys move between headers | Tab moves between headers. |
| Focus after a dialog closes | Stays where it was if nothing took it back | Returns to the element that had it. |

If you write your own components on top of the Base UI versions, style with the `data-*` attributes above, and use `render` where you would use `asChild` on a part.

## Switch between them

Install the other version over the first. Run the `add` command with `--overwrite`:

```bash
npx shadcn@latest add https://ui.ballmac.com/r/base/dialog.json --overwrite
```

To go back, add the item by its usual name (`@ballmac/dialog`) with `--overwrite`. Then remove the Base UI package if nothing else uses it.

Your own edits to a component are overwritten, so commit first.

## Helpers

A Base UI version may add small helper files to your project, in `lib/ballmac`: `positioner` (maps Radix UI's `side`, `align` and `onSelect` onto Base UI), `slot` (`asChild`), `fieldset` (disabled fieldsets), `menubar-value` and `direction`. They are small, and a component pulls in the ones it needs when you add it.

---

<!-- Source: https://ui.ballmac.com/docs/guides/motion -->

# Use theme motion in your own code

Make your own CSS and JavaScript animations follow the theme's speed, bounce, travel, hover and stagger, and share a motion setup as a link.

## Follow the theme in CSS

Use the `--bm-*` variables the theme defines:

```css title="Your CSS"
.card {
  transition: transform var(--bm-duration-base) var(--bm-ease-out);
}
.card:hover {
  transform: translateY(var(--bm-lift)); /* -2px at hover 1, 0 at hover 0 */
}
.card:active {
  transform: scale(var(--bm-press)); /* 0.97 at hover 1 */
}
.toast {
  transform: translateY(var(--bm-distance)); /* enter distance, 10px at travel 1 */
  transition: transform var(--bm-duration-slow) var(--bm-ease-spring);
}
.list > * {
  transition-delay: calc(var(--i) * var(--bm-stagger-step));
}
.glow {
  animation: pulse 2s infinite;
  animation-play-state: var(--bm-ambient-state); /* running or paused */
}
```

Tailwind's own classes follow too, with no edits: `duration-*`, `delay-*`, `ease-out`, `ease-in`, `ease-in-out` and `ease-spring`.

## Follow the theme in JavaScript

`@ballmac/motion-presets` reads the same variables. Import the constants for one-off values, or call `useMotion()` when a component should follow the theme live.

```tsx title="components/toast.tsx"
import { motion } from "motion/react"
import { useLoopsPaused, useMotion } from "@/lib/ballmac/motion"

export function Toast({ children }: { children: React.ReactNode }) {
  const m = useMotion()
  return (
    <motion.div
      initial={{ opacity: 0, y: m.distance }}
      animate={{ opacity: 1, y: 0 }}
      transition={m.spring.snappy}
    >
      {children}
    </motion.div>
  )
}
```

## Hold decorative loops still

Decorative loops hold still when the theme turns them off, or under reduced motion. Use `useLoopsPaused` for those only:

```tsx
function Glow() {
  const still = useLoopsPaused()
  return <motion.span animate={still ? undefined : { opacity: [0.4, 1, 0.4] }} transition={{ duration: 2, repeat: Infinity }} />
}
```

> **Note:** Spinners and "thinking" indicators carry meaning, so they should keep running. Don't pause them with `useLoopsPaused`.

## Share a motion setup as a link

A feel has a short address, and any combination is a link. Nothing is stored, and every value is clamped to its range, so a link can never produce an unsafe theme.

```bash
# a ready-made feel
https://ui.ballmac.com/themes?mo=playful

# or any values: speed, bounce, travel, hover, stagger, ambient (1 or 0), reduce (system or always)
https://ui.ballmac.com/themes?sp=1.3&bn=0.6&tv=0.8&hv=1.2&st=0.5&am=0
```

Install a custom design, color and motion together:

```bash
npx shadcn@latest add "https://ui.ballmac.com/themes/custom.json?h=262&mo=calm"
```

---

<!-- Source: https://ui.ballmac.com/docs/guides/platform-feels -->

# Pick a platform feel

Make every component handle like macOS, iOS or Windows, by installing one feel or letting the device choose before the first paint.

## Install one feel

For an app that always runs on one platform, install its feel. It changes handling only and leaves your colors alone.

```bash
npx shadcn@latest add @ballmac/feel-macos
```

The other feels are `@ballmac/feel-ios`, `@ballmac/feel-windows` and `@ballmac/feel-web`. See [what each feel sets](https://ui.ballmac.com/docs/reference/platform-feels).

## Let the device pick

A Tauri or Electron app that ships to Mac and Windows, or a web app used on phones and desktops, should handle like the platform it is on. `feel-auto` includes all four feels and sets the right one before the first paint, so there is no flash.

```bash
npx shadcn@latest add @ballmac/feel-auto
```

Add `FeelScript` to your root layout:

```tsx title="app/layout.tsx"
import { FeelScript } from "@/components/ballmac/feel-script"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <FeelScript />
      </head>
      <body>{children}</body>
    </html>
  )
}
```

## Let people override it

Go back to the device's own feel with `auto`. The choice is remembered in the browser.

```tsx
import { useFeel } from "@/lib/ballmac/feel"

const { feel, setFeel } = useFeel()
setFeel("windows") // "web" | "macos" | "ios" | "windows"
setFeel("auto") // the device's own feel
```

## Share a feel as a link

```bash
# open a feel in the builder
https://ui.ballmac.com/themes?fe=windows
```

Install a custom design with a feel, colors and motion together:

```bash
npx shadcn@latest add "https://ui.ballmac.com/themes/custom.json?h=262&fe=macos"
```

## Related

- [How platform feels work](https://ui.ballmac.com/docs/concepts/platform-feels)
- [Ship a desktop app](https://ui.ballmac.com/docs/guides/desktop-app)

---

<!-- Source: https://ui.ballmac.com/docs/guides/desktop-app -->

# Ship a desktop app

Draw your own window chrome in a Tauri v2 or Electron app with app-window, wired to the real window actions.

`app-window` is the window chrome of a real app: a title bar that drags the window, maximizes on double-click, and carries the controls the platform draws. It has no Tauri or Electron dependency. You pass the actions in.

```bash
npx shadcn@latest add @ballmac/app-window @ballmac/feel-auto
```

## Use it in Tauri v2

### Turn off the native frame

```json title="src-tauri/tauri.conf.json"
{
  "app": {
    "windows": [{ "title": "Notes", "decorations": false }]
  }
}
```

### Allow the window actions

```json title="src-tauri/capabilities/default.json"
{
  "identifier": "default",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "core:window:allow-close",
    "core:window:allow-minimize",
    "core:window:allow-toggle-maximize",
    "core:window:allow-is-maximized",
    "core:window:allow-set-fullscreen",
    "core:window:allow-start-dragging"
  ]
}
```

### Wire the buttons

```tsx title="src/App.tsx"
import { getCurrentWindow } from "@tauri-apps/api/window"
import { AppWindow, AppWindowContent, AppWindowTitleBar } from "@/components/ballmac/app-window"

const win = getCurrentWindow()

export default function App() {
  const [maximized, setMaximized] = useState(false)
  useEffect(() => {
    win.isMaximized().then(setMaximized)
    const unlisten = win.onResized(() => win.isMaximized().then(setMaximized))
    return () => void unlisten.then((f) => f())
  }, [])

  return (
    <AppWindow
      className="h-screen rounded-none border-0"
      maximized={maximized}
      onClose={() => win.close()}
      onMinimize={() => win.minimize()}
      onMaximize={() => win.toggleMaximize()}
      onFullscreen={async () => win.setFullscreen(!(await win.isFullscreen()))}
    >
      <AppWindowTitleBar title="Notes" />
      <AppWindowContent>…</AppWindowContent>
    </AppWindow>
  )
}
```

## Use it in Electron

Create the window without a frame and handle the actions in the main process:

```ts title="main.ts"
const win = new BrowserWindow({ frame: false, webPreferences: { preload: join(__dirname, "preload.js") } })

ipcMain.on("window", (event, action: "close" | "minimize" | "maximize") => {
  const w = BrowserWindow.fromWebContents(event.sender)
  if (!w) return
  if (action === "maximize") w.isMaximized() ? w.unmaximize() : w.maximize()
  else w[action]()
})
```

Expose a small bridge in the preload script:

```ts title="preload.ts"
contextBridge.exposeInMainWorld("windowControls", {
  send: (action: "close" | "minimize" | "maximize") => ipcRenderer.send("window", action),
})
```

Pass the actions to the window in the renderer:

```tsx title="renderer"
<AppWindow
  onClose={() => window.windowControls.send("close")}
  onMinimize={() => window.windowControls.send("minimize")}
  onMaximize={() => window.windowControls.send("maximize")}
/>
```

The title bar sets both `data-tauri-drag-region` and `-webkit-app-region: drag`, and the toolbar inside it is marked no-drag, so buttons stay clickable.

> **Note:** Windows 11's snap layouts and the macOS window-tiling menu belong to native controls. See [the limits of platform feels](https://ui.ballmac.com/docs/concepts/platform-feels#know-the-limits).

## Start from the desktop blocks

The free [desktop blocks](https://ui.ballmac.com/blocks) (preferences, menu bar and tray, software update, licence activation, permissions setup, About, welcome window and shortcuts editor) draw real window chrome with `app-window` and handle like macOS or Windows from the page's feel. The AI notes on the software update block show how to wire `tauri-plugin-updater` and `electron-updater`.

---

<!-- Source: https://ui.ballmac.com/docs/guides/right-to-left -->

# Turn on right-to-left

Set the page direction and every component, block and template mirrors. Add the direction provider, write RTL-safe code and test it.

## Set the direction

Set `dir` and `lang` on `<html>`, and wrap the app in `DirectionProvider` so the Radix-based components (tabs, sliders, menus, radio groups) know the direction too.

```bash
npx shadcn@latest add @ballmac/direction
```

```tsx title="app/layout.tsx"
import { DirectionProvider } from "@/lib/ballmac/direction"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

Items that need the direction in code (`Tabs`, `Slider`, `Calendar`, `Carousel`, `Sidebar` and others) add it for you. They also read `<html dir>` on their own, so an app that sets only the attribute still works. The provider adds the Radix side.

Give a region its own direction with `<Dir dir="ltr">`.

## Test it

Every component and block preview has a right-to-left toggle in its toolbar. Any preview opens right-to-left with `/preview/<name>?dir=rtl`.

`pnpm rtl:sweep` loads every preview in both directions and reports any whose layout isn't the mirror image of the other, or that overflows only in RTL.

## Write RTL-safe code

The repository checks this on every build (`pnpm check` runs `scripts/rtl.ts`):

- Physical classes are rejected: `ml-` `mr-` `pl-` `pr-` become `ms-` `me-` `ps-` `pe-`; `left-` and `right-` become `start-` and `end-`; `text-left` becomes `text-start`; `border-l` becomes `border-s`; `rounded-l` becomes `rounded-s`. Centering with `left-1/2 -translate-x-1/2` is symmetric and allowed.
- Directional icons from lucide-react must have an `rtl:` class. `npx tsx scripts/rtl.ts --fix` rewrites the safe cases.
- Anything that is physical on purpose carries a comment containing `rtl-fixed` and a reason, or is listed in `scripts/rtl-exceptions.json`.
- A `translate-x` nudge needs its mirror: `group-hover:translate-x-1 rtl:group-hover:-translate-x-1`.

## Know the limits

- Templates mirror, but their sample copy is English and written into the files, so translate it as you would any content.
- Recharts draws SVG in its own coordinates: axes don't mirror. Pass `reversed` to an axis yourself if your design needs it.
- The marquee and blur-fade `direction` props are physical. Choose the value you want for your reading direction.

## Related

- [What mirrors, and what doesn't](https://ui.ballmac.com/docs/concepts/right-to-left)
- [Translate Ballmac UI](https://ui.ballmac.com/docs/guides/translate)

---

<!-- Source: https://ui.ballmac.com/docs/guides/translate -->

# Translate Ballmac UI

Translate every built-in label, placeholder and screen reader text with one provider, and set the locale for dates and numbers.

Every built-in label, placeholder and screen reader text is a message with an English default. One provider translates all of them and sets the locale for dates and numbers. The full list is in the [messages reference](https://ui.ballmac.com/docs/reference/messages).

## Add the provider

```bash
npx shadcn@latest add @ballmac/i18n
```

```tsx title="app/layout.tsx"
import { I18nProvider } from "@/lib/ballmac/i18n"
import ar from "@/messages/ar.json"

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <I18nProvider locale="ar" messages={ar}>
          {children}
        </I18nProvider>
      </body>
    </html>
  )
}
```

`messages` is a flat object from key to text. Anything you leave out stays in English, so you can translate in stages.

## Start from the full key list

Download the complete list of keys with their English text from [/i18n/en.json](https://ui.ballmac.com/i18n/en.json), copy it to `ar.json`, and translate the values.

## Use placeholders and plurals

```json title="messages/ar.json"
{
  "pagination.nextPage": "الصفحة التالية",
  "data-table.pageOf": "الصفحة {page} من {total}",
  "workspace-card.members_one": "عضو واحد",
  "workspace-card.members_two": "عضوان",
  "workspace-card.members_few": "{count} أعضاء",
  "workspace-card.members_many": "{count} عضوًا",
  "workspace-card.members_other": "{count} عضو"
}
```

`{name}` is replaced with a value. Where a message depends on a number, add one key per plural category your language has (`_zero`, `_one`, `_two`, `_few`, `_many`, `_other`, chosen with `Intl.PluralRules`). English needs only `_one` and `_other`, which are built in.

## Set the locale for dates, numbers and money

The provider's `locale` is used by every component that formats a number, a date, a currency or a duration. Without a provider, `<html lang>` is used, then `en-US`. A `locale` prop on a single component always wins. To get Western digits in an Arabic UI, use `ar-u-nu-latn`.

## Translate your own components

```tsx
"use client"
import { useLocale, useMessages } from "@/lib/ballmac/i18n"

export function Greeting({ name }: { name: string }) {
  const msg = useMessages()
  return <p>{msg("app.greeting", "Welcome back, {name}", { name })}</p>
}
```

`msg.rich` fills placeholders with React nodes, for example a `<kbd>`. If you already use next-intl, i18next or Lingui, keep them for your app and pass their output to `I18nProvider` as `messages`.

## What is translated, and what is not

- All components: accessible names, placeholders, hidden text, default button and empty-state text, status words and templated sentences.
- Props still win. `closeLabel`, `emptyMessage`, `label` and similar props override the dictionary.
- Blocks and templates are copy-in starting points. Their headlines and sample copy are props or live in the file, so you edit them as content. The right-to-left layout is covered in [What mirrors in right-to-left](https://ui.ballmac.com/docs/concepts/right-to-left).
- Sample values (such as the `.env` placeholder) and brand names aren't messages.

---

<!-- Source: https://ui.ballmac.com/docs/guides/images -->

# Use your own images

Every hero, feature, card and template ships with generated artwork. Pass an image and it replaces the artwork, with the shape, loading and alt text handled.

## Pass one prop

```tsx title="app/page.tsx"
import { Hero1 } from "@/components/ballmac/blocks/hero-1"

<Hero1
  media={{ src: "/shots/dashboard.png", srcDark: "/shots/dashboard-dark.png", alt: "The dashboard with this week's revenue chart" }}
/>
```

The prop is called `media` on blocks with one big picture, and `image` on items in a list (a post, a speaker, a project). It accepts a URL, an object with the alt text, or your own element such as a `next/image` or a video.

```tsx
<Hero1 media="/shots/dashboard.png" mediaAlt="The dashboard" />          // URL plus alt
<Hero1 media={{ src, srcDark, alt, width, height, position: "top" }} />  // full control
<Hero1 media={<Image src={shot} alt="The dashboard" priority fill />} />  // your own element
```

## What you get

- **Alt text is required.** In development a missing `alt` logs a warning. Use `alt=""` only for decoration.
- **No layout jump.** Each slot reserves a fixed aspect ratio before the file arrives.
- **Lazy by default.** Slots above the fold (hero) load right away with high priority.
- **Dark mode.** `srcDark` swaps the file when an ancestor has the `dark` class.
- **Never broken.** If the file is missing or fails to load, the built-in artwork shows instead of a broken icon.
- **Right-to-left safe.** Nothing here depends on reading direction.

## Use the Media component directly

Everything above is one component, `Media`, which you can use in your own layouts.

```bash
npx shadcn@latest add @ballmac/media
```

```tsx
import { Media } from "@/components/ballmac/media"

<Media media="/team.jpg" alt="The team at the 2026 offsite" aspect="video" frame priority />
```

`aspect` takes `square`, `video`, `photo`, `wide`, `portrait`, `cinema` or a ratio like `"3/2"`. `fit` is `cover`, `contain` or `fill`. All props are in the [building blocks reference](https://ui.ballmac.com/docs/reference/building-blocks).

## Where it works

Hero, feature and newsletter blocks, login and download screens, blog and changelog cards, the team grid, the device frames (phone, tablet, laptop, browser, watch, Android), the lock screen, control center, bento grid and team switcher, and the portfolio, studio, publication, podcast and goods templates. Each block page lists its image props.

---

<!-- Source: https://ui.ballmac.com/docs/guides/shadcn-mcp -->

# Use the shadcn MCP server

Ballmac UI is a standard shadcn registry, so the official shadcn MCP server works once the registry is in your project.

## Add the registry to your project

The `shadcn` CLI knows `@ballmac` from the official registry directory, but the MCP server only reads the registries listed in `components.json`, so add it there:

```bash
npx shadcn@latest registry add @ballmac=https://ui.ballmac.com/r/{name}.json
```

## Connect your client

```bash
npx shadcn@latest mcp init --client claude
```

Use `--client cursor`, `--client vscode`, `--client codex` or `--client opencode` for the others.

For Windsurf and other clients, add the server to your client's MCP configuration:

```json
{
  "mcpServers": {
    "shadcn": {
      "command": "npx",
      "args": ["shadcn@latest", "mcp"]
    }
  }
}
```

## What the agent sees

The shadcn MCP server exposes tools to list, search and view registry items, fetch examples, and get the add command. Each Ballmac item includes what an agent needs to pick it and wire it up. See [Built for AI agents](https://ui.ballmac.com/docs/concepts/built-for-agents).

> **Tip:** The [Ballmac MCP server](https://ui.ballmac.com/docs/get-started/connect-your-agent) adds tools the shadcn one doesn't have, such as page planning, change notes, tests and the quality audit.

---

<!-- Source: https://ui.ballmac.com/docs/guides/shadcn-chat-kit -->

# Use Ballmac with the shadcn chat kit

Trace, Questionnaire and Code Block have the same parts and props as shadcn's, so agent output drops into a chat built with either library.

shadcn's chat kit has three parts that an AI interface shows in almost every turn: a **Trace** for what the agent did, a **Questionnaire** for questions it needs answered, and a **Code Block** for what it ran or wrote. Ballmac has all three with the same part names and props, so code written for one works with the other.

## Install

```bash
npx shadcn@latest add @ballmac/trace @ballmac/questionnaire @ballmac/code-block
```

`questionnaire` is built on shadcn's headless `@shadcn/react/questionnaire`, which is installed with it, so its keyboard, form and validation behavior is the same as theirs. `trace` and `code-block` are our own code with the same API.

## One agent turn

```tsx
<Trace>
  <TraceCollapsible>
    <TraceCollapsibleTrigger>
      <TraceLine>
        <TraceIcon><Terminal /></TraceIcon>
        <TraceTitle>Ran the billing tests</TraceTitle>
        <TraceDescription>2 failed</TraceDescription>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      <CodeBlock size="sm" variant="destructive">
        <CodeBlockContent wrap className="max-h-48">{output}</CodeBlockContent>
      </CodeBlock>
    </TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>
```

The [Trace page](https://ui.ballmac.com/components/trace) has this turn live, with a questionnaire under it.

## What is the same

| Part | Same as shadcn | Also in Ballmac |
| --- | --- | --- |
| Trace | `TraceLine` with `status`, `TraceIcon`, `TraceTitle`, `TraceDescription`, `TraceCollapsible`, its trigger and content | A running title shimmers, the content respects reduced motion, and the open state is a controlled prop |
| Questionnaire | Every part, `items`, `shortcuts`, `multiple`, `invalid`, controlled `item` | Button labels come from the [translations](https://ui.ballmac.com/docs/guides/translate), and the actions use our `Button` |
| Code Block | `CodeBlockHeader`, `Title`, `Actions`, `CopyButton`, `Content`, `Line`, `Footer`, `size`, `variant`, `lineNumbers`, `startLine`, `wrap` | `useHighlightedCode` highlights with no async loading, and the ready-made panel (`code`, `filename`, `language`) still works |

## Where it differs

- `useHighlightedCode` uses Ballmac's small built-in tokenizer (JSON, shell, JavaScript and TypeScript, Python, Go, HTTP and `.env`), not Shiki, so there is nothing to load and it renders on the server. For other languages, render your own highlighted lines inside `CodeBlockLine`, or use Shiki output as children of the ready-made panel.
- ANSI colors in terminal output are not rendered. Strip the escape codes before you pass the text in.
- `CodeBlock` with no parts inside is the ready-made panel; with `CodeBlockHeader`, `CodeBlockActions`, `CodeBlockContent` or `CodeBlockFooter` as direct children it is the compound block. Wrapping the parts in another component switches it back to the panel, so keep them direct.

## Related

- [Built for AI agents](https://ui.ballmac.com/docs/concepts/built-for-agents)
- [States](https://ui.ballmac.com/docs/guides/states)

---

<!-- Source: https://ui.ballmac.com/docs/guides/pro -->

# Set up Ballmac UI Pro

Add your licence key and the private registry once per project, then install Pro blocks and starter apps with the shadcn CLI or the MCP server.

Pro items install the same way as free ones, from a private registry that checks your licence key. Set it up once per project.

## Get a licence key

Buy Pro on the [pricing page](https://ui.ballmac.com/pricing). Your licence key arrives by email from our payment partner. Keep it private: anyone with the key can install Pro items.

> **Tip:** You don't need the command line to use Pro. [Log in with your licence key](https://ui.ballmac.com/pro/login) to read and copy the code of any Pro block on its page, download the starter apps as a zip, and get install commands with your key already filled in. There is no password and no account: the key is the login, and it is kept in an encrypted cookie in your browser until you log out.

## Add the key to your project

Put it in `.env.local`, which the shadcn CLI reads. Don't commit this file.

```bash title=".env.local"
BALLMAC_LICENSE_KEY=your-licence-key
```

## Add the Pro registry

Add both registries to `components.json`. The CLI sends your key only to the Pro registry.

```json title="components.json"
{
  "registries": {
    "@ballmac": "https://ui.ballmac.com/r/{name}.json",
    "@ballmac-pro": {
      "url": "https://ui.ballmac.com/r/pro/{name}.json",
      "headers": { "Authorization": "Bearer ${BALLMAC_LICENSE_KEY}" }
    }
  }
}
```

## Install Pro items

Pro items use the `@ballmac-pro` namespace. Their page shows the exact command.

```bash
npx shadcn@latest add @ballmac-pro/<name>
```

Free items keep working as before, and a Pro item can depend on free ones.

## Download a starter app

A starter is a complete app you download and own. **Beacon SaaS** is a Next.js app with sign-in, workspaces with teams and invitations, Stripe billing, a dashboard, settings and API keys. Download it with the same key:

```bash
mkdir my-app && curl -fsSL -H "Authorization: Bearer $BALLMAC_LICENSE_KEY" https://ui.ballmac.com/r/pro/starters/beacon-saas.tar.gz | tar -xz -C my-app --strip-components=1 && cd my-app && pnpm install && pnpm dev
```

It runs on an embedded database in development, so there is nothing else to install. The README in the download covers customising it, billing and deployment.

**Cove** (`cove-desktop`) is a Tauri 2 desktop app, a notes app that looks native on macOS and Windows: traffic lights or caption buttons, the system blur behind the sidebar on a Mac, native menus and a command palette. It runs in a browser too, so you can build the interface without installing Rust.

## Import the Figma design tokens

Every Ballmac theme as W3C design tokens, with light and dark as separate groups (one Figma variable mode each) and the original color kept beside each hex value. Import them with a Figma plugin that reads the W3C token format, such as Tokens Studio.

```bash
curl -fsSL -H "Authorization: Bearer $BALLMAC_LICENSE_KEY" https://ui.ballmac.com/r/pro/kits/ballmac-figma-tokens.tar.gz | tar -xz
```

## Use Pro with the MCP server

Give the Ballmac MCP server your key and it can read Pro items and give your agent the right install commands.

```bash title="Claude Code"
claude mcp add ballmac --env BALLMAC_LICENSE_KEY=your-licence-key -- npx -y @ballmac/mcp
```

In a JSON config, add `"env": { "BALLMAC_LICENSE_KEY": "your-licence-key" }` next to `args`. Your [Pro library](https://ui.ballmac.com/pro) shows these commands with your own key filled in.

## Use Pro in CI and with a team

Set `BALLMAC_LICENSE_KEY` as a secret in your CI or hosting provider. Each person on a Team licence can use the shared key in their own `.env.local`.

## Check a key

```bash
curl -s -X POST https://ui.ballmac.com/api/v1/license/verify -H "content-type: application/json" -d '{"key":"your-licence-key"}'
```

The answer is `{"valid":true}` or a reason the key isn't accepted.

## Troubleshoot

- **I logged in but the code doesn't show:** reload the page. If you still see the lock, your session ended (for example after you revoked the key); [log in again](https://ui.ballmac.com/pro/login).
- **401 licence required:** the CLI didn't send a key. Check that `BALLMAC_LICENSE_KEY` is in `.env.local` and that the `@ballmac-pro` entry has the `headers` block.
- **403 licence invalid:** the key was mistyped or is no longer active. Copy it again from your purchase email.
- **Unknown registry @ballmac-pro:** the `registries` block is missing from `components.json`.

Still stuck? [Contact support](https://ui.ballmac.com/support?topic=help) and include the item name and the error you see. Never send your licence key: we find your purchase from your email.

## Licence terms

What you may do with Pro items is set out in the [Pro licence agreement](https://ui.ballmac.com/license/pro). In short: build as many apps and sites as you like, for yourself or your clients, but don't share, resell or publish the Pro source or your key.

---

<!-- Source: https://ui.ballmac.com/docs/reference/cli -->

# CLI commands

Every ballmac command and option, generated from the CLI's own help text.

Run `ballmac` through `npx @ballmac/cli`. This page lists every command and option the tool prints in `ballmac --help`.

```bash
npx @ballmac/cli <command> [options]
```

## `ballmac add <item…>` [#add]

Install items with shadcn and start tracking them

| Option | What it does |
| --- | --- |
| `--with-tests` | also install the tests that ship with the items |
| `--no-install` | do not install the test packages (print them instead) |

## `ballmac outdated [item…]` [#outdated]

Which tracked items have a newer version, and what changed

| Option | What it does |
| --- | --- |
| `--json` | machine-readable output |
| `--exit-code` | exit 1 when something is behind (for CI) |

## `ballmac update [item…]` [#update]

Merge new versions into your copies (your edits are kept)

| Option | What it does |
| --- | --- |
| `--dry-run` | show what would change and write nothing |

## `ballmac diff <item>` [#diff]

What you changed since you installed an item

## `ballmac track [item…]` [#track]

Start tracking items you installed with plain shadcn

| Option | What it does |
| --- | --- |
| `--rebase` | declare your files up to date with the latest version |

## `ballmac check [path…]` [#check]

Audit your app for what agents and people get wrong: missing states, unlabelled controls, fixed colours, endless motion, overflow on phones

| Option | What it does |
| --- | --- |
| `--rules` | list the rules and exit |
| `--format <f>` | pretty (default), json or github (annotations for CI) |
| `--fail-on <level>` | error (default), warn or never: when to exit with 1 |
| `--all` | show every finding, not the first few per rule |
| `--offline` | do not read the Ballmac catalogue |
| `--include-library` | also audit components/ui and components/ballmac |
| `--url <address>` | also load a running app in a real browser (axe, overflow, console) |
| `--routes <a,b>` | routes to load with --url (default /) |

## `ballmac verify <registry>` [#verify]

Score any shadcn registry (a URL, a folder or a registry.json) on what can be checked from its source, and write a badge. Needs TypeScript in this project.

| Option | What it does |
| --- | --- |
| `--min-score <n>` | exit 1 when the score is below n (for CI) |
| `--format <f>` | pretty (default) or json |
| `--out <file>` | write the scorecard JSON |
| `--badge <file>` | write the "Ballmac Verified" SVG badge |
| `--item-url <url>` | where items live when it is not next to registry.json, e.g. https://acme.com/r/\{name\}.json |
| `--limit <n>` | score only the first n items |
| `--all` | list every item that lost points |

## `ballmac test [item…]` [#test]

Run the Ballmac tests installed in this project

## `ballmac test add <item…>` [#test-add]

Install the tests for items that are already installed

## Global options [#global-options]

| Option | What it does |
| --- | --- |
| `--cwd <dir>` | Project folder (default: the current folder) |
| `-v, --version` | Print the version |
| `-h, --help` | Print this help |

## Environment variables [#environment]

| Variable | What it does |
| --- | --- |
| `BALLMAC_UI_URL` | Registry to read (default https://ui.ballmac.com) |
| `BALLMAC_LICENSE_KEY` | Your Ballmac UI Pro key (env or .env.local); needed for Pro items |
| `BALLMAC_SHADCN` | How to run shadcn, e.g. "pnpm dlx shadcn@latest" (default: your local copy, else the package manager's runner) |

## Related

- [Update components without losing your edits](https://ui.ballmac.com/docs/guides/update-components)
- [Check your own app](https://ui.ballmac.com/docs/guides/check-your-app)

---

<!-- Source: https://ui.ballmac.com/docs/reference/check-rules -->

# Check rules

Every rule ballmac check and the audit_project tool apply, with why it matters, how to fix it and an example.

15 rules. Each one says why it matters to the person using your app and how to fix it. To run them, see [Check your own app](https://ui.ballmac.com/docs/guides/check-your-app). To change a rule's level, set it in `.ballmacrc.json`.

## States [#states]

### states/ballmac-state-prop [#states-ballmac-state-prop]

**Warning.** A Ballmac component with states is used without a state prop

The component can show a loading skeleton, an empty message and an error with a retry, but this use only ever shows the ready view. When the request is slow or fails, people see a blank area or a frozen screen.

**Fix:** Pass `state` from your data hook, for example state=\{isPending ? "loading" : isError ? "error" : rows.length ? "ready" : "empty"\} and onRetry=\{refetch\}.

Instead of:

```tsx
<DashboardPro1 />
```

Write:

```tsx
<DashboardPro1 state={isPending ? "loading" : isError ? "error" : "ready"} onRetry={refetch} />
```

### states/data-without-states [#states-data-without-states]

**Warning.** A component fetches data but never reads loading or error state

While the request is running, or if it fails, the screen has nothing to show. That is the first thing a person sees on a slow connection.

**Fix:** Read the loading and error values the hook returns (isPending, isError, error), render a skeleton for loading and a message with a retry for errors, and an empty state when there is no data.

Instead of:

```tsx
const { data } = useQuery(...)
return <List items={data} />
```

Write:

```tsx
const { data, isPending, isError, refetch } = useQuery(...)
if (isPending) return <ListSkeleton />
if (isError) return <ErrorState onRetry={refetch} />
return <List items={data} />
```

## Accessibility [#a11y]

### a11y/img-alt [#a11y-img-alt]

**Error.** An image has no alt attribute

Screen reader users hear the file name or nothing. Search engines and failed loads show nothing useful either.

**Fix:** Add alt with a short description of what the image shows, or alt="" if it is purely decorative.

Instead of:

```tsx
<img src="/hero.png" />
```

Write:

```tsx
<img src="/hero.png" alt="The dashboard with a revenue chart" />
```

### a11y/control-name [#a11y-control-name]

**Error.** A button or link has an icon but no accessible name

Screen reader users hear only “button” with no hint of what it does, and voice control users cannot say its name.

**Fix:** Add aria-label="What it does" to the control, or include visually hidden text such as \<span className="sr-only">Close\</span>.

Instead of:

```tsx
<button onClick={close}><XIcon /></button>
```

Write:

```tsx
<button onClick={close} aria-label="Close"><XIcon aria-hidden="true" /></button>
```

### a11y/clickable-non-interactive [#a11y-clickable-non-interactive]

**Error.** A click handler is on an element that keyboards cannot reach

A \<div> with onClick cannot be focused or activated with the keyboard, and screen readers do not announce it as something to press.

**Fix:** Use a \<button> (or \<a> for navigation). If it truly must stay a div, add role, tabIndex=\{0\} and onKeyDown for Enter and Space.

Instead of:

```tsx
<div onClick={open}>Open</div>
```

Write:

```tsx
<button type="button" onClick={open}>Open</button>
```

### a11y/focus-outline-removed [#a11y-focus-outline-removed]

**Error.** The focus outline is removed and nothing replaces it

Keyboard users lose track of where they are. It is one of the most common ways an app becomes unusable without a mouse.

**Fix:** Keep the default outline, or replace it with a visible style such as focus-visible:ring-2 focus-visible:ring-ring.

Instead of:

```tsx
<button className="outline-none">
```

Write:

```tsx
<button className="outline-none focus-visible:ring-2 focus-visible:ring-ring">
```

### a11y/input-label [#a11y-input-label]

**Warning.** A form field has no label

A placeholder disappears as soon as someone types and is not a label. Screen readers announce an unnamed field, and clicking the text does not focus it.

**Fix:** Add \<label htmlFor="id"> that matches the field's id, wrap the field in a \<label>, or add aria-label.

Instead of:

```tsx
<input placeholder="Email" />
```

Write:

```tsx
<label htmlFor="email">Email</label>
<input id="email" />
```

### a11y/positive-tabindex [#a11y-positive-tabindex]

**Error.** tabIndex is greater than zero

It pulls the element to the front of the tab order, so focus jumps around the page in a way nobody expects.

**Fix:** Use tabIndex=\{0\} to make an element focusable in its natural place, or tabIndex=\{-1\} to focus it from code only.

### a11y/hand-rolled-dialog [#a11y-hand-rolled-dialog]

**Warning.** A full-screen overlay is built by hand

A hand-made modal usually misses the focus trap, Escape to close, scroll lock and the dialog role, so keyboard and screen reader users get stuck behind it.

**Fix:** Use the Dialog, Sheet or Drawer component (Radix-based), which does all of that.

Instead of:

```tsx
<div className="fixed inset-0 z-50 bg-black/50">…</div>
```

Write:

```tsx
<Dialog><DialogContent>…</DialogContent></Dialog>
```

### a11y/link-no-href [#a11y-link-no-href]

**Warning.** A link has no href

An \<a> without href is not focusable or announced as a link. People cannot open it in a new tab or copy its address.

**Fix:** Add an href, or use a \<button> if it performs an action instead of navigating.

## Tokens [#tokens]

### tokens/hardcoded-color [#tokens-hardcoded-color]

**Warning.** A fixed colour is used instead of a theme token

Fixed colours do not follow the theme, so dark mode and any re-theming break: white panels glowing in the dark, grey text that cannot be read.

**Fix:** Use token classes: bg-background, bg-card, text-foreground, text-muted-foreground, border-border, bg-primary, text-destructive.

Instead of:

```tsx
className="bg-white text-gray-900 border-gray-200"
```

Write:

```tsx
className="bg-background text-foreground border-border"
```

## Motion [#motion]

### motion/endless-decorative [#motion-endless-decorative]

**Warning.** A decorative animation runs forever and ignores reduced motion

People with vestibular disorders turn on “reduce motion” because endless movement makes them ill. An animation that ignores the setting is an accessibility bug.

**Fix:** Add motion-reduce:animate-none to the class, wrap the Motion loop in useReducedMotion, or put the CSS animation inside @media (prefers-reduced-motion: no-preference).

Instead of:

```tsx
<span className="animate-ping" />
```

Write:

```tsx
<span className="animate-ping motion-reduce:animate-none" />
```

## Layout [#layout]

### layout/fixed-wide [#layout-fixed-wide]

**Warning.** A fixed width is wider than a small phone

A phone is 360 to 390 pixels wide. A fixed 480px element makes the whole page scroll sideways.

**Fix:** Add max-w-full, or make the width responsive: w-full md:w-[480px].

Instead of:

```tsx
<div className="w-[480px]">
```

Write:

```tsx
<div className="w-full max-w-[480px]">
```

### layout/viewport-height [#layout-viewport-height]

**Note.** h-screen is used for a full-height layout

On phones, 100vh includes the area behind the browser's address bar, so the bottom of the page is cut off.

**Fix:** Use min-h-dvh or h-dvh, which follow the visible area.

## Right to left [#rtl]

### rtl/physical-direction [#rtl-physical-direction]

**Off by default.** A left or right class will not mirror in right-to-left languages

In Arabic, Hebrew, Persian and Urdu the layout should be mirrored. Fixed left and right spacing leaves margins and icons on the wrong side.

**Fix:** Use logical classes: ms-/me- instead of ml-/mr-, ps-/pe- instead of pl-/pr-, text-start/text-end, start-/end- instead of left-/right-.

---

<!-- Source: https://ui.ballmac.com/docs/reference/mcp-tools -->

# MCP tools

Every tool, resource and prompt of the @ballmac/mcp server, read from the running server.

`@ballmac/mcp` is read-only: it answers questions and returns install commands, and never writes files or runs commands. To connect it, see [Connect your AI agent](https://ui.ballmac.com/docs/get-started/connect-your-agent).

## Tools [#tools]

13 tools.

### list_items [#list_items]

List components, blocks or templates, optionally filtered by kind, category (a component category such as primitives, motion or ai; a block section such as hero or pricing; or a template kind such as marketing) or tier. Call list_categories for the valid names.

| Input | Type | Required |
| --- | --- | --- |
| `kind` | `"component" \| "block" \| "template"` | No |
| `category` | `string` | No |
| `tier` | `"free" \| "pro"` | No |

### list_categories [#list_categories]

How many components, blocks and templates there are, and the categories each is grouped into, with counts. Use the names as the category filter of list_items.

Takes no input.

### search_items [#search_items]

Search by what the user needs (for example 'chat input with attachments', 'animated stat', 'pricing section', 'online store'). Understands common synonyms such as modal, navbar or ecommerce. Returns the best matches with when-to-use guidance.

| Input | Type | Required |
| --- | --- | --- |
| `query` | `string` | Yes |
| `kind` | `"component" \| "block" \| "template"` | No |
| `limit` | `integer` (1 to 30) | No |

### get_item [#get_item]

Everything about one item: description, when to use and not, import line, props, keyboard behaviour, dependencies, composition hints, template pages and the full source (for Pro items only when the server has BALLMAC_LICENSE_KEY; see the `licence` field). Set includeSource to false for a shorter answer.

| Input | Type | Required |
| --- | --- | --- |
| `name` | `string` | Yes |
| `includeSource` | `boolean` | No |

### get_examples [#get_examples]

Working example code for an item (the same examples shown on its page).

| Input | Type | Required |
| --- | --- | --- |
| `name` | `string` | Yes |

### get_changes [#get_changes]

The changelog of an item: what changed in each version, newest first, and whether anything is breaking. Pass `since` (the version the user has, for example from ballmac.lock.json) to see only newer changes. Use it before updating an installed item; `npx @ballmac/cli outdated` lists which installed items are behind and `npx @ballmac/cli update` merges new versions into the user's edited copies.

| Input | Type | Required |
| --- | --- | --- |
| `name` | `string` | Yes |
| `since` | `string` | No |

### make_preset [#make_preset]

Make a whole design system (colors, corner radius, typeface, platform feel and motion) from a description, and get its 16-character code and the command that installs it: `npx shadcn@latest add @ballmac/preset-<code>`. Start from a ready-made system with `from` (ink, harbor, lumen, bloom, forge, rosewater, canopy, lagoon, atelier, paper, terminal, signal, menubar, pocket, pane), a theme name such as ocean, or an existing code, then change what the user asked for. With no input it returns the default. Contrast is checked for every pairing in light and dark; the result lists any that fail. Add ?only=colors,shape,font,motion,feel to the returned url to install only some parts.

| Input | Type | Required |
| --- | --- | --- |
| `from` | `string` | No |
| `hue` | `number` (0 to 360) | No |
| `chroma` | `number` (0 to 0.26) | No |
| `greyHue` | `number` (0 to 360) | No |
| `tint` | `number` (0 to 0.03) | No |
| `radius` | `number` (0 to 1.5) | No |
| `primary` | `"brand" \| "ink"` | No |
| `paper` | `boolean` | No |
| `edge` | `"soft" \| "strong"` | No |
| `density` | `"compact" \| "default" \| "comfortable"` | No |
| `font` | `string` | No |
| `feel` | `"web" \| "macos" \| "ios" \| "windows"` | No |
| `motion` | `"native" \| "calm" \| "snappy" \| "playful" \| "still"` | No |

### get_tests [#get_tests]

The vitest + Testing Library test file that ships with an item (keyboard use, states, ARIA). Use it to check an item still works after you or the user changed it. `npx @ballmac/cli add <name> --with-tests` installs it next to the component with the packages and config it needs, and `npx @ballmac/cli test` runs it. Returns shipped:false when the item has no test of its own.

| Input | Type | Required |
| --- | --- | --- |
| `name` | `string` | Yes |

### get_quality_rules [#get_quality_rules]

The rules `audit_project` checks, with why each matters, how to fix it and a bad and a good example: missing loading, empty and error states, unlabelled controls, removed focus outlines, fixed colours instead of theme tokens, endless motion, layouts that overflow a phone. Read it BEFORE writing UI so the first draft already follows them. It needs no project and no network.

Takes no input.

### audit_project [#audit_project]

Read the project's React source (it never writes or runs anything) and report what people and assistive technology trip over: components that can show loading, empty and error states but are used without a state prop, data fetched with no loading or error handling, icon-only buttons with no name, click handlers on divs, removed focus outlines, images without alt, unlabelled fields, hand-built modals, fixed colours instead of theme tokens, endless animations that ignore reduced motion, and fixed widths wider than a phone. Call it when you finish writing UI, fix every error, and call it again until it is clean. `path` is the project folder (default: the folder this server was started in); it must contain a package.json. Findings come worst first, each with the line and the fix.

| Input | Type | Required |
| --- | --- | --- |
| `path` | `string` | No |
| `paths` | `string[]` | No |
| `minSeverity` | `"error" \| "warn" \| "info"` | No |
| `limit` | `integer` (1 to 200) | No |
| `includeLibrary` | `boolean` | No |

### get_install_command [#get_install_command]

The shadcn CLI commands to add one or more items to the user's project. `setup` is optional: a current shadcn CLI resolves @ballmac without it, so run it only for an older CLI. Then run `add`. `byUrl` works without setup for free items. Pro items install as @ballmac-pro/\<name> and need `proSetup` once.

| Input | Type | Required |
| --- | --- | --- |
| `names` | `string[]` | Yes |
| `packageManager` | `"pnpm" \| "npm" \| "yarn" \| "bun"` | No |

### compose_page [#compose_page]

Plan a page from Ballmac UI blocks for an intent such as 'SaaS landing page with pricing and FAQ' or 'login screen'. Returns the chosen blocks in page order, install commands, a page.tsx scaffold that renders them, and any complete templates that already fit the intent. When the request names macOS, Windows, iOS, Tauri or Electron (or you pass feel), it also returns the platform feel to install so the whole app handles like that platform.

| Input | Type | Required |
| --- | --- | --- |
| `intent` | `string` | Yes |
| `packageManager` | `"pnpm" \| "npm" \| "yarn" \| "bun"` | No |
| `includePro` | `boolean` | No |
| `feel` | `"web" \| "macos" \| "ios" \| "windows"` | No |

### get_setup [#get_setup]

How to prepare a project for Ballmac UI (shadcn init, optional theme, and the registry entry older CLIs need) and its requirements.

| Input | Type | Required |
| --- | --- | --- |
| `packageManager` | `"pnpm" \| "npm" \| "yarn" \| "bun"` | No |

## Resources [#resources]

- `ballmac://catalog`: Every component, block and template with a one-line description.
- `ballmac://items/{name}`: One item as Markdown: install, import, props, keyboard behaviour and when to use it.

## Prompts [#prompts]

- `build_page`: Plan and build a page from Ballmac UI blocks, or start from a template that fits. Arguments: `intent`.
- `choose_component`: Find the right component for a need and wire it up correctly. Arguments: `need`.

## Environment variables [#environment]

Both are environment variables of the server. Neither is needed for the free catalog.

| Variable | What it does |
| --- | --- |
| `BALLMAC_UI_URL` | The catalog to read. Default `https://ui.ballmac.com`. Set it to read a local copy of the site. |
| `BALLMAC_LICENSE_KEY` | Your Ballmac UI Pro licence key. With it, `get_item` and `get_examples` also return the source of Pro items. The key is only sent to the Ballmac UI site. |

`npx @ballmac/mcp --help` and `--version` work from a terminal.

## Pro items [#pro-items]

Pro items show up in search with `tier: "pro"` and install from the `@ballmac-pro` namespace. Without a key the agent still sees their description, props and install command, but not the source. See [Set up Ballmac UI Pro](https://ui.ballmac.com/docs/guides/pro#use-pro-with-the-mcp-server).

---

<!-- Source: https://ui.ballmac.com/docs/reference/registry -->

# Registry

Addresses, file locations, dependencies and the JSON format of the @ballmac shadcn registry.

Ballmac UI is a standard shadcn registry. Everything the shadcn CLI can do with a registry, it can do with `@ballmac`.

## Addresses

Every item has a permanent name and a JSON URL:

```bash
npx shadcn@latest add @ballmac/button
npx shadcn@latest add https://ui.ballmac.com/r/button.json
```

The first form uses the namespace: `@ballmac` is in the official shadcn registry directory, so it needs no setup. The second works anywhere.

## Search and inspect

```bash
npx shadcn@latest search @ballmac -q "chat"
npx shadcn@latest view @ballmac/prompt-input
```

`search` lists everything in `@ballmac` that matches. `view` shows an item's files, dependencies and docs.

## Compare before updating

Components are your code once installed, so updates never happen silently. To see what changed in a newer version, compare before overwriting:

```bash
npx shadcn@latest add @ballmac/button --diff
npx shadcn@latest add @ballmac/button --overwrite
```

To merge new versions into edited copies instead, see [Update components without losing your edits](https://ui.ballmac.com/docs/guides/update-components).

## Where files go [#where-files-go]

- Components: `components/ballmac/`, which respects your `components` alias and `src/` folder
- Blocks: `components/ballmac/blocks/<name>/`
- Hooks and helpers: `hooks/ballmac/`, `lib/ballmac/`

Ballmac UI never writes to `components/ui`, so it can't overwrite your shadcn/ui components.

## Dependencies

Each item declares the npm packages it imports and the registry items it builds on, for example `@ballmac/motion-presets` or shadcn's `utils`. The CLI installs them for you. Every build checks that declared dependencies match the actual imports.

## JSON format

Items follow the official [shadcn registry schema](https://ui.shadcn.com/docs/registry). The full index is at [ui.ballmac.com/r/registry.json](https://ui.ballmac.com/r/registry.json). Each item's `meta` adds Ballmac fields for tools and agents: tier, tags, version, when to use it, what it composes with, and keyboard notes.

## Search

`shadcn search @ballmac --query button` returns ranked results from our server instead of downloading the whole 1,600-item catalog. The CLI sends `q`, `type`, `limit` and `offset` to `/r/registry.json` and gets back the matching items with a `pagination` object:

```bash
curl "https://ui.ballmac.com/r/registry.json?q=data+grid&type=registry:ui&limit=5"
```

Components rank ahead of blocks, and blocks ahead of examples, when two items match equally well. Every word of the query has to match something in the name, title, tags, category or description. Without any of those parameters the response is the full catalog, as before, so older clients are unaffected.

The same data is available as a [JSON API](https://ui.ballmac.com/api/v1/index.json), and as [llms.txt](https://ui.ballmac.com/llms.txt) for assistants.

---

<!-- Source: https://ui.ballmac.com/docs/reference/theme-tokens -->

# Theme tokens

The CSS variables Ballmac components read, and the motion variables the Ballmac theme derives.

## Color and shape tokens

Every component reads these standard shadcn variables. Change them in your `globals.css` and every Ballmac component follows.

| Token | Used for |
| --- | --- |
| `--background`, `--foreground` | Page and text |
| `--card` | Raised surfaces |
| `--primary` | Primary actions |
| `--muted` | Quiet surfaces and secondary text |
| `--accent` | Hover and selected states |
| `--border`, `--input` | Hairlines and field borders |
| `--ring` | Focus ring |
| `--destructive` | Errors and dangerous actions |
| `--chart-1` to `--chart-5` | Charts, accents and status dots |
| `--radius` | Corner radius |

## Motion tokens

Motion is part of the theme. The Ballmac theme sets these variables:

| Token | Meaning |
| --- | --- |
| `--bm-speed` | How fast everything moves |
| `--bm-bounce` | How springy |
| `--bm-travel` | How far things travel |
| `--bm-hover` | How much hover lifts |
| `--bm-stagger` | The delay between items |

It derives `--bm-ease-out`, `--bm-ease-spring`, and `--bm-duration-fast`, `--bm-duration-base` and `--bm-duration-slow` from them. Tailwind's `duration-*` and `ease-*` classes follow, and animated components import the same values from `@ballmac/motion-presets`, so CSS and Motion animations share one rhythm.

See [Motion settings](https://ui.ballmac.com/docs/reference/motion-settings) for the ranges, and [how motion works](https://ui.ballmac.com/docs/concepts/motion).

---

<!-- Source: https://ui.ballmac.com/docs/reference/motion-settings -->

# Motion settings

The six motion settings with their variables, ranges and defaults, and the five ready-made motion feels.

A theme sets how a product moves with six settings. Each is a CSS variable on `:root`. The defaults reproduce Ballmac UI exactly as it was, so adding the theme changes nothing until you change a setting. For what they do together, see [How motion works](https://ui.ballmac.com/docs/concepts/motion).

## Settings [#settings]

| Variable | Range | Default | What it does |
| --- | --- | --- | --- |
| `--bm-speed` | 0.5 to 2.5 | 1 | Playback speed. 1 is the default, 2 halves every duration, 0.5 doubles them. |
| `--bm-bounce` | 0 to 1 | 0.25 | Springiness, 0 (settles with no overshoot) to 1 (clearly bouncy). |
| `--bm-travel` | 0 to 2 | 1 | How far things travel when they enter or move, as a multiple of the default distance. |
| `--bm-hover` | 0 to 2 | 1 | How strongly controls answer a hover or press: lift, press depth. |
| `--bm-stagger` | 0 to 2 | 1 | The delay between items in a staggered list, as a multiple of the default step. |
| `--bm-ambient` | 1 or 0 | 1 | Decorative loops (shimmer, floating, pulsing). Off pauses them. |

Reduced motion has its own setting, `reduce`: "system" follows the visitor's setting. "always" ships the reduced values to everyone.

## Ready-made feels [#ready-made-feels]

Each is a registry theme that changes movement only and leaves your colors alone. Installing another replaces it.

```bash
npx shadcn@latest add @ballmac/motion-calm
```

### Native [#native]

The Ballmac default: short fades, a soft settle on spatial moves and a light lift on hover. Reads as the platform, not as an effect. Install: `@ballmac/motion-native`.

| Speed | Bounce | Travel | Hover | Stagger | Loops |
| --- | --- | --- | --- | --- | --- |
| 1 | 0.25 | 1 | 1 | 1 | On |

### Calm [#calm]

Longer, softer transitions with no bounce and short travel. Suits products people use for a long time, like editors, health and finance. Install: `@ballmac/motion-calm`.

| Speed | Bounce | Travel | Hover | Stagger | Loops |
| --- | --- | --- | --- | --- | --- |
| 0.75 | 0 | 0.7 | 0.5 | 1.4 | On |

### Snappy [#snappy]

Short durations, tight stagger and very little bounce. Suits dense tools and dashboards where speed is the feature. Install: `@ballmac/motion-snappy`.

| Speed | Bounce | Travel | Hover | Stagger | Loops |
| --- | --- | --- | --- | --- | --- |
| 1.6 | 0.05 | 0.7 | 0.8 | 0.5 | Off |

### Playful [#playful]

Clear overshoot, longer travel and a pronounced hover lift. Suits consumer apps, creative tools and marketing pages. Install: `@ballmac/motion-playful`.

| Speed | Bounce | Travel | Hover | Stagger | Loops |
| --- | --- | --- | --- | --- | --- |
| 1.1 | 0.8 | 1.4 | 1.5 | 1.2 | On |

### Still [#still]

Ships the reduced-motion values to every visitor: no travel, no lift, no loops, no bounce. Opacity changes stay so state remains visible. Install: `@ballmac/motion-still`.

| Speed | Bounce | Travel | Hover | Stagger | Loops |
| --- | --- | --- | --- | --- | --- |
| 2 | 0 | 0 | 0 | 0.4 | Off |

---

<!-- Source: https://ui.ballmac.com/docs/reference/platform-feels -->

# Platform feels

What the web, macOS, iOS and Windows feels set: control size, corners, focus ring and window controls.

A feel is everything about a product except its colors. This page lists what each of the four feels sets. For how they work and their limits, see [How platform feels work](https://ui.ballmac.com/docs/concepts/platform-feels). To use one, see [Pick a platform feel](https://ui.ballmac.com/docs/guides/platform-feels).

## What each feel sets [#what-each-feel-sets]

| Feel | Control height | Corners | Focus ring | Window controls |
| --- | --- | --- | --- | --- |
| **Web**<br />Neutral, as it is today | 36 px | 10 px | 3px, 50% | None |
| **macOS**<br />Compact, soft shadows, frosted menus | 28 px | 6 px | 3px, 50% | Left (traffic lights) |
| **iOS**<br />Big targets, rounded, dims on press | 44 px | 12 px | 3px, 40% | None |
| **Windows**<br />Square-ish, bordered, crisp 2 px focus | 32 px | 4 px | 2px, 100% | Right (caption buttons) |

## The four feels [#the-four-feels]

### Web [#web]

Ballmac UI's own feel and the default: shadcn proportions, a soft focus halo and Tailwind's shadows. Nothing changes until you pick another feel. Install: `@ballmac/feel-web`.

### macOS [#macos]

Compact 28 px controls, 13 px text, a soft focus halo, hairline-edged shadows and frosted popovers and sidebars. For desktop apps built with Tauri or Electron that should sit comfortably next to native Mac apps. Install: `@ballmac/feel-macos`.

### iOS [#ios]

44 px touch targets, 17 px text, large corners, flat grouped surfaces and a springier motion. Controls dim when pressed and nothing reacts to hover. For mobile web apps and webviews. Install: `@ballmac/feel-ios`.

### Windows [#windows]

4 px corners, 32 px controls, Fluent-style layered shadows, a 2 px solid focus outline and a fast, no-bounce motion. For desktop apps that should sit comfortably next to native Windows apps. Install: `@ballmac/feel-windows`.

---

<!-- Source: https://ui.ballmac.com/docs/reference/building-blocks -->

# Building blocks

Props of StateView, AppWindow, Media and the other building blocks, and the signatures of the hooks and helpers, read from their TypeScript types.

The components and helpers the guides refer to, with their props and signatures read from their TypeScript types. The page of every component and block lists its own props too.

## Components [#components]

### State View [#state-view]

Install: `@ballmac/state-view`. Page: [State View](https://ui.ballmac.com/components/state-view).

#### `<StateView>` [#stateview-props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `state` (required) | `BlockState` |  | What to show. `submitting` and `success` belong to forms and render the children. |
| `loading` | `React.ReactNode` |  | Shown while loading. Make it the same shape as the ready content so nothing jumps. A plain set of lines is used if you leave it out. |
| `empty` | `EmptyConfig \| React.ReactNode` |  | Copy and action for the empty state, or your own node. |
| `error` | `ErrorConfig \| React.ReactNode` |  | Copy for the error state, or your own node. |
| `onRetry` | `() => void` |  | Called by the retry button. Without it the button is not drawn. |
| `label` | `string` |  | Text announced to assistive technology while loading. |
| `bare` | `boolean` | `false` | Drop the empty and error panels' border and tint, for use inside a frame that already has them. |
| `children` | `React.ReactNode` |  | The ready content. |

#### `<State>` [#state-props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `state` | `BlockState` |  | What the block shows. Defaults to `ready`. |
| `onRetry` | `() => void` |  | Called by the retry button in the error state. Without it the button is not drawn. |
| `emptyState` | `EmptyConfig` |  | Copy and action for the empty state. |
| `errorState` | `ErrorConfig` |  | Copy for the error state. |

Also accepts the standard attributes of its root element.

### App Window [#app-window]

Install: `@ballmac/app-window`. Page: [App Window](https://ui.ballmac.com/components/app-window).

#### `<AppWindow>` [#appwindow-props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `feel` | `FeelId` |  | Which platform's chrome to draw. Defaults to the page's feel (`data-feel`), which Feel Auto sets from the device. |
| `active` | `boolean` |  | Whether the window is the focused one. Defaults to following the real window focus. |
| `maximized` | `boolean` | `false` | The window fills the screen: corners square off and the Windows button shows "restore". |
| `onClose` | `() => void` |  | Called by the close button. Without a handler the button is drawn but not focusable. |
| `onMinimize` | `() => void` |  | Called by the minimize button. |
| `onMaximize` | `() => void` |  | Called by the maximize (Windows) or zoom (macOS) button, and when the title bar is double-clicked. |
| `onFullscreen` | `() => void` |  | Called by the macOS green button; hold Option to call onMaximize instead. Falls back to onMaximize. |

#### `<AppWindowControls>` [#appwindowcontrols-props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `feel` | `FeelId` |  |  |

#### `<AppWindowTitleBar>` [#appwindowtitlebar-props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `React.ReactNode` |  | The window title. Centered on macOS and iOS, at the start beside the icon on Windows. |
| `icon` | `React.ReactNode` |  | A small icon before the title (Windows). |
| `leading` | `React.ReactNode` |  | Items after the controls and title, at the start (a back button, a sidebar toggle). |
| `controls` | `boolean` | `true` | Draw the window controls. Turn off when something else draws them. |

Also accepts the standard attributes of its root element.

### Media [#media]

Install: `@ballmac/media`. Page: [Media](https://ui.ballmac.com/components/media).

#### `<Media>` [#media-props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `media` | `MediaSource \| null` |  | The picture, or your own element. Leave it out to show `fallback`. |
| `alt` | `string` |  | Describes a picture passed as a plain URL. Required for informative images; use "" for decoration. |
| `fallback` | `React.ReactNode` |  | What shows when there is no `media` or the file fails to load. Usually the block's built-in artwork. |
| `aspect` | `MediaAspect` | `"auto"` | Reserves the box before the image loads, so the page does not jump. `auto` keeps the image's own proportions. |
| `fit` | `"cover" \| "contain" \| "fill"` | `"cover"` | `cover` fills and crops, `contain` shows all of it, `fill` stretches. |
| `priority` | `boolean` | `false` | Above the fold: load now and with high priority instead of lazily. |
| `fill` | `boolean` |  | Fill a parent that has its own size (an absolutely positioned panel) instead of reserving space by aspect ratio. |
| `frame` | `boolean` | `false` | A card frame (rounded corners, border, soft shadow) around a real image or element. The built-in artwork keeps its own. |
| `onImageError` | `() => void` |  | Called when the file fails to load (the fallback is shown). |

Also accepts the standard attributes of its root element.

### Match Highlight [#match-highlight]

Install: `@ballmac/match-highlight`. Page: [Match Highlight](https://ui.ballmac.com/components/match-highlight).

#### `<MatchHighlight>` [#matchhighlight-props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` |  | The text to show. |
| `query` | `string \| string[]` | `""` | What was searched for: a string (split into words) or a list of words. Every occurrence of any word is marked. |

Also accepts the standard attributes of its root element.

### Cover Art [#cover-art]

Install: `@ballmac/cover-art`. Page: [Cover Art](https://ui.ballmac.com/components/cover-art).

#### `<CoverArt>` [#coverart-props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `seed` (required) | `string \| number` |  | Anything stable: an id, a name, a slug. The same seed always draws the same picture. |
| `variant` | `CoverArtVariant \| "auto"` | `"auto"` | Which picture to draw. Leave it out (or "auto") to let the seed choose among the five. |
| `grid` | `boolean` | `false` | Lay a faint grid over the picture. |

Also accepts the standard attributes of its root element.

## Helpers [#helpers]

### use-resource [#use-resource]

Turns a fetch into the state a block takes, and maps TanStack Query or SWR results onto it. Install: `@ballmac/use-resource`.

```ts
isEmptyValue(value: unknown): boolean
```

Empty arrays, empty objects with no keys, empty strings, null and undefined count as empty.

```ts
stateOf<T>(query: QueryLike<T>, isEmpty: (data: T | undefined) => boolean = isEmptyValue): BlockState
```

Maps the result of TanStack Query, SWR or your own fetch onto a block state:

  const query = useQuery(...)
  \<Mail1 state=\{stateOf(query)\} messages=\{query.data\} />

```ts
useResource<T>(fetcher: (signal: AbortSignal) => Promise<T>, options: ResourceOptions<T> = {}): Resource<T>
```

A small fetch state machine: it starts in `loading`, then becomes `ready`, `empty` or `error`, and `retry()` goes round again.
A request that is replaced or unmounted is aborted, and a slow older answer can never overwrite a newer one.

  const mail = useResource((signal) => fetch("/api/mail", \{ signal \}).then((r) => r.json()))
  \<Mail1 state=\{mail.state\} messages=\{mail.data\} onRetry=\{mail.retry\} />

```ts
formPhase(state: BlockState | undefined): FormPhase | undefined
```

```ts
isBusy(state: BlockState)
```

Whether something is in flight, so a region can set `aria-busy`.

```ts
type BlockState = "ready" | "loading" | "empty" | "error" | "submitting" | "success"
```

What a block is showing. `ready` is the normal view. Forms add `submitting` (the request is in flight) and `success`
(it went through); data views add `loading`, `empty` and `error`.

```ts
type FormStateProps = { /** `submitting` shows the request in flight, `success` the confirmation, `error` the failure. Leave out to let the form decide. */ state?: BlockState /** Copy for the error state. */ errorState?: { title?: string; description?: string } }
```

The props every form block takes. Leave `state` out and the form runs by itself; pass it and you drive the form.

```ts
type FormPhase = "idle" | "sending" | "error" | "done"
```

Where a form is in its life. Forms keep this themselves; `formPhase` turns the public `state` prop into it, or returns
undefined when the prop is not given so the form's own value is used.

```ts
type Resource = { state: BlockState data: T | undefined error: unknown /** Fetches again, showing the loading state. */ retry: () => void /** Replaces the data you have, for example after an optimistic edit. Keeps `ready` or `empty` in step. */ setData: (next: T) => void }
```

### direction [#direction]

The text direction provider and hook for right-to-left. Install: `@ballmac/direction`.

```ts
useDirection(dir?: TextDirection): TextDirection
```

The reading direction for code that has to know it (arrow-key order, pointer maths, which way a chevron faces).
An explicit `dir` wins, then a surrounding `<Direction>` or Radix `DirectionProvider`, then `<html dir>`.
Server render and the first client render say "ltr", so markup matches, and it updates right after hydration.

```ts
DirectionProvider({ dir, children }: { dir: TextDirection; children: React.ReactNode })
```

Sets the direction for a part of the page and tells every Radix-based component inside it, so keyboard arrows,
sliders, menus and tabs follow it. For the whole app, put it in the root layout next to `<html dir>`.

```ts
Dir({ dir, children, ...props }: Omit<React.ComponentProps<"div">, "dir"> & { dir: TextDirection })
```

A box with its own direction (`display: contents`, so it adds no layout), for previews and mixed-language regions.

```ts
type TextDirection = "ltr" | "rtl"
```

### i18n [#i18n]

The translation provider and hooks. Install: `@ballmac/i18n`.

```ts
I18nProvider({ messages = {}, locale, children }: { messages?: Messages; locale?: string; children: React.ReactNode })
```

Translates every built-in string (aria-labels, placeholders, hidden text, default button labels) and sets the locale that
dates, numbers and sorting use. Put it once near the root. Anything you leave out stays in English.

  \<I18nProvider locale="ar" messages=\{\{ "pagination.nextPage": "الصفحة التالية" \}\}>

```ts
useLocale(fallback = "en-US"): string
```

The locale for Intl formatting: the provider's, else `<html lang>`, else `fallback`.
Server render and the first client render use the provider or the fallback, so markup matches.

```ts
useMessages(): Msg
```

Returns `msg(key, english, values?)`. The text comes from the provider's messages, else the English default.
`{name}` placeholders are filled from `values`. With a numeric `count`, a plural form is used: the dictionary's
`key_one`, `key_few`, `key_many` or `key_other` (the CLDR categories of the locale), else the English forms you pass as
`{ one: "{count} item", other: "{count} items" }`.

```ts
defineMessage(key: string, text: string): Message
```

```ts
type Messages = Record<string, string>
```

A flat dictionary: message key to translated text. Keys and English defaults are listed at /i18n/en.json.

```ts
type Fallback = string | ({ [K in Intl.LDMLPluralRule]?: string } & { other: string })
```

English text, or its plural forms keyed by CLDR category (`other` is required).

```ts
type Message = { key: string; text: string }
```

A string declared at module level (a status label in a lookup table); render it with `msg.of(message)`.

```ts
type Msg = { (key: string, fallback: Fallback, values?: Values): string /** Translate a message declared with defineMessage. */ of: (message: Message, values?: Values) => string /** Like `msg`, but `{name}` placeholders are filled with React nodes: msg.rich("k", "Press {key} to open", { key: <Kbd>K</Kbd> }). */ rich: (key: string, fallback: string, nodes: Record<string, React.ReactNode>) => React.ReactNode }
```

### feel [#feel]

Read and set the platform feel. Install: `@ballmac/feel`.

```ts
detectFeel(nav: Pick<Navigator, "userAgent" | "platform" | "maxTouchPoints"> = navigator): FeelId
```

The feel that matches the device the page is running on. Works in a browser, a Tauri window and an Electron window, because
all of them report the platform in the user agent. An iPad that asks for the desktop site still counts as iOS.

```ts
readFeel(el: Element | null = typeof document === "undefined" ? null : document.documentElement): FeelId
```

The feel set on the page, or "web" when none is.

```ts
storedFeel(): FeelId | null
```

The choice remembered for this browser, if any.

```ts
setFeel(feel: FeelId | "auto")
```

Sets the feel for the whole page. `"auto"` goes back to the device's own feel and forgets the override. The choice is
remembered in localStorage when that is available.

```ts
useFeel(): { feel: FeelId; setFeel: typeof setFeel }
```

The feel the page is set to, live. Server render and the first client render say "web", then the real value is read, so
markup always matches. Pass an element to read a scope inside the page.

```ts
isFeel(value: unknown): value is FeelId
```

```ts
type FeelId = "web" | "macos" | "ios" | "windows"
```

### return-focus [#return-focus]

Puts keyboard focus back where it was when a dialog closes. Install: `@ballmac/return-focus`.

```ts
useReturnFocus(open: boolean)
```

Radix returns focus to a dialog's trigger when it closes, so a dialog opened from state (a confirmation after pressing Delete, a
lightbox opened from a tile) has no trigger and focus falls back to the top of the page. This remembers the element that had focus
when `open` became true and returns it there. Spread the result onto DialogContent or AlertDialogContent.

### shortcut [#shortcut]

Formats and records keyboard shortcuts in each platform's notation. Install: `@ballmac/shortcut`.

```ts
formatShortcut(shortcut: string, feel: FeelId)
```

Writes a shortcut the way the platform does: ⇧⌘Space on a Mac (modifiers in Apple's order, no separators), Ctrl+Shift+Space elsewhere.

```ts
normalizeShortcut(shortcut: string, feel: FeelId = "web")
```

The same shortcut in one canonical order, so two spellings of one combination compare equal. On Windows and the web Mod and Ctrl are one key.

```ts
shortcutFromEvent(e: Pick<KeyboardEvent, "key" | "code" | "metaKey" | "ctrlKey" | "altKey" | "shiftKey">, mac: boolean, requireModifier = true): string | null
```

Turns a key press into a shortcut string, or null while only modifiers are down. With `requireModifier` (the default) a plain
letter is refused, so a global shortcut cannot take a key from every other app.

```ts
usesMacKeys(feel: FeelId)
```

Whether the feel writes shortcuts with Apple's symbols.

---

<!-- Source: https://ui.ballmac.com/docs/reference/block-states -->

# Block states

Which free blocks show loading, empty, error, submitting and success states.

118 of the 186 free blocks show states. The rest are static content with nothing to load, submit or fail, and say so on their page. For what the states mean, see [Why every block has states](https://ui.ballmac.com/docs/concepts/states). To drive them, see [Handle loading, empty and error states](https://ui.ballmac.com/docs/guides/states).

- [Access 1: access review](https://ui.ballmac.com/blocks/access-1): loading, empty, error
- [Agent 1: live agent run](https://ui.ballmac.com/blocks/agent-1): loading, empty, error
- [Agent 10: console with handoff](https://ui.ballmac.com/blocks/agent-10): loading, empty, error
- [Agent 2: approvals inbox](https://ui.ballmac.com/blocks/agent-2): loading, empty, error
- [Agent 3: runs and traces](https://ui.ballmac.com/blocks/agent-3): loading, empty, error
- [Agent 4: prompt playground with model compare](https://ui.ballmac.com/blocks/agent-4): submitting, error
- [Agent 5: eval results](https://ui.ballmac.com/blocks/agent-5): loading, empty, error
- [Agent 6: assistant memory](https://ui.ballmac.com/blocks/agent-6): loading, empty, error
- [Agent 7: agent builder](https://ui.ballmac.com/blocks/agent-7): loading, submitting, success, error
- [Agent 8: knowledge manager](https://ui.ballmac.com/blocks/agent-8): loading, empty, error
- [Agent 9: autonomy and limits](https://ui.ballmac.com/blocks/agent-9): loading, empty, error
- [AI Chat 1: full chat app pane](https://ui.ballmac.com/blocks/ai-chat-1): loading, empty, error
- [AI Chat 2: conversation with artifact panel](https://ui.ballmac.com/blocks/ai-chat-2): loading, empty, error
- [Alerts 1: alert rules](https://ui.ballmac.com/blocks/alerts-1): loading, empty, error
- [API 1: request explorer](https://ui.ballmac.com/blocks/api-1): loading, error
- [Audit 1: audit log](https://ui.ballmac.com/blocks/audit-1): loading, empty, error
- [Billing 1: plan, usage and invoices](https://ui.ballmac.com/blocks/billing-1): loading, empty, error
- [Billing 2: change plan with proration](https://ui.ballmac.com/blocks/billing-2): loading, empty, error
- [Blog 1: featured post with filterable grid](https://ui.ballmac.com/blocks/blog-1): loading, empty, error
- [Booking 1: class timetable](https://ui.ballmac.com/blocks/booking-1): loading, empty, error
- [Calendar 1: week view with mini month](https://ui.ballmac.com/blocks/calendar-1): loading, empty, error
- [Capacity 1: runway](https://ui.ballmac.com/blocks/capacity-1): loading, empty, error
- [Changelog 1: release notes timeline](https://ui.ballmac.com/blocks/changelog-1): loading, empty, error
- [Charts 1: analytics overview](https://ui.ballmac.com/blocks/charts-1): loading, empty, error
- [Charts 2: revenue report](https://ui.ballmac.com/blocks/charts-2): loading, empty, error
- [Charts 3: small multiples](https://ui.ballmac.com/blocks/charts-3): loading, empty, error
- [Charts 4: scorecard](https://ui.ballmac.com/blocks/charts-4): loading, empty, error
- [Chat 1: team chat with threads](https://ui.ballmac.com/blocks/chat-1): loading, empty, error
- [Claims 1: claim tracker](https://ui.ballmac.com/blocks/claims-1): loading, empty, error
- [Clinic 1: day schedule](https://ui.ballmac.com/blocks/clinic-1): loading, empty, error
- [Comments 1: threaded discussion](https://ui.ballmac.com/blocks/comments-1): loading, empty, error
- [Contact 1: form with contact methods](https://ui.ballmac.com/blocks/contact-1): submitting, success, error
- [Cost 1: cloud cost explorer](https://ui.ballmac.com/blocks/cost-1): loading, empty, error
- [Course 1: curriculum and progress](https://ui.ballmac.com/blocks/course-1): loading, empty, error
- [CRM 1: contact and deal record](https://ui.ballmac.com/blocks/crm-1): loading, empty, error
- [Cron 1: scheduled jobs](https://ui.ballmac.com/blocks/cron-1): loading, empty, error
- [Dashboard 1: sales overview](https://ui.ballmac.com/blocks/dashboard-1): loading, empty, error
- [Dashboard 2: traffic analytics](https://ui.ballmac.com/blocks/dashboard-2): loading, empty, error
- [Deploys 1: deploy history](https://ui.ballmac.com/blocks/deploys-1): loading, empty, error
- [Desktop 2: menu bar and tray panel](https://ui.ballmac.com/blocks/desktop-2): loading, empty, error
- [Desktop 3: software update](https://ui.ballmac.com/blocks/desktop-3): loading, submitting, success, empty, error
- [Desktop 4: licence activation](https://ui.ballmac.com/blocks/desktop-4): submitting, success, error
- [Desktop 5: permissions setup](https://ui.ballmac.com/blocks/desktop-5): loading, error
- [Desktop 7: welcome window](https://ui.ballmac.com/blocks/desktop-7): loading, empty, error
- [Dev 1: developers page](https://ui.ballmac.com/blocks/dev-1): loading, empty, error
- [Directory 1: people directory](https://ui.ballmac.com/blocks/directory-1): loading, empty, error
- [Dispatch 1: field service board](https://ui.ballmac.com/blocks/dispatch-1): loading, empty, error
- [Donations 1: donation page](https://ui.ballmac.com/blocks/donations-1): loading, empty, error
- [Editor 1: Markdown with live preview](https://ui.ballmac.com/blocks/editor-1): loading, error
- [Expenses 1: expense report](https://ui.ballmac.com/blocks/expenses-1): loading, empty, error
- [Experiments 1: A/B test results](https://ui.ballmac.com/blocks/experiments-1): loading, empty, error
- [Flags 1: feature flags manager](https://ui.ballmac.com/blocks/flags-1): loading, empty, error
- [Fleet 1: vehicle fleet](https://ui.ballmac.com/blocks/fleet-1): loading, empty, error
- [Floorplan 1: restaurant floor](https://ui.ballmac.com/blocks/floorplan-1): loading, empty, error
- [Forgot Password 1: request, resend and reset](https://ui.ballmac.com/blocks/forgot-password-1): submitting, success, error
- [Funnel 1: conversion funnel](https://ui.ballmac.com/blocks/funnel-1): loading, empty, error
- [Gallery 1: filterable gallery with lightbox](https://ui.ballmac.com/blocks/gallery-1): loading, empty, error
- [Gantt 1: project timeline](https://ui.ballmac.com/blocks/gantt-1): loading, empty, error
- [Goals 1: objectives and key results](https://ui.ballmac.com/blocks/goals-1): loading, empty, error
- [Gradebook 1: class grades](https://ui.ballmac.com/blocks/gradebook-1): loading, empty, error
- [Habits 1: streaks and weekly goals](https://ui.ballmac.com/blocks/habits-1): loading, empty, error
- [Help 1: help center home](https://ui.ballmac.com/blocks/help-1): loading, empty, error
- [Import 1: CSV import with column matching](https://ui.ballmac.com/blocks/import-1): loading, error
- [Inbox 1: notifications page](https://ui.ballmac.com/blocks/inbox-1): loading, empty, error
- [Incident 1: incident command](https://ui.ballmac.com/blocks/incident-1): loading, empty, error
- [Inspection 1: checklist inspection](https://ui.ballmac.com/blocks/inspection-1): loading, empty, error
- [Integrations 1: directory](https://ui.ballmac.com/blocks/integrations-1): loading, empty, error
- [Inventory 1: stock levels and reordering](https://ui.ballmac.com/blocks/inventory-1): loading, empty, error
- [Invite 1: accept a team invitation](https://ui.ballmac.com/blocks/invite-1): submitting, success, error, expired
- [Invoice 1: invoice you can pay](https://ui.ballmac.com/blocks/invoice-1): loading, error
- [Kanban 1: project board with drag and keyboard moves](https://ui.ballmac.com/blocks/kanban-1): loading, empty, error
- [Kitchen 1: kitchen display](https://ui.ballmac.com/blocks/kitchen-1): loading, empty, error
- [Leaderboard 1: podium and rankings](https://ui.ballmac.com/blocks/leaderboard-1): loading, empty, error
- [Listing 1: property listing](https://ui.ballmac.com/blocks/listing-1): loading, empty, error
- [Locations 1: finder on a world map](https://ui.ballmac.com/blocks/locations-1): loading, empty, error
- [Login 1: card with SSO and magic link](https://ui.ballmac.com/blocks/login-1): submitting, error
- [Login 2: split screen with testimonial](https://ui.ballmac.com/blocks/login-2): submitting, error
- [Mail 1: three-pane inbox](https://ui.ballmac.com/blocks/mail-1): loading, empty, error
- [Matrix 1: service health by region](https://ui.ballmac.com/blocks/matrix-1): loading, empty, error
- [Matter 1: legal matters](https://ui.ballmac.com/blocks/matter-1): loading, empty, error
- [Newsletter 1: signup with topics and sample issue](https://ui.ballmac.com/blocks/newsletter-1): submitting, success, error
- [Onboarding 1: workspace setup wizard](https://ui.ballmac.com/blocks/onboarding-1): submitting, success, error
- [On-call 1: weekly rota](https://ui.ballmac.com/blocks/oncall-1): loading, empty, error
- [Org chart 1: organization tree](https://ui.ballmac.com/blocks/orgchart-1): loading, empty, error
- [Pipeline 1: deploy pipeline](https://ui.ballmac.com/blocks/pipeline-1): loading, empty, error
- [Poll 1: vote and live results](https://ui.ballmac.com/blocks/poll-1): loading, empty, error
- [Portfolio 1: investments overview](https://ui.ballmac.com/blocks/portfolio-1): loading, empty, error
- [Profile 1: public profile](https://ui.ballmac.com/blocks/profile-1): loading, empty, error
- [Queues 1: job queues](https://ui.ballmac.com/blocks/queues-1): loading, empty, error
- [Rentals 1: rental booking](https://ui.ballmac.com/blocks/rentals-1): loading, empty, error
- [Retention 1: cohort heatmap](https://ui.ballmac.com/blocks/retention-1): loading, empty, error
- [Reviews 1: customer reviews](https://ui.ballmac.com/blocks/reviews-1): loading, empty, error
- [Roster 1: weekly shift planner](https://ui.ballmac.com/blocks/roster-1): loading, empty, error
- [Runbook 1: guided runbook](https://ui.ballmac.com/blocks/runbook-1): loading, empty, error
- [Salon 1: appointment booking](https://ui.ballmac.com/blocks/salon-1): loading, empty, error
- [Search 1: results page](https://ui.ballmac.com/blocks/search-1): loading, empty, error
- [Seat map 1: choose seats](https://ui.ballmac.com/blocks/seatmap-1): loading, empty, error
- [Secrets 1: secrets inventory](https://ui.ballmac.com/blocks/secrets-1): loading, empty, error
- [Settings 1: profile and account](https://ui.ballmac.com/blocks/settings-1): loading, submitting, success, error
- [Settings 2: notification preferences](https://ui.ballmac.com/blocks/settings-2): loading, submitting, success, error
- [Settings 3: team members and roles](https://ui.ballmac.com/blocks/settings-3): loading, empty, error
- [Settings 4: integrations directory](https://ui.ballmac.com/blocks/settings-4): loading, empty, error
- [Settings 5: audit log](https://ui.ballmac.com/blocks/settings-5): loading, empty, error
- [Settings 6: data and privacy](https://ui.ballmac.com/blocks/settings-6): loading, error
- [Signup 1: form with benefits column](https://ui.ballmac.com/blocks/signup-1): submitting, success, error
- [SLO 1: service level objectives](https://ui.ballmac.com/blocks/slo-1): loading, empty, error
- [Status 1: system status page](https://ui.ballmac.com/blocks/status-1): loading, empty, error
- [Survey 1: NPS feedback](https://ui.ballmac.com/blocks/survey-1): submitting, success, error
- [Tasks 1: task list](https://ui.ballmac.com/blocks/tasks-1): loading, empty, error
- [Tickets 1: support ticket](https://ui.ballmac.com/blocks/tickets-1): loading, empty, error
- [Timesheet 1: weekly hours](https://ui.ballmac.com/blocks/timesheet-1): loading, empty, error
- [Trust 1: security and trust center](https://ui.ballmac.com/blocks/trust-1): loading, error
- [Upload 1: upload manager](https://ui.ballmac.com/blocks/upload-1): loading, empty, error
- [Verify 1: one-time code with resend](https://ui.ballmac.com/blocks/verify-1): submitting, success, error
- [Waitlist 1: referral waitlist](https://ui.ballmac.com/blocks/waitlist-1): submitting, success, error
- [Webhooks 1: endpoints and deliveries](https://ui.ballmac.com/blocks/webhooks-1): loading, empty, error
- [Workflow 1: automation builder](https://ui.ballmac.com/blocks/workflow-1): loading, empty, error
- [Work order 1: repair job](https://ui.ballmac.com/blocks/workorder-1): loading, empty, error

A build check keeps this true: a free block that lists no states and gives no reason fails `pnpm check`.

---

<!-- Source: https://ui.ballmac.com/docs/reference/messages -->

# Messages

Every translatable message in Ballmac UI, with its key and English text.

Every built-in label, placeholder and screen reader text is a message with an English default: 773 in all. To translate them, see [Translate Ballmac UI](https://ui.ballmac.com/docs/guides/translate). The same keys are available as JSON at [/i18n/en.json](https://ui.ballmac.com/i18n/en.json).

| Key | English |
| --- | --- |
| `activity-feed.emptyMessage` | No activity yet |
| `agent-graph.ends` | Nothing follows it. |
| `agent-graph.hint` | Select a node to see its connections. The arrow keys follow the edges. |
| `agent-graph.label` | How the agents and tools are connected |
| `agent-graph.node` | \{label\}, \{kind\}\{status\} |
| `agent-graph.receives` | Receives from \{names\}. |
| `agent-graph.sends` | Sends to \{names\}. |
| `agent-graph.starts` | Starts the work. |
| `agent-lanes.handedBy` | Started by \{from\}. |
| `agent-lanes.handoffText` | \{from\} hands over to \{to\} at \{time\} |
| `agent-lanes.handsTo` | Hands over to \{to\}. |
| `agent-lanes.hint` | Select a step to see what happened. Arrow keys move between steps. |
| `agent-lanes.legend` | Step kinds |
| `agent-lanes.now` | Now |
| `agent-lanes.step` | \{label\}, \{lane\}, \{kind\}, \{from\} to \{to\} |
| `agent-plan.progress` | \{title\} progress |
| `agent-plan.retry` | Retry |
| `agent-plan.title` | Plan |
| `agent-roster.all` | All |
| `agent-roster.count` | \{count\} agents shown |
| `agent-roster.filter` | Filter by status |
| `agent-roster.load` | \{name\} load |
| `agent-roster.title` | Agents |
| `ai-chat.label` | Conversation |
| `ai-chat.scrollButtonLabel` | Scroll to latest message |
| `ai-chat.suggestions` | Suggestions |
| `ai-message.label` | Copy message |
| `ai-message.messageActions` | Message actions |
| `api-endpoint.defaultValue` | Default: \{value\} |
| `api-endpoint.parameters` | \{group\} parameters |
| `api-endpoint.requestAndResponses` | Request and responses |
| `api-key-field.copied` | Copied |
| `api-key-field.copyKey` | Copy key |
| `api-key-field.hideKey` | Hide key |
| `api-key-field.label` | API key |
| `api-key-field.regenerateKey` | Regenerate key |
| `api-key-field.showKey` | Show key |
| `app-shell.label` | Sidebar |
| `app-shell.label2` | Details |
| `app-shell.navigation` | Navigation |
| `app-shell.openNavigation` | Open navigation |
| `app-window.close` | Close window |
| `app-window.fullscreen` | Enter full screen |
| `app-window.maximize` | Maximize window |
| `app-window.minimize` | Minimize window |
| `app-window.restore` | Restore window |
| `app-window.zoom` | Zoom window |
| `approval-card.alwaysAllowLabel` | Always allow |
| `approval-card.approveLabel` | Approve |
| `approval-card.deniesItselfIn` | Denies itself in \{seconds\}s |
| `approval-card.denyLabel` | Deny |
| `approval-card.previewLabel` | Exactly what will run |
| `artifact-panel.close` | Close |
| `artifact-panel.closeArtifact` | Close artifact |
| `artifact-panel.copied` | Copied |
| `artifact-panel.copyCode` | Copy code |
| `artifact-panel.download` | Download |
| `artifact-panel.downloadFile` | Download \{name\} |
| `artifact-panel.file` | file |
| `artifact-panel.nextVersion` | Next version |
| `artifact-panel.previousVersion` | Previous version |
| `artifact-panel.versionOf` | of \{total\} |
| `artifact-panel.versions` | Versions |
| `artifact-panel.view` | View |
| `audio-player.audioPlayer` | Audio player: \{title\} |
| `audio-player.backSeconds` | Back \{back\} seconds |
| `audio-player.chapter` | Chapter: |
| `audio-player.forwardSeconds` | Forward \{forward\} seconds |
| `audio-player.pause` | Pause |
| `audio-player.play` | Play |
| `audio-player.playbackSpeedChangeSpeed` | Playback speed \{rate\}×. Change speed |
| `audio-player.seek` | Seek |
| `autonomy-dial.asks` | Stops to ask about |
| `autonomy-dial.does` | Does on its own |
| `autonomy-dial.label` | How much can the agent do on its own? |
| `avatar-circles.andMore` | and \{extra\} more |
| `avatar.more` | \{count\} more |
| `back-to-top.label` | Back to top |
| `banner.dismiss` | Dismiss \{title\} |
| `billing-card.cancelSubscription` | Cancel subscription |
| `billing-card.changePlan` | Change plan |
| `billing-card.currentPlan` | Current plan |
| `billing-card.downloadInvoice` | Download invoice \{id\} |
| `billing-card.exp` | Exp \{date\} |
| `billing-card.paymentMethod` | Payment method |
| `billing-card.recentInvoices` | Recent invoices |
| `billing-card.seats` | Seats |
| `billing-card.seatsOf` | \{used\} of \{total\} used |
| `billing-card.seatsUsed` | Seats used |
| `billing-card.update` | Update |
| `breadcrumb.label` | Breadcrumb |
| `breadcrumb.morePages` | More pages |
| `browser-frame.secureConnection` | Secure connection |
| `bullet-chart.of` | of \{target\} |
| `calendar-agenda.agenda` | Agenda |
| `calendar-agenda.chooseDate` | Choose date |
| `calendar-agenda.emptyMessage` | No events scheduled |
| `calendar-agenda.nextDay` | Next day |
| `calendar-agenda.previousDay` | Previous day |
| `carousel.chooseSlide` | Choose slide |
| `carousel.goToSlide` | Go to slide \{n\} |
| `carousel.label` | Carousel |
| `carousel.nextSlide` | Next slide |
| `carousel.pauseAutoplay` | Pause autoplay |
| `carousel.previousSlide` | Previous slide |
| `carousel.startAutoplay` | Start autoplay |
| `changelog-feed.all` | All |
| `changelog-feed.filterChanges` | Filter changes |
| `changelog-feed.latest` | Latest |
| `changelog-feed.noReleasesMatchThisFilter` | No releases match this filter. |
| `chart-bar.category` | Category |
| `chart-frame.category` | Category |
| `chart-frame.chart` | Chart |
| `chart-frame.download` | Download CSV |
| `chart-frame.emptyDescription` | The chart appears when there is something to plot. |
| `chart-frame.emptyTitle` | No data yet |
| `chart-frame.errorDescription` | Check your connection and try again. |
| `chart-frame.errorTitle` | The chart could not be loaded |
| `chart-frame.loading` | Loading \{title\} |
| `chart-frame.patterns` | Patterns |
| `chart-frame.patternsHint` | Fill with patterns as well as color |
| `chart-frame.ranking` | \{series\}: \{list\}. |
| `chart-frame.series` | Series |
| `chart-frame.single` | \{series\}: \{value\} in \{at\}. |
| `chart-frame.table` | Table |
| `chart-frame.tableLabel` | \{title\}, data table |
| `chart-frame.trend` | \{series\}: from \{first\} in \{firstAt\} to \{last\} in \{lastAt\}; highest \{max\} in \{maxAt\}, lowest \{min\} in \{minAt\}. |
| `chart-frame.view` | View |
| `chart-pie.category` | Category |
| `chart-pie.share` | Share |
| `chart-pie.totalLabel` | Total |
| `chart-pie.value` | Value |
| `chart-radial.category` | Category |
| `chart-radial.share` | Of \{max\} |
| `chart-radial.summary` | \{name\}: \{value\} of \{max\} (\{share\}). |
| `chart-radial.value` | Value |
| `chart-scatter.group` | Group |
| `chart-scatter.name` | Name |
| `chat-attachment.label` | Attachments |
| `chat-attachment.open` | Open \{name\} |
| `chat-attachment.remove` | Remove \{name\} |
| `chat-attachment.retry` | Retry |
| `chat-attachment.retryUploading` | Retry uploading \{name\} |
| `chat-attachment.uploading` | Uploading \{name\} |
| `chat-attachment.uploadingEllipsis` | Uploading… |
| `chat-attachment.uploadingPercent` | Uploading \{percent\}% |
| `citation-coverage.gapHelp` | This claim has no source. |
| `citation-coverage.legend` | A wavy underline means no source backs the claim. |
| `citation-coverage.noSource` | no source |
| `citation-coverage.none` | Every claim has a source. |
| `citation-coverage.onlyGaps` | Show only claims without a source |
| `citation-coverage.source` | source |
| `citation-coverage.sources` | Sources |
| `citation-coverage.summary` | \{supported\} of \{total\} claims have a source |
| `citation-coverage.title` | Source coverage |
| `citation.nextSource` | Next source |
| `citation.openSource` | Open source |
| `citation.previousSource` | Previous source |
| `citation.sourceOf` | Source \{n\} of \{total\} |
| `code-block.copied` | Copied |
| `code-block.copy` | Copy |
| `code-block.files` | Files |
| `code-block.label` | Copy code |
| `code-block.wrapLines` | Wrap lines |
| `color-picker.choose` | Choose \{label\} |
| `color-picker.hexColor` | Hex color |
| `color-picker.hexValue` | \{label\} hex value |
| `color-picker.label` | Color |
| `combobox.clearSelection` | Clear selection |
| `combobox.emptyText` | No results found. |
| `combobox.placeholder` | Select an option |
| `combobox.searchPlaceholder` | Search |
| `command-bar.all` | All |
| `command-bar.back` | Back |
| `command-bar.close` | Close |
| `command-bar.currentPage` | Current page |
| `command-bar.emptyText` | No results found. |
| `command-bar.esc` | Esc |
| `command-bar.navigate` | Navigate |
| `command-bar.placeholder` | Search commands… |
| `command-bar.select` | Select |
| `command-bar.title` | Command bar |
| `command.description` | Type a command or search. Use the arrow keys to move and Enter to run. |
| `command.label` | Command menu |
| `command.title` | Command menu |
| `comparison-table.available` | Available |
| `comparison-table.feature` | Feature |
| `comparison-table.notSpecified` | Not specified |
| `comparison-table.unavailable` | Unavailable |
| `contribution-graph.less` | Less |
| `contribution-graph.more` | More |
| `contribution-graph.summary` | \{label\}: \{total\} total across 0 days |
| `control-center.label` | Control Center |
| `control-center.nextTrack` | Next track |
| `control-center.pause` | Pause |
| `control-center.play` | Play |
| `control-center.previousTrack` | Previous track |
| `cookie-consent.acceptAll` | Accept all |
| `cookie-consent.alwaysOn` | Always on |
| `cookie-consent.back` | Back |
| `cookie-consent.cookiePolicy` | Cookie policy |
| `cookie-consent.customize` | Customize |
| `cookie-consent.description` | We use cookies to make the site work, understand how it is used and, with your permission, personalize content. |
| `cookie-consent.rejectNonEssential` | Reject non-essential |
| `cookie-consent.savePreferences` | Save preferences |
| `cookie-consent.title` | We value your privacy |
| `copy-button.ariaLabel` | Copy to clipboard |
| `copy-button.copiedLabel` | Copied |
| `countdown.complete` | \{label\} complete |
| `countdown.label` | Time remaining |
| `data-grid.editCell` | Edit \{column\}, row \{n\} |
| `data-grid.loading` | Loading rows |
| `data-grid.no` | No |
| `data-grid.noMatches` | No rows match “\{search\}”. |
| `data-grid.noRows` | No rows. |
| `data-grid.notDate` | Enter a date as year-month-day. |
| `data-grid.notNumber` | Enter a number. |
| `data-grid.notOption` | Choose one of the options. |
| `data-grid.pastedSome` | Pasted \{count\} cells. Some were refused: \{problem\} |
| `data-grid.resize` | Resize \{column\} |
| `data-grid.rowsOfTotal` | \{n\} of \{total\} rows |
| `data-grid.selectAll` | Select all rows |
| `data-grid.selectRow` | Select row \{n\} |
| `data-grid.selectedCount` | \{n\} selected |
| `data-grid.sortHint` | Sort by \{column\}. Hold Shift to add a second sort. |
| `data-grid.yes` | Yes |
| `data-table.emptyMessage` | No results. |
| `data-table.nextPage` | Next page |
| `data-table.pageOf` | Page \{page\} of \{total\} |
| `data-table.previousPage` | Previous page |
| `data-table.search` | Search \{label\} |
| `data-table.searchPlaceholder` | Search |
| `data-table.selectAllRows` | Select all rows on this page |
| `data-table.selectRow` | Select row \{n\} |
| `data-table.showColumns` | Show columns |
| `date-picker.clearDate` | Clear date |
| `date-picker.placeholder` | Pick a date |
| `date-range-picker.from` | From |
| `date-range-picker.to` | To |
| `desktop-icons.label` | Desktop |
| `dialog.closeLabel` | Close |
| `diff-viewer.diff` | \{label\} diff |
| `diff-viewer.summary` | \{added\} added · \{removed\} removed |
| `dotted-map.label` | World map |
| `emoji-picker.clear` | Clear search |
| `emoji-picker.errorDescription` | Check your connection and try again. |
| `emoji-picker.errorTitle` | Could not load the emoji |
| `emoji-picker.groups` | Emoji groups |
| `emoji-picker.label` | Emoji picker |
| `emoji-picker.loading` | Loading emoji |
| `emoji-picker.none` | No emoji found for “\{query\}” |
| `emoji-picker.prompt` | Pick an emoji |
| `emoji-picker.recent` | Recently used |
| `emoji-picker.results` | Search results |
| `emoji-picker.search` | Search emoji |
| `emoji-picker.selected` | Selected \{name\} |
| `emoji-picker.skinTone` | Skin tone |
| `emoji-picker.tone` | Skin tone \{n\} |
| `emoji-picker.toneDefault` | Default skin tone |
| `env-editor.addVariable` | Add variable |
| `env-editor.cancel` | Cancel |
| `env-editor.hideValueOf` | Hide value of \{name\} |
| `env-editor.import` | Import |
| `env-editor.key` | KEY |
| `env-editor.nameOfVariable` | Name of variable \{n\} |
| `env-editor.noVariablesYet` | No variables yet |
| `env-editor.pasteAEnvFile` | Paste a .env file |
| `env-editor.pasteEnv` | Paste .env |
| `env-editor.remove` | Remove \{name\} |
| `env-editor.showValueOf` | Show value of \{name\} |
| `env-editor.tipPasteSeveralLinesInto` | Tip: paste several lines into any name field to import them. |
| `env-editor.title` | Environment variables |
| `env-editor.toFix` | \{count\} to fix |
| `env-editor.value` | value |
| `env-editor.valueOf` | Value of \{name\} |
| `env-editor.variableN` | variable \{n\} |
| `escalation-card.agent` | Agent |
| `escalation-card.failed` | That did not go through. Nothing was changed. |
| `escalation-card.feels` | Seems |
| `escalation-card.messages` | Last messages |
| `escalation-card.reply` | Suggested reply, edit before sending |
| `escalation-card.send` | Send reply |
| `escalation-card.sent` | Reply sent to \{customer\}. |
| `escalation-card.snooze` | Snooze |
| `escalation-card.snoozed` | Snoozed. It will come back if the customer writes again. |
| `escalation-card.summary` | What the agent understood |
| `escalation-card.take` | Take over |
| `escalation-card.taken` | You have taken over from the agent. |
| `escalation-card.waiting` | Waiting \{time\} |
| `escalation-card.why` | Why a person is needed: |
| `event-calendar.add` | Add event |
| `event-calendar.allDay` | All day |
| `event-calendar.describeAllDay` | \{title\}, \{date\}, all day\{where\} |
| `event-calendar.describeAllDayRange` | \{title\}, \{from\} to \{to\}, all day\{where\} |
| `event-calendar.describeTimed` | \{title\}, \{date\}, \{from\} to \{to\}\{where\} |
| `event-calendar.describeTimedRange` | \{title\}, \{from\} to \{to\}\{where\} |
| `event-calendar.emptyDescription` | Events you add will be listed here by day. |
| `event-calendar.emptyTitle` | No events this month |
| `event-calendar.eventsOn` | Events on \{date\} |
| `event-calendar.loading` | Loading the calendar |
| `event-calendar.more` | +\{count\} more |
| `event-calendar.moreOn` | \{count\} more events on \{date\} |
| `event-calendar.movedTimed` | Moved \{title\} to \{date\}, \{from\} to \{to\}. |
| `event-calendar.movedTo` | Moved \{title\} to \{date\}. |
| `event-calendar.openDay` | Open \{date\} |
| `event-calendar.today` | Today |
| `event-calendar.viewSwitch` | Calendar view |
| `feedback-widget.tellUsMore` | Tell us more |
| `feedback-widget.thankYou` | Thank you |
| `feedback-widget.topic` | Topic |
| `feedback-widget.triggerLabel` | Feedback |
| `feedback-widget.whatWorkedWellOrWhat` | What worked well, or what could be better? |
| `feedback-widget.yourFeedbackHelpsUsImprove` | Your feedback helps us improve. |
| `file-browser.clear` | Clear |
| `file-browser.delete` | Delete |
| `file-browser.download` | Download |
| `file-browser.files` | Files |
| `file-browser.folder` | folder |
| `file-browser.folderPath` | Folder path |
| `file-browser.rootLabel` | All files |
| `file-browser.rowLabel` | \{name\}, \{detail\} |
| `file-browser.searchFiles` | Search files |
| `file-browser.select` | Select \{name\} |
| `file-browser.selectAllFiles` | Select all files |
| `file-browser.selectedCount` | \{count\} selected |
| `file-browser.selectionActions` | Selection actions |
| `file-browser.view` | View |
| `file-dropzone.choose` | Choose |
| `file-dropzone.dropFilesHereOrBrowse` | Drop files here or browse |
| `file-dropzone.filesToUpload` | Files to upload |
| `file-dropzone.maxSize` | \{size\} MB each |
| `file-dropzone.remove` | Remove \{name\} |
| `file-dropzone.sizeKb` | \{size\} KB |
| `file-tree.label` | Files |
| `finder-window.asIcons` | as Icons |
| `finder-window.asList` | as List |
| `finder-window.back` | Back |
| `finder-window.dateModified` | Date Modified |
| `finder-window.document` | document |
| `finder-window.forward` | Forward |
| `finder-window.itemLabel` | \{name\}, \{kind\} |
| `finder-window.name` | Name |
| `finder-window.path` | Path |
| `finder-window.search` | Search |
| `finder-window.searchThisFolder` | Search this folder |
| `finder-window.size` | Size |
| `finder-window.view` | View |
| `floating-nav.label` | Primary |
| `flow-sankey.from` | From |
| `flow-sankey.hint` | Select a step to follow its flows. |
| `flow-sankey.inout` | \{inV\} in · \{outV\} out |
| `flow-sankey.label` | Flows between steps |
| `flow-sankey.node` | \{label\}: \{inV\} in, \{outV\} out |
| `flow-sankey.table` | Flows, from one step to the next |
| `flow-sankey.to` | To |
| `flow-sankey.value` | Amount |
| `git-graph.label` | Commit history |
| `git-graph.merge` | Merge |
| `globe.label` | Rotating globe |
| `guardrail-feed.all` | All |
| `guardrail-feed.count` | \{count\} checks shown |
| `guardrail-feed.empty` | Nothing here yet. |
| `guardrail-feed.filter` | Filter by action |
| `guardrail-feed.title` | Guardrail activity |
| `handoff-card.context` | Context passed |
| `handoff-card.handsOver` | hands over to |
| `handoff-card.questions` | Open questions |
| `handoff-card.rules` | Rules to keep |
| `heat-grid.cell` | \{row\}, \{column\}: \{value\} |
| `heat-grid.label` | Values |
| `heat-grid.legend` | Legend |
| `heat-grid.none` | No data |
| `icon-cloud.label` | Items |
| `image-cropper.degrees` | \{degrees\} degrees |
| `image-cropper.errorTitle` | The picture could not be loaded |
| `image-cropper.help` | Arrow keys move the picture. Plus and minus zoom. The square brackets turn it a quarter. Zero resets. |
| `image-cropper.label` | Crop the picture |
| `image-cropper.loading` | Loading the picture |
| `image-cropper.reset` | Reset |
| `image-cropper.resetDone` | Reset |
| `image-cropper.retry` | Try again |
| `image-cropper.role` | image cropper |
| `image-cropper.rotateLeft` | Turn left |
| `image-cropper.rotateRight` | Turn right |
| `image-cropper.rotationSpoken` | Rotated to \{degrees\} degrees |
| `image-cropper.straighten` | Straighten |
| `image-cropper.zoom` | Zoom |
| `image-cropper.zoomSpoken` | Zoom \{percent\}% |
| `image-cropper.zoomValue` | \{percent\}% |
| `install-tabs.copied` | Copied |
| `install-tabs.copyCommand` | Copy command |
| `install-tabs.packageManager` | Package manager |
| `invite-members.description` | They will get an email with a link to join. |
| `invite-members.emailAddresses` | Email addresses |
| `invite-members.nameCompanyCom` | name@company.com |
| `invite-members.pending` | Pending (\{count\}) |
| `invite-members.remove` | Remove \{chip\} |
| `invite-members.resendInvitationTo` | Resend invitation to \{email\} |
| `invite-members.revokeInvitationFor` | Revoke invitation for \{email\} |
| `invite-members.role` | Role |
| `invite-members.separateAddressesWithAComma` | Separate addresses with a comma, space or Enter. You can paste a list. |
| `invite-members.title` | Invite teammates |
| `json-viewer.collapse` | Collapse \{name\} |
| `json-viewer.expand` | Expand \{name\} |
| `json-viewer.label` | JSON data |
| `json-viewer.root` | root |
| `k` | Press \{key\} to open |
| `kanban-board.label` | Task board |
| `kanban-board.moveToNextColumn` | Move \{title\} to next column |
| `kanban-board.moveToPreviousColumn` | Move \{title\} to previous column |
| `kanban-board.useAltAndArrowKeys` | \{title\}, \{column\}. Use Alt and arrow keys to move. |
| `keyboard-shortcuts.description` | Work faster without leaving the keyboard. |
| `keyboard-shortcuts.noShortcutMatches` | No shortcut matches “\{query\}”. |
| `keyboard-shortcuts.press` | Press \{key\} anywhere to open this list. |
| `keyboard-shortcuts.searchPlaceholder` | Search shortcuts |
| `keyboard-shortcuts.title` | Keyboard shortcuts |
| `launchpad.label` | Launchpad |
| `launchpad.noAppsMatch` | No apps match “\{query\}”. |
| `launchpad.page` | Page \{n\} |
| `launchpad.pageOf` | Page \{n\} of \{pages\} |
| `launchpad.pages` | Pages |
| `launchpad.search` | Search |
| `launchpad.searchApps` | Search apps |
| `lens.label` | Zoomable image |
| `lens.moveThePointerOverIt` | \{label\}. Move the pointer over it, or use the arrow keys, to magnify. Escape hides the lens. |
| `loading-dots.label` | Loading |
| `lock-screen.enterPassword` | Enter Password |
| `lock-screen.hint` | Touch ID or enter password |
| `lock-screen.lockScreen` | Lock screen |
| `lock-screen.passwordFor` | Password for \{name\} |
| `lock-screen.unlock` | Unlock |
| `log-stream.clear` | Clear |
| `log-stream.emptyText` | Waiting for output… |
| `log-stream.filter` | Filter |
| `log-stream.filterLogLines` | Filter log lines |
| `log-stream.lines` | \{title\}, \{length\} lines |
| `log-stream.pauseStream` | Pause stream |
| `log-stream.resumeStream` | Resume stream |
| `log-stream.showLevels` | Show levels |
| `log-stream.title` | Logs |
| `log-stream.wrapLongLines` | Wrap long lines |
| `mac-window.close` | Close window |
| `mac-window.minimize` | Minimize window |
| `mac-window.zoom` | Zoom window |
| `mega-menu.label` | Main |
| `mega-menu.learnMore` | Learn more |
| `mention-input.noMatches` | No matches for “\{query\}” |
| `mention-input.searching` | Searching |
| `mention-input.suggestions` | Suggestions |
| `model-picker.availableWith` | Available with \{plan\} |
| `model-picker.contextTokens` | \{size\} tokens |
| `model-picker.costHigh` | High |
| `model-picker.costLevel` | \{level\} cost |
| `model-picker.costLow` | Low |
| `model-picker.costMedium` | Medium |
| `model-picker.higherPlan` | a higher plan |
| `model-picker.locked` | Locked |
| `model-picker.modelDetails` | Model details |
| `model-picker.new` | New |
| `model-picker.placeholder` | Select a model |
| `model-picker.searchPlaceholder` | Search models… |
| `multi-select.label` | Select options |
| `multi-select.noMatchingOptions` | No matching options |
| `multi-select.placeholder` | Choose options |
| `multi-select.searchOptions` | Search options |
| `multi-select.searchOptions2` | Search options… |
| `multi-select.selected` | \{count\} selected |
| `multi-select.selectedOf` | \{count\} selected of \{max\} |
| `navbar.label` | Main |
| `navbar.label2` | Menu |
| `navbar.siteNavigation` | Site navigation |
| `notification-center.allNotifications` | All notifications |
| `notification-center.dismiss` | Dismiss "\{title\}" |
| `notification-center.emptyText` | You're all caught up. |
| `notification-center.filterNotifications` | Filter notifications |
| `notification-center.label` | Notifications |
| `notification-center.markAllAsRead` | Mark all as read |
| `notification-center.markAsRead` | Mark "\{title\}" as read |
| `notification-center.unread` | Unread |
| `notification-center.unreadNotifications` | Unread notifications |
| `notification-stack.clearAllNotifications` | Clear all notifications |
| `notification-stack.dismissLabel` | Dismiss |
| `notification-stack.label` | Notifications |
| `notification-stack.showAllNotifications` | Show all \{count\} notifications |
| `notification-stack.showLess` | Show less |
| `number-input.decrease` | Decrease \{label\} |
| `number-input.increase` | Increase \{label\} |
| `number-input.label` | Number |
| `onboarding-checklist.completeMessage` | You're all set. Nice work. |
| `onboarding-checklist.description` | Finish these steps to set up your workspace. |
| `onboarding-checklist.dismissChecklist` | Dismiss checklist |
| `onboarding-checklist.markDone` | \{title\}: mark as done |
| `onboarding-checklist.markNotDone` | \{title\}: mark as not done |
| `onboarding-checklist.reviewTheStepsAgain` | Review the steps again |
| `onboarding-checklist.title` | Get started |
| `package-badge.perWeek` | \{count\}/wk |
| `package-badge.typesIncluded` | Types included |
| `pagination.label` | Pagination |
| `pagination.morePages` | More pages |
| `pagination.nextPage` | Next page |
| `pagination.previousPage` | Previous page |
| `parallel-attempts.added` | \{count\} added |
| `parallel-attempts.choose` | Continue with \{label\} |
| `parallel-attempts.chooseNone` | Continue |
| `parallel-attempts.files` | \{count\} files |
| `parallel-attempts.hint` | Pick a finished attempt to continue with. |
| `parallel-attempts.label` | Attempts |
| `parallel-attempts.picked` | \{label\} is selected. |
| `parallel-attempts.progress` | \{label\} progress |
| `parallel-attempts.recommended` | Suggested |
| `parallel-attempts.removed` | \{count\} removed |
| `password-input.fair` | Fair |
| `password-input.good` | Good |
| `password-input.hidePassword` | Hide password |
| `password-input.label` | Password |
| `password-input.needsWork` | Needs work |
| `password-input.showPassword` | Show password |
| `password-input.strengthHint` | Strength: \{strength\}. Use 8+ characters, mixed case, a number, and a symbol. |
| `password-input.strong` | Strong |
| `password-input.tooShort` | Too short |
| `permission-grid.noAsk` | Runs without asking |
| `permission-grid.permission` | Permission |
| `permission-grid.presets` | Presets |
| `permission-grid.radio` | \{label\}: \{decision\} |
| `permission-grid.summary` | \{allow\} allowed, \{ask\} ask first, \{deny\} denied |
| `permission-grid.title` | What the agent may do |
| `phone-input.countryDialingCode` | Country dialing code |
| `phone-input.label` | Phone number |
| `plan-selector.billingPeriod` | Billing period |
| `plan-selector.perMonth` | / month |
| `plan-selector.save` | Save |
| `popover.closeLabel` | Close popover |
| `progress-steps.label` | Progress |
| `progress-steps.returnTo` | Return to \{label\} |
| `prompt-diff.added` | +\{count\} words |
| `prompt-diff.addedMark` | [added: |
| `prompt-diff.after` | After |
| `prompt-diff.before` | Before |
| `prompt-diff.removed` | −\{count\} words |
| `prompt-diff.removedMark` | [removed: |
| `prompt-diff.restore` | Restore \{label\} |
| `prompt-diff.same` | The two versions are the same. |
| `prompt-diff.split` | Side by side |
| `prompt-diff.unified` | Inline |
| `prompt-diff.view` | View |
| `prompt-input.attachments` | Attachments |
| `prompt-input.label` | Attach files |
| `prompt-input.message` | Message |
| `prompt-input.placeholder` | Send a message… |
| `prompt-input.remove` | Remove \{name\} |
| `prompt-input.stopLabel` | Stop generating |
| `prompt-input.submitLabel` | Send message |
| `qr-code.empty` | Nothing to encode yet |
| `qr-code.label` | QR code for \{value\} |
| `qr-code.tooLong` | This is too long to fit in a QR code. Use a shorter link. |
| `questionnaire.next` | Next |
| `questionnaire.previous` | Previous |
| `questionnaire.skip` | Skip |
| `questionnaire.submit` | Submit |
| `rating.clear` | Clear |
| `rating.label` | Rating |
| `rating.ofStars` | \{number\} of \{count\} stars |
| `resizable.label` | Resize panels |
| `rich-text-editor.charsOf` | \{count\} of \{limit\} characters |
| `rich-text-editor.hint` | Press Escape, then Tab, to leave the editor. |
| `rich-text-editor.linkInvalid` | Enter a web address like example.com. |
| `rich-text-editor.loading` | Loading the editor |
| `rich-text-editor.placeholder` | Write something, or type / for blocks |
| `rich-text-editor.slash.bullets` | Bulleted list |
| `rich-text-editor.slash.bulletsHint` | A simple list of points |
| `rich-text-editor.slash.code` | Code block |
| `rich-text-editor.slash.codeHint` | Monospaced code |
| `rich-text-editor.slash.divider` | Divider |
| `rich-text-editor.slash.dividerHint` | A line between sections |
| `rich-text-editor.slash.h1` | Heading 1 |
| `rich-text-editor.slash.h1Hint` | A large section heading |
| `rich-text-editor.slash.h2` | Heading 2 |
| `rich-text-editor.slash.h2Hint` | A medium heading |
| `rich-text-editor.slash.h3` | Heading 3 |
| `rich-text-editor.slash.h3Hint` | A small heading |
| `rich-text-editor.slash.numbers` | Numbered list |
| `rich-text-editor.slash.numbersHint` | Steps in order |
| `rich-text-editor.slash.quote` | Quote |
| `rich-text-editor.slash.quoteHint` | Set a passage apart |
| `rich-text-editor.slash.tasks` | Checklist |
| `rich-text-editor.slash.tasksHint` | Things to tick off |
| `rich-text-editor.slashLabel` | Blocks |
| `rich-text-editor.slashNone` | No matching blocks |
| `rich-text-editor.toolbar` | Formatting |
| `run-ledger.budget` | Budget |
| `run-ledger.cost` | Cost |
| `run-ledger.left` | \{amount\} left in the budget |
| `run-ledger.nearing` | Nearing the budget, \{amount\} left |
| `run-ledger.over` | Over budget by \{amount\} |
| `run-ledger.step` | Step |
| `run-ledger.summary` | \{count\} steps, \{time\}, \{tokens\} tokens |
| `run-ledger.time` | Time |
| `run-ledger.title` | Run ledger |
| `run-ledger.tokens` | Tokens |
| `run-ledger.top` | Most expensive |
| `run-ledger.total` | Total |
| `run-replay.markers` | Markers |
| `run-replay.next` | Next marker |
| `run-replay.pause` | Pause |
| `run-replay.play` | Play |
| `run-replay.prev` | Previous marker |
| `run-replay.reached` | Reached: \{label\} |
| `run-replay.replay` | Replay from the start |
| `run-replay.scrub` | Position in the run |
| `run-replay.speed` | Playback speed |
| `scroll-velocity.label` | Scrolling content |
| `search-field.clearSearch` | Clear search |
| `search-field.label` | Search |
| `section-tabs.label` | Sections |
| `settings-panel.dirtyMessage` | You have unsaved changes |
| `settings-panel.discard` | Discard |
| `settings-panel.label` | Settings |
| `settings-panel.saveLabel` | Save changes |
| `sheet.closeLabel` | Close |
| `shortcut-hint.label` | \{label\}: \{keys\} |
| `shortcut-hint.plus` |  plus  |
| `sidebar.label` | Sidebar |
| `sidebar.navigation` | Navigation |
| `sidebar.toggleSidebar` | Toggle sidebar |
| `signature-pad.clearSignature` | Clear signature |
| `signature-pad.draw` | Draw |
| `signature-pad.drawOrType` | Draw or type |
| `signature-pad.drawingArea` | \{label\} drawing area |
| `signature-pad.signatureMethod` | Signature method |
| `signature-pad.type` | Type |
| `signature-pad.typeLabel` | Type \{label\} |
| `signature-pad.typeYourFullName` | Type your full name |
| `signature-pad.undoLastStroke` | Undo last stroke |
| `slider-range.maximum` | \{label\} maximum |
| `slider-range.minimum` | \{label\} minimum |
| `snippet-tabs.code` | \{label\} code |
| `snippet-tabs.language` | Language |
| `sortable.cancelled` | Cancelled. \{item\} went back to position \{position\}. |
| `sortable.dropped` | Dropped \{item\} at position \{position\} of \{total\}. |
| `sortable.droppedBack` | Dropped \{item\}. It did not move. |
| `sortable.groupCancelled` | Cancelled. \{item\} went back to \{group\}. |
| `sortable.groupDropped` | Dropped \{item\} in \{group\} at position \{position\} of \{total\}. |
| `sortable.groupMoved` | Moved \{item\} to position \{position\} of \{total\} in \{group\}. |
| `sortable.groupOver` | \{item\} is over \{group\}, position \{position\} of \{total\}. |
| `sortable.groupPickedUp` | Picked up \{item\} from \{group\}. It is position \{position\} of \{total\}. |
| `sortable.handle` | Reorder \{item\} |
| `sortable.instructions` | To pick up an item, press Space or Enter. While dragging, use the arrow keys to move it, Space or Enter to drop it, and Escape to cancel. |
| `sortable.moveEarlier` | Move \{item\} earlier |
| `sortable.moveLater` | Move \{item\} later |
| `sortable.moved` | Moved \{item\} to position \{position\} of \{total\}. |
| `sortable.over` | \{item\} is over position \{position\} of \{total\}. |
| `sortable.pickedUp` | Picked up \{item\}. It is position \{position\} of \{total\}. |
| `sources-list.showAll` | Show all |
| `sources-list.title` | Sources |
| `sparkline.noData` | \{label\}: no data |
| `sparkline.range` | \{label\}: from \{from\} to \{to\} |
| `spinner.label` | Loading |
| `split-view.label` | List |
| `split-view.label2` | Detail |
| `split-view.resizeList` | Resize list |
| `spotlight-search.placeholder` | Spotlight Search |
| `spotlight-search.title` | Spotlight Search |
| `spring-drawer.closeLabel` | Close |
| `spring-drawer.handleLabel` | Resize drawer |
| `state-view.details` | Details |
| `state-view.emptyDescription` | When there is something to show, it will appear here. |
| `state-view.emptyTitle` | Nothing here yet |
| `state-view.errorDescription` | We could not load this. Check your connection and try again. |
| `state-view.errorTitle` | Something went wrong |
| `state-view.loading` | Loading |
| `state-view.retry` | Try again |
| `status-badge-row.historyDays` | \{name\} history, \{length\} days |
| `status-badge-row.title` | System status |
| `status-badge-row.today` | Today |
| `status-badge-row.uptime` | \{percent\}% uptime |
| `stepper-form.back` | Back |
| `stepper-form.completeLabel` | Complete |
| `stepper-form.progress` | Progress |
| `stepper-form.ready` | Ready |
| `stepper-form.stepCompleted` | \{title\}, step \{n\} of \{total\}, completed |
| `stepper-form.stepLabel` | \{title\}, step \{n\} of \{total\} |
| `stepper-form.stepOf` | Step \{n\} of \{total\} |
| `suggestion-chips.label` | Suggested prompts |
| `suggestion-chips.refreshLabel` | Show other suggestions |
| `table-of-contents.title` | On this page |
| `tag-input.label` | Tags |
| `tag-input.placeholder` | Add a tag… |
| `tag-input.remove` | Remove \{tag\} |
| `team-switcher.addLabel` | Add team |
| `team-switcher.teams` | Teams |
| `terminal.copied` | Copied |
| `terminal.copyCommand` | Copy command |
| `terminal.terminal` | Terminal |
| `thinking-indicator.label` | Thinking |
| `thinking-indicator.statusLabel` | Assistant is thinking |
| `time-picker.label` | Time |
| `toast-stack.dismiss` | Dismiss \{title\} |
| `toast-stack.label` | Notifications |
| `token-meter.full` | Context is full |
| `token-meter.left` | \{left\} left |
| `token-meter.nearingLimit` | Nearing the limit, \{left\} left |
| `token-meter.pill` | \{title\}: \{percent\}% used. \{state\}. |
| `token-meter.summary` | \{used\} of \{limit\} tokens used, \{percent\}%. \{state\}. |
| `token-meter.title` | Context window |
| `tool-catalog.all` | All |
| `tool-catalog.attention` | Needs attention |
| `tool-catalog.calls` | \{count\} calls this week |
| `tool-catalog.count` | \{count\} tools shown |
| `tool-catalog.errors` | \{rate\}% fail |
| `tool-catalog.failed` | \{name\} could not be changed, so it is as it was. |
| `tool-catalog.filter` | Filter tools |
| `tool-catalog.flaky` | fails often |
| `tool-catalog.highOn` | On for a high-risk tool: check it is meant to be. |
| `tool-catalog.latency` | \{ms\} ms typical |
| `tool-catalog.none` | No tools match. |
| `tool-catalog.off` | Off |
| `tool-catalog.on` | On |
| `tool-catalog.schema` | \{name\} input schema |
| `tool-catalog.search` | Search tools |
| `tool-catalog.searchPlaceholder` | Search by name or description |
| `tool-catalog.switch` | \{name\} is on |
| `tool-catalog.title` | Tools |
| `toolbar.ariaLabel` | Search |
| `toolbar.clearSearch` | Clear search |
| `toolbar.search` | Search |
| `trace-waterfall.failed` | failed |
| `trace-waterfall.failedShort` | Failed |
| `trace-waterfall.hint` | Select a span to see its timing. Right opens a span, Left closes it. |
| `trace-waterfall.item` | \{name\}, \{kind\}, \{duration\}, starts at \{start\}\{failed\} |
| `trace-waterfall.label` | Spans of the run |
| `trace-waterfall.share` | \{percent\}% of the run |
| `trace-waterfall.span` | Step |
| `treemap.cell` | \{label\}: \{value\}\{unit\}, \{share\}% of the total\{group\} |
| `treemap.groups` | Groups |
| `treemap.hint` | Select a block to see its share. |
| `treemap.label` | Share of the total by item |
| `treemap.share` | \{percent\}% of the total |
| `user-menu.signOut` | Sign out |
| `video-dialog.closeVideo` | Close video |
| `video-dialog.playVideo` | Play video: \{title\} |
| `video-dialog.videoPlayerPressEscapeTo` | Video player. Press Escape to close. |
| `voice-input.discardRecording` | Discard recording |
| `voice-input.label` | Start voice input |
| `voice-input.processingLabel` | Transcribing… |
| `voice-input.recordingTime` | Recording time |
| `voice-input.stopAndUseRecording` | Stop and use recording |
| `waterfall-chart.after` | Total after |
| `waterfall-chart.change` | Change |
| `waterfall-chart.ends` | Ends at \{value\}. |
| `waterfall-chart.step` | Step |
| `webhook-card.events` | Events |
| `webhook-card.hideSigningSecret` | Hide signing secret |
| `webhook-card.moreEvents` | +\{count\} more |
| `webhook-card.noDeliveriesYetEventsAppear` | No deliveries yet. Events appear here as soon as they are sent. |
| `webhook-card.recentDeliveries` | Recent deliveries |
| `webhook-card.redeliver` | Redeliver |
| `webhook-card.requestBody` | Request body |
| `webhook-card.revealSigningSecret` | Reveal signing secret |
| `webhook-card.sendEventsToThisEndpoint` | Send events to this endpoint |
| `webhook-card.sendTestEvent` | Send test event |
| `webhook-card.signingSecret` | Signing secret |
| `webhook-card.successRate` | \{percent\}% of \{count\} deliveries succeeded |
| `webhook-card.webhook` | Webhook \{url\} |
| `webhook-card.yourResponse` | Your response |
| `widgets.noEventsToday` | No events today |
| `window-manager.label` | Desktop |
| `window-manager.restore` | Restore \{title\} |
| `window-manager.trayLabel` | Minimized windows |
| `window-manager.windowAltAndArrowKeys` | \{title\} window. Alt and arrow keys move it; Alt, Shift and arrow keys resize it. |
| `workspace-card.actions` | \{name\} actions |
| `workspace-card.current` | Current |
| `workspace-card.loadingWorkspace` | Loading workspace |
| `workspace-card.storageUsed` | Storage used |

---

<!-- Source: https://ui.ballmac.com/docs/reference/rtl-fixed-items -->

# Fixed-layout items

The items that keep a fixed layout in right-to-left, with the reason for each.

These items keep a fixed layout in right-to-left on purpose. For what mirrors and what doesn't, see [What mirrors in right-to-left](https://ui.ballmac.com/docs/concepts/right-to-left).

- [Android Frame](https://ui.ballmac.com/components/android-frame): Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
- [Laptop Frame](https://ui.ballmac.com/components/laptop-frame): Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
- [Light Rays](https://ui.ballmac.com/components/light-rays): Rays fan out from a point centred with left-1/2 and a negative margin; both are physical and symmetric, so the picture is the same in either direction.
- [Phone Frame](https://ui.ballmac.com/components/phone-frame): Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
- [Sparkles Text](https://ui.ballmac.com/components/sparkles-text): Sparkles are placed by random percentages from the left edge and centred with a negative margin; decoration with no reading direction.
- [Tablet Frame](https://ui.ballmac.com/components/tablet-frame): Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.
- [Watch Frame](https://ui.ballmac.com/components/watch-frame): Hardware buttons and bezels sit on fixed physical edges of the device, so the frame keeps its shape; the screen content you place inside follows the page direction.

---

<!-- Source: https://ui.ballmac.com/docs/concepts/how-ballmac-works -->

# How Ballmac UI works

Why Ballmac UI is a registry you install from, not a package, and what that means for ownership, updates and collisions.

Ballmac UI is a standard shadcn registry. The shadcn CLI copies an item's source into your project, so you read it, change it and own it. There is no runtime dependency on Ballmac UI to keep up to date.

## You own the code

Copy-paste libraries give you the code once, and then you are on your own: fix a bug upstream and your copy never gets it, re-add the component and you lose your edits. The [ballmac CLI](https://ui.ballmac.com/docs/guides/update-components) remembers what it installed and merges new versions into your edited copies, with the item's own change notes beside each version.

## Nothing collides

Everything installs into `components/ballmac`, so it never overwrites your shadcn/ui files. Each item declares the npm packages it imports and the registry items it builds on, such as `@ballmac/motion-presets` or shadcn's `utils`. The CLI installs them for you, and every build checks that declared dependencies match the actual imports. File locations are in the [registry reference](https://ui.ballmac.com/docs/reference/registry#where-files-go).

## One design language

Primitives, effects, AI interfaces and developer components share tokens, spacing, focus states and motion curves. They follow your theme in light and dark, because they read the standard shadcn CSS variables. See [theme tokens](https://ui.ballmac.com/docs/reference/theme-tokens).

## Effects with manners

Globes, beams and particles pause off-screen, stop for reduced motion and keep your page accessible. [How motion works](https://ui.ballmac.com/docs/concepts/motion).

## Updates are never silent

Components are your code once installed, so updates never happen silently. You compare before you overwrite, with `npx shadcn@latest add @ballmac/button --diff`, or let `ballmac update` merge.

## Verified before every release

Each release installs every item into a fresh Next.js app, then type-checks and builds it, and every item is loaded in a real browser with the results [published, including failures](https://ui.ballmac.com/quality). [What the quality measurement covers](https://ui.ballmac.com/docs/concepts/quality).

## A registry for your own team

This is the same setup we build for clients: your components, in your brand, installable with one command and available to AI agents over MCP. [Talk to us about it](https://ballmac.com/services?offer=registry&ref=ui-registry#start).

---

<!-- Source: https://ui.ballmac.com/docs/concepts/motion -->

# How motion works

A theme is color, shape and movement. Six settings describe how a product moves, and every duration, easing, lift and stagger derives from them.

Most libraries hard-code their animation timings. In Ballmac UI, motion is part of the theme, so one set of values changes how every component moves, and your own code can follow it too.

Try it on real components in the [theme builder](https://ui.ballmac.com/themes#motion), or [install a feel](https://ui.ballmac.com/docs/reference/motion-settings#ready-made-feels) and tune it by hand.

## Six settings

Each setting is a CSS variable on `:root`: speed, bounce, travel, hover, stagger and decorative loops. The defaults reproduce Ballmac UI exactly as it was, so adding the theme changes nothing until you change a setting. The names, variables and ranges are in [Motion settings](https://ui.ballmac.com/docs/reference/motion-settings).

## Five ready-made feels

A motion feel is a registry theme that changes movement only and leaves your colors alone. Installing another replaces it. The twelve color themes each carry a feel that suits them: Graphite and Mono are snappy, Sand, Forest and Midnight are calm, Violet and Ember are playful. Installing `@ballmac/theme-violet` brings its motion too.

## What follows automatically

The theme rewrites Tailwind's own scales, so code you already have follows without edits:

- `duration-75` to `duration-1000` and `delay-75` to `delay-1000` are divided by the speed.
- `ease-out`, `ease-in` and `ease-in-out` use Ballmac's curves. `ease-spring` is a new class: a real spring, written as a CSS `linear()` easing from the bounce setting. A browser that can't parse `linear()` ignores it and keeps the previous easing.
- Dialogs, popovers, menus, tooltips and accordions take their default duration from the speed, through the enter and exit animations from `tw-animate-css`.
- Every Ballmac component follows for transitions, hover lift, button press and decorative loops.
- The shared motion presets (`spring`, `duration`, `ease`, `variants`, `stagger`) that Motion-based components use.

To use the values in your own code, see [Use theme motion in your own code](https://ui.ballmac.com/docs/guides/motion).

## Reduced motion is not no motion

The theme includes a `prefers-reduced-motion` rule: no travel, no lift, no bounce, no loops, and everything a little faster. Fades remain, because a change of state must stay visible.

The **Still** feel ships those reduced values to every visitor, for products that should hold perfectly still. In the builder, **Preview reduced motion** shows what a visitor with the setting sees.

Spinners and "thinking" indicators carry meaning, so they keep running. Only decorative loops, such as glows, shimmers and drifting backgrounds, hold still.

---

<!-- Source: https://ui.ballmac.com/docs/concepts/platform-feels -->

# How platform feels work

A feel is everything about a product except its colors. Pick macOS, iOS, Windows or the neutral web feel and every component follows.

A feel is the sizes, corners, focus ring, shadows, menus, sidebars, window controls and motion of a product, everything except its colors. It works on top of any color theme, so Ocean with the Windows feel is a valid combination.

Try the four side by side in the [theme builder](https://ui.ballmac.com/themes#feels), or see one app in all of them on the [platform feels showcase](https://ui.ballmac.com/feels).

> **Web components, not native controls:** These are web components that feel like each platform. Ballmac UI is not affiliated with Apple or Microsoft. The feels are original work informed by open-source design tokens and by measuring the platforms.

## The four feels

Web is the default and changes nothing. macOS, iOS and Windows each set their own control sizes, corners, focus ring and window controls. The values are listed in the [platform feels reference](https://ui.ballmac.com/docs/reference/platform-feels).

## What follows automatically

- **Shadows and text sizes.** Tailwind's `shadow-xs` to `shadow-2xl` and `text-xs`, `text-sm`, `text-base` are rewritten, so every component and your own classes follow.
- **Radius and density.** The usual `--radius` and `--spacing`, set by the feel.
- **The focus ring.** One rule restyles every component that uses shadcn's standard `focus-visible:ring-[3px]`, which includes shadcn's own components. A few Ballmac items draw their own ring and keep it.
- **Controls, menus and sidebars.** Button, Input, Select and Tabs heights, menu rows and corners, sidebar rows (including the Windows accent pill), frosted popovers and sidebars, press dimming on iOS, and scrollbars.
- **Motion.** Each feel brings a [motion](https://ui.ballmac.com/docs/concepts/motion) personality that suits it.

Tokens are plain CSS variables, so you can change any of them:

```css
:root {
  --bm-control-h: 2rem;        /* button and input height */
  --bm-focus-width: 2px;       /* focus ring */
  --bm-focus-alpha: 100%;
  --bm-shadow-sm: 0 1px 2px rgb(0 0 0 / .12);
  --bm-menu-item-h: 2rem;      /* menu rows */
  --bm-sidebar-item-h: 2.5rem; /* sidebar rows */
  --bm-sidebar-indicator: 3px; /* the accent pill on the selected row */
  --bm-material-blur: 30px;    /* frosted menus and sidebars */
  --bm-press-dim: 0.6;         /* opacity of a pressed button */
}
```

## Know the limits

- Windows 11's snap layouts (hovering the maximize button) belong to native caption buttons. If you need them, use Electron's `titleBarOverlay` and pass `controls={false}` to the title bar.
- On macOS, drawn traffic lights don't get the system's window-tiling menu. Keep the native frame (`titleBarStyle: "hidden"` in Electron) if you rely on it.
- Most of the [Desktop collection](https://ui.ballmac.com/components) is Mac-only by nature (Dock, Menu Bar, Spotlight, Launchpad, Dynamic Island, Control Center) and stays that way. `app-window`, the sidebar, menus and the form controls are the parts every platform shares.

## Next steps

- [Pick a platform feel](https://ui.ballmac.com/docs/guides/platform-feels) for your app.
- [Ship a desktop app](https://ui.ballmac.com/docs/guides/desktop-app) with Tauri or Electron.

---

<!-- Source: https://ui.ballmac.com/docs/concepts/states -->

# Why every block has states

An interface isn't finished until it handles loading, nothing and failure. Every data block and form block takes one prop, state, and shows the right thing.

The edge cases are what make an app feel built. They are also what generated UI skips. In Ballmac UI they are part of the block: skeletons that match its layout, a clear empty message, an error you can retry, and announcements for screen readers.

Open any block in the [gallery](https://ui.ballmac.com/blocks) and use the **State** chips above the preview to see each one.

## The states

- `loading`: data is on its way. A skeleton with the same shape as the content, announced as loading.
- `empty`: there is nothing yet. A clear message and, where it helps, a way to make the first thing.
- `error`: it failed. A message that says what happened and a way to try again.
- `submitting`: a form request is in flight. Fields lock, the button spins and nothing can be sent twice.
- `success`: the request went through. The confirmation the flow ends on.

Leave `state` out and the block behaves as it always has. Nothing changes until you pass it.

## What it does for accessibility

- Loading is announced as a status, and the region is marked `aria-busy` while it lasts. The skeleton itself is hidden from assistive technology.
- An error is announced as an alert. Focus is never moved for you, so nobody loses their place.
- Under reduced motion the fades stop. The skeleton's pulse follows the theme's [decorative loops](https://ui.ballmac.com/docs/concepts/motion#reduced-motion-is-not-no-motion) switch, and the fades follow its speed.
- A form that is submitting is a disabled fieldset, so every control inside is disabled, the same way the browser does it.

## How the repository keeps it true

A free block that lists no states and gives no reason fails `pnpm check`, and so does a state example that would break a production build. The list of blocks and their states is in the [block states reference](https://ui.ballmac.com/docs/reference/block-states).

## Related

- [Handle loading, empty and error states](https://ui.ballmac.com/docs/guides/states)

---

<!-- Source: https://ui.ballmac.com/docs/concepts/quality -->

# What quality measurement covers

How Ballmac UI measures every item in a real browser, what ballmac check does and doesn't tell you, and where the limits are.

Every item in the catalog is measured before it ships, and the results are public on the [quality page](https://ui.ballmac.com/quality), including failures. `ballmac check` holds your own app to the same bar.

## What it is, and is not

- **Conservative.** A rule reports only when it can see the problem in the code. If a value comes from a variable it can't follow, it stays quiet. A clean result means none of these specific mistakes was found, not that the app is accessible.
- **Not a replacement for testing.** Automated checks find a subset of problems. Use a keyboard and a screen reader on the real app as well.
- **Honest about severity.** Errors are things that break for someone, such as an image with no alt or a button with no name. Warnings are likely gaps. You decide what fails the build.

## What each item is measured on

Every item page has a scorecard measured in a real browser: an axe-core accessibility scan in light and dark, no sideways scrolling at 360, 390 and 1280 px, reduced motion, a right-to-left mirror, a clean console, a keyboard pass, states, tests and size.

The measurement is honest about its limits: a loading spinner is kept turning on purpose, WCAG 2.2's 24 px target size is reported as a note rather than a failure, and some items aren't mirrored by design (code, charts, keyboard shortcuts), each with its reason shown.

## Limits of ballmac check

- It reads TypeScript and JSX. Other file types and styles written in separate stylesheets are only partly covered (motion in CSS is).
- Components from other libraries are treated as able to render what they need. Only plain elements and Ballmac components are judged.
- Contrast is measured only by `--url` (axe), because it depends on the final rendered colors.

Found a false positive? [Tell us](https://ui.ballmac.com/support) and include the line.

## Related

- [Check your own app](https://ui.ballmac.com/docs/guides/check-your-app)
- [Every rule](https://ui.ballmac.com/docs/reference/check-rules)

---

<!-- Source: https://ui.ballmac.com/docs/concepts/right-to-left -->

# What mirrors in right-to-left

What changes when the page direction is right to left, what stays left to right on purpose, and why.

Set the page direction and every component, block and template mirrors for Arabic, Hebrew, Persian and Urdu: layout, icons, keyboard arrows, sliders, menus and sheets. To turn it on, see [Turn on right-to-left](https://ui.ballmac.com/docs/guides/right-to-left).

## What mirrors

- **Layout.** Spacing, borders, positions and alignment use CSS logical properties (`ms-`, `me-`, `ps-`, `pe-`, `start-`, `end-`, `text-start`, `border-s`, `rounded-e`), which follow `dir` with no JavaScript.
- **Icons with a direction.** Arrows, chevrons and back and forward icons carry `rtl:rotate-180` or `rtl:-scale-x-100`. Disclosure chevrons still point down when open.
- **Keyboard.** In RTL, ArrowLeft goes to the next tab, raises a slider, expands a tree node, moves a card to the next column and pages a carousel forward. Radix handles its primitives. The custom handlers read `useDirection`.
- **Sides.** `Sheet` and `Sidebar` take `side="start"` or `"end"` (the new default), which flip. `"left"` and `"right"` stay fixed edges.
- **Dates and numbers** use the locale (see [Translate Ballmac UI](https://ui.ballmac.com/docs/guides/translate)), including Arabic-Indic digits when you ask for them.

## What stays left to right, on purpose

- **Code and data.** Code blocks, terminals, logs, JSON, diffs, `.env` rows, API keys and keyboard shortcuts are `dir="ltr"` inside, as in every editor. The surrounding chrome still mirrors.
- **Charts** keep their axes: time runs left to right, as Recharts draws it. Legends and text around them mirror.
- **Motion with a named direction.** Props such as `direction="left"` on marquees and blur-fade describe a physical direction and aren't flipped.
- **Hardware frames and two decorative effects** keep a fixed layout. What you put inside a device frame follows the page direction. Mac windows, the menu bar and the Finder mirror, as Safari does in Arabic. The red, yellow and green lights keep their order.

The items that keep a fixed layout are listed, with the reason for each, in the [fixed-layout items reference](https://ui.ballmac.com/docs/reference/rtl-fixed-items).

---

<!-- Source: https://ui.ballmac.com/docs/concepts/built-for-agents -->

# Built for AI agents

What an AI coding agent sees in Ballmac UI, and how it can read the catalog with or without MCP.

Every Ballmac item includes a description written for agents, when to use it and when not to, the items it composes with, keyboard notes and working examples. An agent can pick the right component and wire it up correctly, instead of guessing from a name.

## Three ways in

- **The Ballmac MCP server.** `@ballmac/mcp` searches the catalog, reads props and examples, returns install commands, and plans pages from blocks. [Connect it](https://ui.ballmac.com/docs/get-started/connect-your-agent).
- **The shadcn MCP server.** It lists, searches and views registry items and gets the add command. [Use it](https://ui.ballmac.com/docs/guides/shadcn-mcp).
- **No MCP.** Point any assistant at [ui.ballmac.com/llms.txt](https://ui.ballmac.com/llms.txt), which lists every component with its purpose and install command, or at [llms-full.txt](https://ui.ballmac.com/llms-full.txt) for the documentation in one file. Every docs page is also available as Markdown: add `/md` in front of the address and `.md` at the end, for example `/md/docs/get-started/install.md`.

## A gate before the agent finishes

`get_quality_rules` lets an agent read the rules before it writes a component, and `audit_project` runs the same check as [`ballmac check`](https://ui.ballmac.com/docs/guides/check-your-app) on its work. Tell the agent to run it before it says it is done.

## Want this for your own design system?

We build private, shadcn-compatible registries with an MCP server for teams, so their AI coding agents use their own components. [See how it works and what it costs](https://ballmac.com/services?offer=registry&ref=ui-mcp#start).
