> ## Documentation Index
> Fetch the complete documentation index at: https://developers.arg.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# @arg/ui

> Use Arg's exact shared React controls in a live TSX or JSX workspace app.

`@arg/ui` gives a React app the same core controls Arg uses. Import it directly in a `.tsx` or `.jsx` workspace file. There is no package installation or stylesheet import: the React preview supplies the module and adds its styles when you use it.

```tsx theme={null}
import { Button, Card, FormInput } from "@arg/ui";
```

The controls inherit the viewer's `light`, `focus`, or `dark` Arg theme. Buttons, cards, form controls, and compatibility primitives forward their documented DOM props and refs, so you can compose them with your own workspace CSS.

## Components

| Export                     | Use it for                                                                |
| -------------------------- | ------------------------------------------------------------------------- |
| `Button`                   | Primary, secondary, ghost, outlined, and danger actions                   |
| `IconButton`               | Compact toolbar or icon-only actions                                      |
| `Card`                     | Surface, muted, or unstyled content containers                            |
| `FormInput`, `Input`       | Text and native input controls with errors and an optional leading icon   |
| `FormTextarea`, `Textarea` | Multi-line input with an optional error                                   |
| `KeyboardShortcut`         | An inline or badge-style keyboard shortcut label                          |
| `Dropdown`                 | A controlled, optionally searchable select menu                           |
| `ContextMenu`              | A viewport-positioned menu with items, submenus, checkboxes, and steppers |

## Buttons

```tsx theme={null}
import { Button, IconButton } from "@arg/ui";

export default function Actions() {
  return (
    <div style={{ display: "flex", gap: 8 }}>
      <Button variant="primary">Save</Button>
      <Button variant="outlined">Preview</Button>
      <Button variant="danger" isLoading={false}>
        Delete
      </Button>
      <IconButton aria-label="More options">...</IconButton>
    </div>
  );
}
```

### `Button` props

`Button` accepts native button props plus:

| Prop        | Type                                                                                 | Default     |
| ----------- | ------------------------------------------------------------------------------------ | ----------- |
| `variant`   | `"primary" \| "secondary" \| "ghost" \| "outlined" \| "danger" \| "danger-outlined"` | `"primary"` |
| `size`      | `"sm" \| "md" \| "lg"`                                                               | `"md"`      |
| `isLoading` | `boolean`                                                                            | `false`     |
| `leftIcon`  | `ReactNode`                                                                          | None        |
| `rightIcon` | `ReactNode`                                                                          | None        |

Loading replaces the left icon with a spinner and disables the button. `buttonClassName({ variant, size, className })` returns the same class set when you need button styling on a compatible custom element.

`IconButton` accepts native button props, `size: "sm" | "md" | "lg"`, and `variant: "ghost" | "solid"`. The defaults are `"md"` and `"ghost"`. Give every icon-only button an `aria-label`.

## Cards

```tsx theme={null}
import { Card } from "@arg/ui";

export default function ProjectCard() {
  return (
    <Card as="a" href="#details" variant="surface" padding="md" radius="xl" interactive>
      <h2>Launch plan</h2>
      <p>Eight tasks are ready for review.</p>
    </Card>
  );
}
```

| Prop          | Type                                       | Default     |
| ------------- | ------------------------------------------ | ----------- |
| `variant`     | `"surface" \| "muted" \| "ghost"`          | `"surface"` |
| `padding`     | `"none" \| "sm" \| "base" \| "md" \| "lg"` | `"md"`      |
| `radius`      | `"lg" \| "xl" \| "2xl"`                    | `"xl"`      |
| `interactive` | `boolean`                                  | `false`     |
| `as`          | A React element type                       | `"div"`     |

`Card` also forwards element attributes. Set `as="a"` when you pass `href`.

## Form controls

```tsx theme={null}
import { FormInput, FormTextarea } from "@arg/ui";

export default function ProfileFields() {
  return (
    <form style={{ display: "grid", gap: 12 }}>
      <FormInput
        aria-label="Project name"
        placeholder="Project name"
        error="A project name is required"
      />
      <FormTextarea aria-label="Summary" placeholder="Summary" rows={5} />
    </form>
  );
}
```

Both controls accept their native input or textarea attributes. Their `variant` is `"default"`, `"medium"`, or `"small"` and defaults to `"default"`. Pass `error` to show an error state and message. `FormInput` also accepts `leftIcon`; `FormTextarea` defaults to four rows.

`Input` is an alias for `FormInput`. `Textarea` is an alias for `FormTextarea`.

## Dropdown

`Dropdown` is controlled: your component owns `value`, and `onChange` receives the selected option's string value.

```tsx theme={null}
import { useState } from "react";
import { Dropdown } from "@arg/ui";

const options = [
  { value: "comfortable", label: "Comfortable" },
  { value: "compact", label: "Compact", description: "Fit more rows on screen" },
];

export default function DensityPicker() {
  const [density, setDensity] = useState("comfortable");

  return (
    <Dropdown
      options={options}
      value={density}
      onChange={setDensity}
      triggerVariant="field"
      searchable
      searchPlaceholder="Find a density..."
    />
  );
}
```

### `Dropdown` props

| Prop                | Type                                      | Default                        |
| ------------------- | ----------------------------------------- | ------------------------------ |
| `id`                | `string`                                  | Generated                      |
| `options`           | `{ value, label, description?, icon? }[]` | Required                       |
| `value`             | `string`                                  | Required                       |
| `onChange`          | `(value: string) => void`                 | Required                       |
| `placeholder`       | `string`                                  | `"Select..."`                  |
| `disabled`          | `boolean`                                 | `false`                        |
| `size`              | `"sm" \| "md"`                            | `"md"`                         |
| `triggerVariant`    | `"default" \| "field" \| "ghost"`         | `"default"`                    |
| `searchable`        | `boolean`                                 | `false`                        |
| `searchPlaceholder` | `string`                                  | `"Search..."`                  |
| `menuMinWidth`      | `number`                                  | Trigger width, at least 120 px |
| `footerAction`      | `{ label, icon?, onSelect, disabled? }`   | None                           |
| `className`         | `string`                                  | None                           |
| `triggerClassName`  | `string`                                  | None                           |

The menu opens in a portal, flips when space is tight, and stays inside the viewport. It supports arrow keys, Home, End, Enter, Escape, and type filtering when `searchable` is on.

## Context menu

Render `ContextMenu` only while you have an anchor position. Clear that position in `onClose`.

```tsx theme={null}
import { useState } from "react";
import { ContextMenu } from "@arg/ui";

export default function FileRow() {
  const [menu, setMenu] = useState<{ x: number; y: number } | null>(null);
  const [pinned, setPinned] = useState(false);

  return (
    <div
      onContextMenu={(event) => {
        event.preventDefault();
        setMenu({ x: event.clientX, y: event.clientY });
      }}
    >
      Right-click this row
      {menu ? (
        <ContextMenu
          position={menu}
          ariaLabel="File actions"
          onClose={() => setMenu(null)}
          items={[
            { kind: "header", id: "file", label: "File" },
            { kind: "item", id: "open", label: "Open", onClick: () => {} },
            {
              kind: "checkbox",
              id: "pin",
              label: "Pinned",
              checked: pinned,
              onClick: () => setPinned((value) => !value),
            },
            { kind: "separator", id: "divider" },
            { kind: "item", id: "delete", label: "Delete", danger: true, onClick: () => {} },
          ]}
        />
      ) : null}
    </div>
  );
}
```

`ContextMenu` accepts `position`, `items`, and `onClose`, plus `size: "sm" | "md"`, `align: "start" | "end"`, `autoFocus`, and `ariaLabel`. The defaults are `"md"`, `"start"`, and `false`.

| Item kind   | Main fields                                                                                   |
| ----------- | --------------------------------------------------------------------------------------------- |
| `item`      | `id`, `label`, `onClick`, `icon?`, `trailingIcon?`, `shortcut?`, `disabled?`, `danger?`       |
| `checkbox`  | `id`, `label`, `checked`, `onClick`, `icon?`, `shortcut?`                                     |
| `submenu`   | `id`, `label`, `items`, `icon?`                                                               |
| `stepper`   | `id`, `label`, `value`, increment and decrement callbacks, optional reset and disabled states |
| `header`    | `id`, `label`                                                                                 |
| `separator` | `id`                                                                                          |

The menu and its submenus stay inside the viewport. Outside clicks and Escape call `onClose`, and keyboard navigation covers menu items and submenus.

## Keyboard shortcuts

```tsx theme={null}
import { KeyboardShortcut } from "@arg/ui";

<KeyboardShortcut variant="badge">⌘K</KeyboardShortcut>;
```

`variant` is `"inline"` or `"badge"` and defaults to `"inline"`.

## Compatibility exports

Existing apps may still import `Badge`, `Heading`, `Text`, `Stack`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, and `CardFooter`. These exports remain available for compatibility, but they are deprecated. New apps should compose semantic HTML inside `Card` and use their own CSS for layout and typography.

## Related

* [React app SDKs](/guides/apps/react-apps)
* [Run Actions with `@arg/actions`](/guides/apps/actions-sdk)
* [Open the finished app in full view](/guides/apps/full-view)
