> ## Documentation Index
> Fetch the complete documentation index at: https://api.fanvue.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Button

> A versatile button component with multiple visual variants, sizes, icon slots, loading state, and optional pricing display.

```tsx theme={null}
import { Button } from "@fanvue/ui";
import type { ButtonProps, ButtonSize, ButtonVariant } from "@fanvue/ui";
```

## Examples

## Primary

<Frame>
  <iframe src={"https://main--697a1b6dd4dad73ee9c0e5f5.chromatic.com/iframe.html?id=components-button--primary&viewMode=story&shortcuts=false&singleStory=true&globals=theme:light"} width="100%" height="300" style={{border: "none", borderRadius: "8px"}} loading="lazy" title="Button — Primary" />
</Frame>

```tsx theme={null}
import { Button } from "@fanvue/ui";

<Button variant="primary" size="48">Label</Button>
```

## Loading

<Frame>
  <iframe src={"https://main--697a1b6dd4dad73ee9c0e5f5.chromatic.com/iframe.html?id=components-button--loading&viewMode=story&shortcuts=false&singleStory=true&globals=theme:light"} width="100%" height="300" style={{border: "none", borderRadius: "8px"}} loading="lazy" title="Button — Loading" />
</Frame>

```tsx theme={null}
import { Button } from "@fanvue/ui";

<Button variant="primary" size="48" loading>Label</Button>
```

## With Left Icon

<Frame>
  <iframe src={"https://main--697a1b6dd4dad73ee9c0e5f5.chromatic.com/iframe.html?id=components-button--with-left-icon&viewMode=story&shortcuts=false&singleStory=true&globals=theme:light"} width="100%" height="300" style={{border: "none", borderRadius: "8px"}} loading="lazy" title="Button — With Left Icon" />
</Frame>

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

<Button variant="primary" size="48" leftIcon={<PlusIcon />}>Add Item</Button>
```

## All Styles V2

<Frame>
  <iframe src={"https://main--697a1b6dd4dad73ee9c0e5f5.chromatic.com/iframe.html?id=components-button--all-styles-v-2&viewMode=story&shortcuts=false&singleStory=true&globals=theme:light"} width="100%" height="1040" style={{border: "none", borderRadius: "8px"}} loading="lazy" title="Button — All Styles V2" />
</Frame>

```tsx theme={null}
import { Button } from "@fanvue/ui";
import type { ButtonVariant } from "@fanvue/ui";

const NEGATIVE_AWARE_VARIANTS_LIST: ButtonVariant[] = [
  "primary",
  "secondary",
  "tertiary",
  "outline",
];

const STANDALONE_VARIANTS_LIST: ButtonVariant[] = [
  "brand",
  "destructive",
  "ai",
  "white",
  "alwaysBlack",
  "link",
  "tertiaryDestructive",
  "text",
];

const renderMatrixRow = (variant: ButtonVariant, negative: boolean) => (
  <div key={`${variant}-${negative}`} className="flex items-center gap-3">
    <span className="w-32 text-xs">{`${variant}${negative ? " (negative)" : ""}`}</span>
    <Button variant={variant} size="48" negative={negative}>
      Label
    </Button>
    <Button variant={variant} size="48" negative={negative} disabled>
      Label
    </Button>
    <Button variant={variant} size="48" negative={negative} loading>
      Label
    </Button>
    <Button variant={variant} size="40" negative={negative}>
      Label
    </Button>
    <Button variant={variant} size="32" negative={negative}>
      Label
    </Button>
    <Button variant={variant} size="24" negative={negative}>
      Label
    </Button>
  </div>
);

<div className="flex flex-col gap-6">
  <div>
    <h3 className="typography-header-heading-xs mb-3">
      Default surface — default, disabled, loading, then sizes 40 / 32 / 24
    </h3>
    <div className="flex flex-col gap-2">
      {NEGATIVE_AWARE_VARIANTS_LIST.map((v) => renderMatrixRow(v, false))}
      {STANDALONE_VARIANTS_LIST.map((v) => renderMatrixRow(v, false))}
    </div>
  </div>
  <div className="rounded-md bg-surface-primary-inverted p-4">
    <h3 className="typography-header-heading-xs mb-3 text-content-primary-inverted">
      Negative on dark surface
    </h3>
    <div className="flex flex-col gap-2">
      {NEGATIVE_AWARE_VARIANTS_LIST.map((v) => renderMatrixRow(v, true))}
    </div>
  </div>
</div>
```

## Join Now With Price

<Frame>
  <iframe src={"https://main--697a1b6dd4dad73ee9c0e5f5.chromatic.com/iframe.html?id=components-button--join-now-with-price&viewMode=story&shortcuts=false&singleStory=true&globals=theme:light"} width="100%" height="300" style={{border: "none", borderRadius: "8px"}} loading="lazy" title="Button — Join Now With Price" />
</Frame>

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

<Button variant="brand" size="48" rightIcon={<CrownIcon />} price="$X.XX/ month">Join now</Button>
```

## Props

## Button

A versatile button component with multiple visual variants, sizes, icon slots, loading state, and optional pricing display.

Pass `negative` when rendering on a dark surface to opt into the inverted treatment for `primary`, `secondary`, `tertiary`, and `outline` variants.

The `ai` variant ships with the AI sparkle icon baked in (locked at 16px regardless of button size); pass `leftIcon` to override the default sparkle.

<ParamField path="asChild" type="boolean" default="false">
  Merge props onto a child element instead of rendering a `<button>`.
</ParamField>

<ParamField path="discount" type="string">
  Old price shown with a strikethrough before the current price.
</ParamField>

<ParamField path="fullWidth" type="boolean" default="false">
  When `true`, the button will take the full width of its container.
</ParamField>

<ParamField path="leftIcon" type="ReactNode">
  Icon element displayed before the label.
</ParamField>

<ParamField path="loading" type="boolean" default="false">
  When `true`, replaces the label with a spinner and disables interaction.
</ParamField>

<ParamField path="negative" type="boolean" default="false">
  Forces the dark-surface treatment regardless of theme. Only honored on `primary`, `secondary`, `tertiary`, and `outline` variants; ignored on all others. Use when placing the button on a dark background in a light theme (e.g. an inverted hero or modal).
</ParamField>

<ParamField path="price" type="string">
  Current price shown inside the button after the label and icons.
</ParamField>

<ParamField path="rightIcon" type="ReactNode">
  Icon element displayed after the label.
</ParamField>

<ParamField path="size" type="&#x22;24&#x22; | &#x22;32&#x22; | &#x22;40&#x22; | &#x22;48&#x22;" default="40">
  Height of the button in pixels.
</ParamField>

<ParamField path="variant" type="&#x22;link&#x22; | &#x22;text&#x22; | &#x22;brand&#x22; | &#x22;primary&#x22; | &#x22;secondary&#x22; | &#x22;tertiary&#x22; | &#x22;outline&#x22; | ..." default="primary">
  Visual style variant of the button.

  Full type: `"link" | "text" | "brand" | "primary" | "secondary" | "tertiary" | "outline" | "destructive" | "white" | "alwaysBlack" | "ai" | "tertiaryDestructive"`.
</ParamField>

## Exported types

Also exported from `@fanvue/ui`: `ButtonProps`, `ButtonSize`, `ButtonVariant`.

***

**Setup:** [Installation](/docs/ui/installation) · [Theming](/docs/ui/theming)
