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

# Button - Web

> Buttons are clickable UI elements that trigger an action or event.

> [!NOTE]
>
> For development, import `Button` from
> `@servicetitan/anvil2`. For design, find it in the
> [Anvil2 file](https://www.figma.com/design/xaNQsvRKczoxu7pj0M6QT8/Anvil2-v3?node-id=1-7067).

![](https://mintcdn.com/servicetitan/uz2PQSvO75TRhQ38/images/docs/web/components/shared/button-overview.png?fit=max\&auto=format\&n=uz2PQSvO75TRhQ38\&q=85\&s=3405a61b02834e79dc7a508666e93d64)

## Anatomy

The Button consists of three primary elements that work together to trigger actions or events.

![Button
anatomy](https://mintcdn.com/servicetitan/Ni0bXIw9diilEZPZ/images/docs/web/components/button/design/button-anatomy.png?fit=max\&auto=format\&n=Ni0bXIw9diilEZPZ\&q=85\&s=7e08db4e3de55569f51a2d8679c209b3)

1. Container
2. Text label
3. Icon

## Options

The Button supports multiple appearances, sizes, and icon configurations to accommodate various action scenarios.

### Appearances

button-appearance

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-appearance?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(3, max-content)" gap="8">
      <Button appearance="primary">Primary</Button>
      <Button>Secondary</Button>
      <Button appearance="ghost">Ghost</Button>
      <Button appearance="danger">Danger</Button>
      <Button appearance="danger-secondary">Danger Secondary</Button>
    </Grid>
  );
}

export default App;
```

| Appearance | When to use |
| - | - |
| Primary | Reserved for important, essential actions on the page. Avoid using more than 3 primary actions in the same view. |
| Secondary | Used as the most common style of actions. |
| Ghost | For less prominent actions, and a good fit for icon-only actions. |

### Sizes

button-size

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-size?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(3, max-content)" gap="8" alignItems="center">
      <Button size="xsmall">Extra Small</Button>
      <Button size="small">Small</Button>
      <Button>Medium</Button>
      <Button size="large">Large</Button>
    </Grid>
  );
}

export default App;
```

| Size | Height |
| - | - |
| Extra Small | 28px |
| Small | 32px |
| Medium | 40px |
| Large | 48px |

Extra small buttons should only be used on desktop devices, as they do not meet the 44x44px tap target required for mobile.

### Icons

button-icon

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-icon?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";
import FileUpload from "@servicetitan/anvil2/assets/icons/material/round/file_upload.svg";
import KeyboardArrowDown from "@servicetitan/anvil2/assets/icons/material/round/keyboard_arrow_down.svg";
import Edit from "@servicetitan/anvil2/assets/icons/material/round/edit.svg";

function App() {
  return (
    <Grid templateColumns="repeat(1, max-content)" gap="8" placeItems="center">
      <Button icon={{ before: FileUpload }}>Prefix Icon</Button>
      <Button icon={{ after: KeyboardArrowDown }}>Suffix Icon</Button>
      <Button icon={Edit} aria-label="edit" />
    </Grid>
  );
}

export default App;
```

Icons can be placed both before and after the Button's text label, and can even replace the text label as an Icon Button.

See the [Button Icon usage guidelines](#icons-in-buttons) on when to use icons, and accessibility considerations when using icons.

### Button Compound

A Button Compound is a standalone component that provides button interactivity to another component. This can be useful for components such as anAvatar, which by themselves lack interactivity.

#### Button Compound Option Circular

button-avatar

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-avatar?file=src/App.tsx)

```tsx lines theme={null}
import { ButtonCompound, Avatar } from "@servicetitan/anvil2";

function App() {
  return (
    <ButtonCompound shape="circular" onClick={console.log}>
      <Avatar name="Rose Tico" size="large" status="online" />
    </ButtonCompound>
  );
}

export default App;
```

The shape of the Button Compound can be changed to conform to the shape of the component it's overlaying, such as an Avatar.

### Button Link

button-link

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-link?file=src/App.tsx)

```tsx lines theme={null}
import { ButtonLink, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex>
      <ButtonLink href="/web">Link to Web</ButtonLink>
    </Flex>
  );
}

export default App;
```

The **Button Link** is a specialized Button component that uses an `<a>` tag under the hood. It gives you the visual style of a button with the correct HTML semantics of a link, making it ideal for actions that navigate to a new page.

The [**Link Button**](https://anvil.servicetitan.com/docs/components/link/design) is its reverse: a Button component styled to look like a simple text link. Use it when an action doesn't involve navigation but you need a link's visual appearance, such as for triggering a modal.

## Behavior

The Button responds to user interaction with distinct visual states and flexible layout behaviors.

### Visual States

#### Primary

button-appearance-primary-data-interactive

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-appearance-primary-data-interactive?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(3, max-content)" gap="8">
      <Button appearance="primary">Rest</Button>
      <Button appearance="primary" data-interactive="hover">
        Hover
      </Button>
      <Button appearance="primary" data-interactive="active">
        Active
      </Button>
      <Button appearance="primary" data-interactive="focus-visible">
        Focus Visible
      </Button>
      <Button appearance="primary" disabled>
        Disabled
      </Button>
    </Grid>
  );
}

export default App;
```

#### Danger Secondary

button-appearance-danger-secondary-data-interactive

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-appearance-danger-secondary-data-interactive?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(3, max-content)" gap="8">
      <Button appearance="danger-secondary">Rest</Button>
      <Button appearance="danger-secondary" data-interactive="hover">
        Hover
      </Button>
      <Button appearance="danger-secondary" data-interactive="active">
        Active
      </Button>
      <Button appearance="danger-secondary" data-interactive="focus-visible">
        Focus Visible
      </Button>
      <Button appearance="danger-secondary" disabled>
        Disabled
      </Button>
    </Grid>
  );
}

export default App;
```

#### Secondary

button-appearance-secondary-data-interactive

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-appearance-secondary-data-interactive?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(3, max-content)" gap="8">
      <Button>Rest</Button>
      <Button data-interactive="hover">Hover</Button>
      <Button data-interactive="active">Active</Button>
      <Button data-interactive="focus-visible">Focus Visible</Button>
      <Button disabled>Disabled</Button>
    </Grid>
  );
}

export default App;
```

#### Ghost

button-appearance-ghost-data-interactive

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-appearance-ghost-data-interactive?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(3, max-content)" gap="8">
      <Button appearance="ghost">Rest</Button>
      <Button appearance="ghost" data-interactive="hover">
        Hover
      </Button>
      <Button appearance="ghost" data-interactive="active">
        Active
      </Button>
      <Button appearance="ghost" data-interactive="focus-visible">
        Focus Visible
      </Button>
      <Button appearance="ghost" disabled>
        Disabled
      </Button>
    </Grid>
  );
}

export default App;
```

#### Danger Primary

button-appearance-danger-data-interactive

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-appearance-danger-data-interactive?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(3, max-content)" gap="8">
      <Button appearance="danger">Rest</Button>
      <Button appearance="danger" data-interactive="hover">
        Hover
      </Button>
      <Button appearance="danger" data-interactive="active">
        Active
      </Button>
      <Button appearance="danger" data-interactive="focus-visible">
        Focus Visible
      </Button>
      <Button appearance="danger" disabled>
        Disabled
      </Button>
    </Grid>
  );
}

export default App;
```

#### Disabled

button-disabled

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-disabled?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(2, max-content)" gap="8" placeItems="center">
      <Button appearance="primary">Primary</Button>
      <Button appearance="primary" disabled>
        Primary
      </Button>

      <Button>Secondary</Button>
      <Button disabled>Secondary</Button>

      <Button appearance="ghost">Ghost</Button>
      <Button appearance="ghost" disabled>
        Ghost
      </Button>

      <Button appearance="danger">Danger</Button>
      <Button appearance="danger" disabled>
        Danger
      </Button>

      <Button appearance="danger-secondary">Danger Secondary</Button>
      <Button appearance="danger-secondary" disabled>
        Danger Secondary
      </Button>
    </Grid>
  );
}

export default App;
```

The disabled style is consistent across each button variant. This style is useful when preventing user clicks when an action isn't available or when a submission of some kind is taking place.

### Text wrap

button-wrap

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-wrap?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid gap="8" placeItems="center">
      <Button>Text Does Not Have A Max Width</Button>
      <Button>
        Text Will Wrap To A Second Line In Code If It Runs Out Of Space
      </Button>
    </Grid>
  );
}

export default App;
```

Buttons fill all available space, wrapping only when no space remains. Text is center-aligned when wrapping.

### Widths

button-width

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-width?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";
import Edit from "@servicetitan/anvil2/assets/icons/material/round/edit.svg";

function App() {
  return (
    <Grid
      templateColumns="repeat(1, 1fr)"
      gap="8"
      placeItems="center"
      flexGrow="1"
    >
      <Button>Text Width</Button>
      <Button style={{ width: "180px" }}>Set Width</Button>
      <Button style={{ width: "100%" }}>Container Width</Button>
      <Button icon={Edit} aria-label="edit" />
    </Grid>
  );
}

export default App;
```

In general, Buttons should match the width of their text. Container width works well when aligning with specific layout elements. Button Icon width always equals its height.

### Left-aligned Text

button-justify-content

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-justify-content?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="2">
      <Button
        appearance="secondary"
        style={{ width: 210 }}
        justifyContent="start"
      >
        Button
      </Button>
      <Button
        appearance="secondary"
        style={{ width: 210 }}
        justifyContent="start"
      >
        A slightly longer Button
      </Button>
      <Button
        appearance="secondary"
        style={{ width: 210 }}
        justifyContent="start"
      >
        Another Button
      </Button>
    </Flex>
  );
}

export default App;
```

Button text is typically centered. Use left-aligned text when aligning with specific layout elements or when stacking multiple buttons vertically.

## Usage Guidelines

| Standard | Requirement |
| - | - |
| DoDenote actions | Use the Button to denote most forms of actions on the page. |
| DoRecommended emphasis pairings | Most combinations of Primary, Secondary, and Ghost are allowed; use the hierarchy that best represents an action's relative importance, and never use more than one Primary action; see [Button Pairing](#button-pairing). |
| DoLeft-align stacked secondary Buttons | When several Buttons are stacked vertically, use the Secondary variant, left-align the text, and give each Button the same width; see [Button Alignment](#button-alignment). |
| DoButton Compound for static components | A component such as an Avatar lacks interactivity on its own and needs button behavior. |
| DoButton Compound to match wrapped shape | The interactive area must conform to the shape of the component it wraps, such as the circular shape for an Avatar. |
| DoButton Link for navigation with button styling | An action navigates to a new page and needs a button's visual style with the semantics of a link. |
| Don'tMultiple Primary or Danger actions | Don't pair multiple Primary or multiple Danger actions together — each should only occur once in a context; see [Don't use these pairings](#dont-use-these-pairings). |
| Don'tUse for navigation | In general, buttons should not be used for navigation. See the Link comparison below for more details. For other navigational contexts, consider the Tab or Side Nav. |

Avoid using the Button Compound for:

* **Clickable cards** – A whole card needs to be clickable; use the [Interactive Card](https://anvil.servicetitan.com/docs/components/interactive-card/web.md) instead.
* **Standard actions** – The control only needs a text label or icon; use the Button instead.

Avoid using the Button Link for:

* **Most navigation** – Links are the preferred choice for navigating; use the [Link](https://anvil.servicetitan.com/docs/components/link/web.md) instead.
* **Actions that don't navigate** – The control runs an action in place; use the Button instead.
* **Non-navigating actions that need a link's appearance** – Use the [Link Button](https://anvil.servicetitan.com/docs/components/link/web.md#link-button) instead.

### Alternatives

#### Button vs Link

In general, Buttons are used to denote an action, while a Link is used to denote navigation. This distinction in practice can be blurry.

##### When navigating

In general, Links are the preferred choice for navigating. There are a few scenarios when a Button may be used however:

* Use a Button when emphasis is needed. Sometimes a page's call to action is navigating somewhere else.
* Use a Button when navigation is mixed with actions.

##### With Actions

In general, Buttons are the preferred choice for actions. A Link however may be used when the action priority is low and space is tight.

##### With triggering overlays

Triggering an overlay UI can be treated like navigation.

### How to Use

#### Button Pairing

There are 3 levels of emphasis in our Buttons: primary, secondary, and ghost. This corresponds to an emphasis scheme of high, medium, and low emphasis.

##### Recommended pairings

button-pairing-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-pairing-do?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(1, max-content)" gap="12">
      <Flex gap="3">
        <Button appearance="primary">Primary</Button>
        <Button>Secondary</Button>
      </Flex>
      <Flex gap="3">
        <Button appearance="primary">Primary</Button>
        <Button appearance="ghost">Ghost</Button>
      </Flex>
      <Flex gap="3">
        <Button>Secondary</Button>
        <Button>Secondary</Button>
      </Flex>
      <Flex gap="3">
        <Button>Secondary</Button>
        <Button appearance="ghost">Ghost</Button>
      </Flex>
      <Flex gap="3">
        <Button appearance="ghost">Ghost</Button>
        <Button appearance="ghost">Ghost</Button>
      </Flex>
    </Grid>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

Most combinations of Primary, Secondary, and Ghost are allowed. In general, use the hierarchy that best represents its relative importance on the page.

* Be consistent within a product area.
* There should never be more than 1 Primary action. There is no upper limit to how many Secondary or Ghost actions could exist.
* Some components, such as [Dialogs](https://anvil.servicetitan.com/docs/components/dialog/web.md), [Drawers](https://anvil.servicetitan.com/docs/components/drawer/web.md), and [Alerts](https://anvil.servicetitan.com/docs/components/alert/web.md) have Button pairing standards already, and those should be used when possible.
* Icon-only actions are frequently represented as Ghost actions.
* A Cancel action is generally a Secondary action.

### Caution when pairing

button-pairing-caution

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-pairing-caution?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(1, max-content)" gap="12">
      <Flex gap="3">
        <Button appearance="primary">Primary</Button>
        <Button>Secondary</Button>
        <Button appearance="ghost">Ghost</Button>
      </Flex>
      <Flex gap="3">
        <Button appearance="primary">Primary</Button>
        <Button>Secondary</Button>
        <Button appearance="danger-secondary">Danger</Button>
      </Flex>
    </Grid>
  );
}

export default App;
```

> [!WARNING]
>
> **Caution**

While these pairings are allowed, caution should be used with them. Three levels of Button hierarchy in one grouping is usually excessive, consider simplifying it to two levels.

#### Don't use these pairings

button-pairing-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-pairing-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Grid templateColumns="repeat(1, max-content)" gap="12">
      <Flex gap="3">
        <Button appearance="primary">Primary</Button>
        <Button appearance="primary">Primary</Button>
      </Flex>
      <Flex gap="3">
        <Button appearance="danger">Danger</Button>
        <Button appearance="danger">Danger</Button>
      </Flex>
    </Grid>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

These pairings should be avoided. Primary actions are meant to only occur once in a context, multiple primaries should not happen.

#### Icons in Buttons

In general, icons should only be used in Buttons when a user can easily associate the icon with the action.

##### Use caution with Icon-only Buttons

While icon-only buttons can save space on the page, the meaning behind icons is not universally understood. Often, this requires users to explore and later recall what each icon does to understand the action.

##### Always include a Tooltip with Icon-only Buttons

All Icon-only buttons should include a Tooltip that appears on hover and focus, describing what the Button would do.

#### Button Alignment

Refer to the [Button alignment guidance from the Form pattern](https://anvil.servicetitan.com/docs/patterns/forms/web.md#button-alignment).

##### Left-aligned Buttons

In scenarios where you have several Buttons stacked vertically, use the Secondary variant and left-align the text. Each button should have the same width.

button-alignment-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-alignment-do?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="2">
      <Button
        appearance="secondary"
        style={{ width: 210 }}
        justifyContent="start"
      >
        Button
      </Button>
      <Button
        appearance="secondary"
        style={{ width: 210 }}
        justifyContent="start"
      >
        A slightly longer Button
      </Button>
      <Button
        appearance="secondary"
        style={{ width: 210 }}
        justifyContent="start"
      >
        Another Button
      </Button>
    </Flex>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

button-alignment-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-alignment-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="2">
      <Button appearance="secondary" style={{ width: 210 }}>
        Button
      </Button>
      <Button appearance="secondary" style={{ width: 210 }}>
        A slightly longer Button
      </Button>
      <Button appearance="secondary" style={{ width: 210 }}>
        Another Button
      </Button>
    </Flex>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

## Content

Content within the Button should clearly communicate the action it will perform.

### Button name should describe what it does

A user should be able to use the button name to predict what will happen when they click it.

Buttons should be action-oriented, pairing a verb and a supporting noun, and be 2 to 3 words long. Common actions like "Save", "Close", "Cancel," or "OK" don't require a supporting noun.

button-action-oriented-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-action-oriented-do?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="2">
      <Button>Merge Accounts</Button>
      <Button>Save</Button>
      <Button>View Settings</Button>
    </Flex>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

button-action-oriented-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-action-oriented-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="2">
      <Button>Account Merge</Button>
      <Button>Save to Database</Button>
      <Button>Settings</Button>
    </Flex>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

### Be concise and consistent

Avoid unnecessary words and articles such as the, an, or a. Never include punctuation in button text and avoid button text that requires punctuation. Always write button text in title case. Capitalize the first word, the last word, and all major words in between. Never use emoji or exclamation points.

button-concise-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-concise-do?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="2">
      <Button>Add Menu Item</Button>
      <Button>Done</Button>
    </Flex>
  );
}

export default App;
```

> [!TIP]
>
> **Do**

button-concise-dont

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-concise-dont?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex direction="column" gap="2">
      <Button>Add a menu item</Button>
      <Button>Yes, I‘m Done!</Button>
    </Flex>
  );
}

export default App;
```

> [!CAUTION]
>
> **Don't**

### Button name should match destination

When a button opens a modal or takeover, the destination's title should match the button text.

## Keyboard Interaction

Users can navigate the Button using standard keyboard controls.

| Key | Interaction |
| - | - |
| Space or Enter | Engages the button action |

Buttons using an href will also use this keyboard combination.

### Accessibility

Anvil provides most of the accessibility needs for buttons out of the box, and only needs some additional considerations for library users.

#### Provide labels for Icon Buttons

button-icon-tooltip

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-icon-tooltip?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Tooltip } from "@servicetitan/anvil2";
import Edit from "@servicetitan/anvil2/assets/icons/material/round/edit.svg";

function App() {
  return (
    <Tooltip open>
      <Tooltip.Trigger>
        <Button icon={Edit} />
      </Tooltip.Trigger>
      <Tooltip.Content>Tooltip Content</Tooltip.Content>
    </Tooltip>
  );
}

export default App;
```

Regardless of whether or not a label is visible on the page, a label should be provided to describe the action for both screen readers and for users who focus on the element.

For more guidance on button labels and accessibility, see [button accessibility best practices](https://anvil.servicetitan.com/docs/accessibility/labels-and-ctas.md#buttons-and-links).

## Code

**Implementation**

button-playground

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-playground?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Button } from "@servicetitan/anvil2";
import Star from "@servicetitan/anvil2/assets/icons/material/round/star.svg";

function App() {
  return (
    <Button
      appearance="primary"
      size="medium"
      icon={Star}
      onClick={console.log}
    >
      Click me
    </Button>
  );
}

export default App;
```

## Common Examples

```tsx theme={null}
import { Button, ButtonCompound, ButtonLink, Card } from "@servicetitan/anvil2";

function ExampleComponent() {
  return (
    <>
      <Button onClick={console.log}>Click me</Button>
      <ButtonCompound onClick={console.log}>
        <Card>Click me</Card>
      </ButtonCompound>
      <ButtonLink href="#">Click me</ButtonLink>
    </>
  );
}
```

The `Button` component can be used in the same way that you would use an HTML `button` element, while the `ButtonLink` component can be used in the same way that you would use an HTML `a` element.

### Form Submit

The `type` prop can be used to alter the default functionality of the button. A common use case is to use `type="submit"` in order to submit the content of a form.

```tsx theme={null}
<form onSubmit={handleSubmit}>
  <TextInput name="firstName" label="First Name" required />
  <Button type="submit">Submit</Button>
</form>
```

### Button links

In general, [Links](https://anvil.servicetitan.com/docs/components/link/design) should be used for navigating to other pages or website. To create a link that is styled like a button, use the `ButtonLink` component.

```tsx theme={null}
<div>
  <div>
    <BodyText>
      A project label can be attached to expense items like timesheet activities
      to provide a visual representation of expense spending.
    </BodyText>
    <br />
    <br />
    <ButtonLink href="#">Learn more</ButtonLink>
  </div>
</div>
```

### Using button compounds to make components interactive

To add interactivity to components that are usually static, such as avatars, use the `ButtonCompound` component.

```tsx theme={null}
<ButtonCompound shape="circular" onClick={handleClick}>
  <Avatar {...avatarProps} />
</ButtonCompound>
```

button-avatar

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-avatar?file=src/App.tsx)

```tsx lines expandable theme={null}
import { ButtonCompound, Avatar } from "@servicetitan/anvil2";

function App() {
  return (
    <ButtonCompound shape="circular" onClick={console.log}>
      <Avatar name="Rose Tico" size="large" status="online" />
    </ButtonCompound>
  );
}

export default App;
```

#### Interactive cards

To make cards clickable, use the [`InteractiveCard`](https://anvil.servicetitan.com/docs/components/interactive-card/code) component. It supports nested interactive elements without accessibility violations.

button-card

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-card?file=src/App.tsx)

```tsx lines expandable theme={null}
import { ButtonCompound, Card, Text } from "@servicetitan/anvil2";

function App() {
  return (
    <ButtonCompound onClick={console.log}>
      <Card flexDirection="column">
        <Text variant="headline" el="h2">
          Interactive Card
        </Text>
        <Text>You can click this card!</Text>
      </Card>
    </ButtonCompound>
  );
}

export default App;
```

### AI Mark

Use the `aiMark` prop to display an AI indicator. `aiMark` accepts a `boolean`, or one of:

* `"mark"`
* `"chat"`
* `"edit"`
* `"form"`
* `"mic"`
* `"search"`

The `aiMark` prop cannot be used together with a before icon (a single `icon` Svg or `icon={{ before: Svg }}`).

| Button usage | `aiMark` | `icon` allowed? |
| - | - | - |
| With `children` | `true` or an AI icon name | Only after: use `icon={{ after: Svg }}` or omit `icon`. You cannot use a before icon or a single `icon` Svg. |
| Icon-only (no `children`) | `true` or an AI icon name | No. Do not pass `icon`. Only the AI icon is shown. Always provide `aria-label`. |

button-aimark

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/button-aimark?file=src/App.tsx)

```tsx lines theme={null}
import { Button, Grid } from "@servicetitan/anvil2";
import ArrowForwardIcon from "@servicetitan/anvil2/assets/icons/material/round/arrow_forward.svg";

function App() {
  return (
    <Grid templateColumns="repeat(2, max-content)" gap="6" placeItems="center">
      <Button appearance="primary" aiMark aria-label="AI draft estimate" />
      <Button appearance="primary" aiMark>
        Summarize Visit
      </Button>
      <Button
        appearance="secondary"
        aiMark="search"
        aria-label="Search similar jobs"
      />
      <Button
        appearance="secondary"
        aiMark="form"
        icon={{ after: ArrowForwardIcon }}
      >
        Autofill Work Order
      </Button>
    </Grid>
  );
}

export default App;
```

**With children:**

```tsx theme={null}
<Button appearance="primary" aiMark>Generate</Button>
<Button appearance="secondary" aiMark icon={{ after: ArrowForward }}>Report</Button>
```

**Icon-only AI:**

```tsx theme={null}
<Button aria-label="AI action" aiMark />
<Button aria-label="AI chat" aiMark="chat" />
```

### Minimum width for buttons

If a `Button` component has any `children`, it will have a `5rem` minimum width. This is not applied in icon-only buttons using the `icon` prop. In some cases, such as when adding a `Badge` as a child of a `Button` component without any text, this can lead to unexpected width changes.

Either override the `width` and `minWidth` of the `Button` component, or make the `Badge` a sibling of the `Button` inside of a `div` with relative positioning.

```tsx theme={null}
// option 1
<Button
  aria-label="filters button"
  icon={FilterIcon}
  style={{ width: '2rem', minWidth: '2rem' }}
>
  <Badge aria-label="2 active filters">2</Badge>
</Button>

// option 2
<div style={{ position: 'relative', display: 'inline-block '}}>
  <Button aria-label="filters button" icon={FilterIcon} />
  <Badge aria-label="2 active filters">2</Badge>
</div>
```

## React Accessibility

### Icon-only buttons

```tsx theme={null}
<Button icon={Edit} aria-label="Edit" />
```

> [!TIP]
>
> **Do**

Use aria-label or aria-labelledby for a button without any text.

```tsx theme={null}
<Button icon={Edit} />
```

> [!CAUTION]
>
> **Don't**

Provide only an icon for screen readers to read.

For more guidance on button labels and accessibility, see [button accessibility best practices](https://anvil.servicetitan.com/docs/accessibility/labels-and-ctas.md#buttons-and-links).

**Button Props**

```tsx theme={null}
<Button
  appearance="primary"
  size="medium"
  icon={StarIcon}
  loading={false}
  aiMark={true}
  onClick={() => console.log("Clicked")}
>
  Click me
</Button>
```

## `Button` Props

**aiMark** `boolean | "mark" | "chat" | "edit" | "form" | "mic" | "search"` (default: false)

Shows an AI indicator. When set with button text, a leading `icon` (plain Svg or `icon.before`) is not rendered—the AI icon is used instead. On an icon-only button, `icon` is not rendered. Use `icon={{ after: Svg }}` for a trailing icon with text.

**appearance** `"primary" | "secondary" | "ghost" | "danger" | "danger-secondary"` (default: secondary)

**children** `ReactNode`

**icon** `Svg | \{ before: Svg \} | \{ after: Svg \}`

The SVGR library imports `*.svg` files as React components, so `icon` can
accept either a React component, or an object with `before` or `after` key
to specify the placement of the icon.

**loading** `boolean` (default: false)

Setting this to `true` will put the button into a loading state.

**size** `"xsmall" | "small" | "medium" | "large"` (default: medium)

**ButtonCompound Props**

```tsx theme={null}
<ButtonCompound shape="circular" onClick={handleClick}>
  <Avatar name="John Doe" />
</ButtonCompound>
```

## `ButtonCompound` Props

**shape** `"pill" | "circular" | "rounded"` (default: rounded)

**ButtonLink Props**

```tsx theme={null}
<ButtonLink
  href="/page"
  appearance="primary"
  size="medium"
  icon={StarIcon}
  loading={false}
  disabled={false}
>
  Click me
</ButtonLink>
```

## `ButtonLink` Props

**appearance** `"primary" | "secondary" | "ghost" | "danger" | "danger-secondary"` (default: secondary)

**children** `ReactNode`

**disabled** `boolean` (default: false)

**icon** `Svg | \{ before: Svg \} | \{ after: Svg \}`

The SVGR library imports `*.svg` files as React components, so `icon` can
accept either a React component, or an object with `before` or `after` key
to specify the placement of the icon.

**loading** `boolean` (default: false)

Setting this to `true` will put the button into a loading state.

**size** `"xsmall" | "small" | "medium" | "large"` (default: medium)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.