> Markdown version of [Button](https://vaadin.com/docs/latest/components/button). Section index: [llms.txt](https://vaadin.com/docs/latest/components/llms.txt)

# Button

The Button component allows users to perform actions. It comes in several different style variants and supports icons as well as text labels.

**Lit** — `button-basic.ts`

```html
<vaadin-horizontal-layout theme="spacing" style="align-items: baseline">
  <vaadin-button @click="${() => this.counter++}">Button</vaadin-button>
  <p>Clicked ${this.counter} times</p>
</vaadin-horizontal-layout>
```

**Flow** — `ButtonBasic.java`

```java
Button button = new Button("Button");
Paragraph info = new Paragraph(infoText());
button.addClickListener(clickEvent -> {
    counter += 1;
    info.setText(infoText());
});
```

**React** — `button-basic.tsx`

```tsx
<HorizontalLayout theme="spacing" style={{ alignItems: 'baseline' }}>
  <Button onClick={() => counter.value++}>Button</Button>
  <p>Clicked {counter} times</p>
</HorizontalLayout>
```

## <a id="buttons-with-icons"></a>Buttons with Icons

Buttons can have icons instead of text, or they can have icons along with text.

**Lit** — `button-icons.ts`

```html
<!-- Icon button using an aria-label to provide a textual alternative
     to screen readers -->
<vaadin-button aria-label="Add item">
  <vaadin-icon icon="vaadin:plus"></vaadin-icon>
</vaadin-button>

<!-- Icon button using a tooltip to provide a textual description of
     the action that it triggers -->
<vaadin-button aria-label="Close">
  <vaadin-icon icon="vaadin:close"></vaadin-icon>
  <vaadin-tooltip slot="tooltip" text="Close the dialog"></vaadin-tooltip>
</vaadin-button>

<vaadin-button>
  <vaadin-icon icon="vaadin:arrow-left" slot="prefix"></vaadin-icon>
  Left
</vaadin-button>

<vaadin-button>
  Right
  <vaadin-icon icon="vaadin:arrow-right" slot="suffix"></vaadin-icon>
</vaadin-button>
```

**Flow** — `ButtonIcons.java`

```java
// Icon button using an aria-label to provide a textual alternative
// to screen readers
Button plusButton = new Button(new Icon(VaadinIcon.PLUS));
plusButton.setAriaLabel("Add item");

// Icon button using a tooltip to provide textual description
// of the action that it triggers
Button closeButton = new Button(new Icon(VaadinIcon.CLOSE));
closeButton.setAriaLabel("Close");
closeButton.setTooltipText("Close the dialog");

Button arrowLeftButton = new Button("Left",
        new Icon(VaadinIcon.ARROW_LEFT));

Button arrowRightButton = new Button("Right",
        new Icon(VaadinIcon.ARROW_RIGHT));
arrowRightButton.setIconAfterText(true);
```

**React** — `button-icons.tsx`

```tsx
{/* Icon button using an aria-label to provide a textual alternative
    to screen readers  */}
<Button aria-label="Add item">
  <Icon icon="vaadin:plus" />
</Button>

{/* Icon button using a tooltip to provide a textual description of
    the action that it triggers */}
<Button aria-label="Close">
  <Icon icon="vaadin:close" />
  <Tooltip slot="tooltip" text="Close the dialog" />
</Button>

<Button>
  <Icon icon="vaadin:arrow-left" slot={'prefix'} />
  Left
</Button>

<Button>
  Right
  <Icon icon="vaadin:arrow-right" slot={'suffix'} />
</Button>
```

Use icons sparingly. Most actions are difficult to represent reliably with icons. The benefit of icons plus text should be weighed against the visual clutter they create.

Icon-only buttons should be used primarily for common recurring actions with highly standardized, universally understood icons (e.g., a cross for **close**), and for actions that are repeated, such as in lists and tables. They should also include a textual alternative for screen readers using the `aria-label` attribute (see the first two buttons in the previous example).

Additionally, [tooltips](https://vaadin.com/docs/latest/components/tooltip.md) can be added to provide a description of the action that the button triggers (see the **Close** button in the previous example).

## <a id="buttons-with-images"></a>Buttons with Images

Images on buttons can be used like icons. See the icon usage recommendations for more information.

**Lit** — `button-images.ts`

```html
<vaadin-button>
  <img src="${img}" width="100" alt="Vaadin logo" />
</vaadin-button>
```

**Flow** — `ButtonImages.java`

```java
Image img = new Image(src, "Vaadin logo");
img.setWidth("100px");

Button imgButton = new Button(img);
```

**React** — `button-images.tsx`

```tsx
<Button>
  <img src={img} width="100" alt="Vaadin logo" />
</Button>
```

## <a id="disabled"></a>Disabled

Buttons representing actions that aren’t currently available to the user should be either hidden or disabled. A disabled button is rendered as "dimmed".

**Lit** — `button-disabled.ts`

```html
<vaadin-button theme="primary" disabled>Primary</vaadin-button>
<vaadin-button theme="secondary" disabled>Secondary</vaadin-button>
<vaadin-button theme="tertiary" disabled>Tertiary</vaadin-button>
```

**Flow** — `ButtonDisabled.java`

```java
Button primaryButton = new Button("Primary");
primaryButton.setEnabled(false);
primaryButton.addThemeVariants(ButtonVariant.PRIMARY);

Button secondaryButton = new Button("Secondary");
secondaryButton.setEnabled(false);

Button tertiaryButton = new Button("Tertiary");
tertiaryButton.setEnabled(false);
tertiaryButton.addThemeVariants(ButtonVariant.TERTIARY);
```

**React** — `button-disabled.tsx`

```tsx
<Button theme="primary" disabled>
  Primary
</Button>

<Button theme="secondary" disabled>
  Secondary
</Button>

<Button theme="tertiary" disabled>
  Tertiary
</Button>
```

### <a id="focus-hover"></a>Focus & Hover

By default, disabled buttons are not focusable and cannot react to hover events. This can cause accessibility issues by making them entirely invisible to assistive technologies, and prevents the use of Tooltips to explain why the action is not available. This can be addressed by enabling the feature flag `accessibleDisabledButtons`, which allows you to focus and hover on disabled buttons, while preventing them from being triggered:

`Flow`

```properties
# Add this line to src/main/resources/vaadin-featureflags.properties
com.vaadin.experimental.accessibleDisabledButtons=true
```

`Lit & React`

```js
// Set before any button is added to the DOM
window.Vaadin.featureFlags.accessibleDisabledButtons = true;
```

### <a id="alternatives-to-disabling"></a>Alternatives to Disabling

The most obvious alternative to disabling a button is to hide it. This reduces UI clutter, but can cause confusion since the user won’t know where to watch for the button once the action it represents becomes available. There’s also a risk of undesired layout shifts in the UI when the button appears.

Another option is to keep the button visible and enabled, but show an error, e.g. using a Notification, when it’s clicked. This option is best combined with some custom styling of the button to give the user a hint that the action is unavailable.

### <a id="prevent-multiple-clicks"></a>Prevent Multiple Clicks

Buttons can be configured to be disabled when clicked. This can be useful especially for actions that take a bit longer to perform. Not only does this avoid the need for special handling of additional clicks while the action is in progress, but it also communicates to the user that the action was received successfully and is being processed.

**Lit** — `button-disable-long-action.ts`

```html
<vaadin-horizontal-layout theme="spacing" style="align-items: center;">
  <vaadin-button
    ?disabled=${this.isDisabled}
    @click=${() => {
      this.isDisabled = true;
      this.fakeProgressBar.simulateProgress();
    }}
    style="flex: none;"
    >Perform Action</vaadin-button
  >
  <fake-progress-bar
    @progress-end=${() => {
      this.isDisabled = false;
    }}
  ></fake-progress-bar>
</vaadin-horizontal-layout>
```

**Flow** — `ButtonDisableLongAction.java`

```java
Button button = new Button("Perform Action");
FakeProgressBar progressBar = new FakeProgressBar();
button.setDisableOnClick(true);
button.addClickListener(event -> progressBar.simulateProgress());

progressBar.addProgressEndListener(event -> {
    button.setEnabled(true);
});
```

**React** — `button-disable-long-action.tsx`

```tsx
const progress = useSignal(-1);

useEffect(() => {
  if (progress.value >= 1) {
    progress.value = -1;
  } else if (progress.value >= 0) {
    setTimeout(() => {
      progress.value += 0.005;
    }, 25);
  }
}, [progress.value]);

return (
  <HorizontalLayout theme="spacing" style={{ alignItems: 'center' }}>
    <Button
      disabled={progress.value >= 0}
      onClick={() => {
        progress.value = 0;
      }}
    >
      Perform Action
    </Button>

    <ProgressBar value={progress.value} />
  </HorizontalLayout>
);
```

## <a id="focus"></a>Focus

As with other components, the focus ring is only rendered when the button is focused by keyboard or programmatically.

**Lit** — `button-focus.ts`

```html
<vaadin-button focus-ring>Keyboard focus</vaadin-button>
```

**React** — `button-focus.tsx`

```tsx
<Button focus-ring>Keyboard focus</Button>
```

### <a id="auto-focus"></a>Auto Focus

Buttons can receive keyboard focus automatically when the UI in which they appear is rendered.

**Lit**

```html
<vaadin-button autofocus>Button</vaadin-button>
```

**Flow**

```java
Button button = new Button("Button");
button.setAutofocus(true);
```

**React**

```html
<Button autofocus>Button</Button>
```

## <a id="keyboard-usage"></a>Keyboard Usage

A focused button can be triggered with `Enter` or `Space`.

## <a id="best-practices"></a>Best Practices

Below are some best practice recommendations related to buttons and their labels.

### <a id="button-labels"></a>Button Labels

A label should describe the action, preferably using active verbs, such as *"View Details"* rather than *"Details"*. To avoid ambiguity, also specify the object of the verb, such as *"Save Changes"* instead of *"Save"*. They also should be brief, ideally less than three words or twenty-five characters.

Button groups representing options, such as the buttons of a [Confirm Dialog](https://vaadin.com/docs/latest/components/confirm-dialog.md), should state what each option represents (e.g., *"Save Changes"*). Don’t label a button *"Yes"* since that requires the user to read the question being asked. It’ll increase the risk of selecting the wrong option.

Use ellipsis (i.e., …) when an action is not immediate, but requires more steps to complete. This is useful, for example, for destructive actions like *"Delete…​"* when a [Confirm Dialog](https://vaadin.com/docs/latest/components/confirm-dialog.md) is used to confirm the action before it’s executed.

### <a id="aria-labels"></a>ARIA Labels

The `aria-label` attribute can be used to provide a separate label for accessibility technologies (AT), such as screen readers. This is important, for example, for icon-only buttons that lack a visible label.

A button with a regular, visible label can also benefit from a separate `aria-label` to provide more context that may otherwise be difficult for an AT user to perceive. In the example here, each button’s `aria-label` specifies which email address is removed:

**Lit** — `button-labels.ts`

```html
<vaadin-form-layout auto-responsive>
  <vaadin-horizontal-layout theme="spacing">
    <vaadin-email-field
      id="primary-email"
      label="Primary email address"
      .value="${this.primaryEmail}"
      @value-changed="${(event: EmailFieldValueChangedEvent) => {
        this.primaryEmail = event.detail.value;
      }}"
    ></vaadin-email-field>
    <vaadin-button
      arial-label="Remove primary email address"
      @click="${() => {
        this.primaryEmail = '';
      }}"
    >
      Remove
    </vaadin-button>
  </vaadin-horizontal-layout>

  <vaadin-horizontal-layout theme="spacing">
    <vaadin-email-field
      id="secondary-email"
      label="Secondary email address"
      .value="${this.secondaryEmail}"
      @value-changed="${(event: EmailFieldValueChangedEvent) => {
        this.secondaryEmail = event.detail.value;
      }}"
    ></vaadin-email-field>
    <vaadin-button
      arial-label="Remove secondary email address"
      @click="${() => {
        this.secondaryEmail = '';
      }}"
    >
      Remove
    </vaadin-button>
  </vaadin-horizontal-layout>
</vaadin-form-layout>
```

**Flow** — `ButtonLabels.java`

```java
Button clearPrimaryEmail = new Button("Remove", event -> {
    emailField.setValue("");
});
clearPrimaryEmail.setAriaLabel("Remove primary email address");

Button clearSecondaryEmail = new Button("Remove", event -> {
    secondaryEmailField.setValue("");
});
clearSecondaryEmail.setAriaLabel("Remove secondary email address");
```

**React** — `button-labels.tsx`

```tsx
<FormLayout autoResponsive>
  <HorizontalLayout theme="spacing" style={{ alignItems: 'baseline' }}>
    <EmailField
      label="Primary email address"
      value={primaryEmail.value}
      onValueChanged={(event) => {
        primaryEmail.value = event.detail.value;
      }}
    />
    <Button
      onClick={() => {
        primaryEmail.value = '';
      }}
    >
      Remove
    </Button>
  </HorizontalLayout>

  <HorizontalLayout theme="spacing" style={{ alignItems: 'baseline' }}>
    <EmailField
      label="Secondary email address"
      value={secondaryEmail.value}
      onValueChanged={(event) => {
        secondaryEmail.value = event.detail.value;
      }}
    />
    <Button
      onClick={() => {
        secondaryEmail.value = '';
      }}
    >
      Remove
    </Button>
  </HorizontalLayout>
</FormLayout>
```

### <a id="buttons-in-forms"></a>Buttons in Forms

Buttons in forms should be placed below the form with which they’re associated. They should be aligned left, with the primary action first, followed by other actions, in order of importance.

**Lit** — `button-form.ts`

```html
<vaadin-vertical-layout theme="spacing">
  <vaadin-form-layout .responsiveSteps="${[{ columns: 2 }]}">
    <vaadin-text-field label="First name" value="John"></vaadin-text-field>
    <vaadin-text-field label="Last name" value="Smith"></vaadin-text-field>
    <vaadin-email-field
      label="Email address"
      value="john.smith@example.com"
      colspan="2"
    ></vaadin-email-field>
  </vaadin-form-layout>

  <vaadin-horizontal-layout theme="spacing">
    <vaadin-button theme="primary">Create account</vaadin-button>
    <vaadin-button theme="secondary">Cancel</vaadin-button>
  </vaadin-horizontal-layout>
</vaadin-vertical-layout>
```

**Flow** — `ButtonForm.java`

```java
TextField firstNameField = new TextField("First name", "John", "");
TextField lastNameField = new TextField("Last name", "Smith", "");
EmailField emailField = new EmailField("Email address");
emailField.setValue("john.smith@example.com");
FormLayout formLayout = new FormLayout(firstNameField, lastNameField,
        emailField);
formLayout.setResponsiveSteps(new ResponsiveStep("0", 2));
formLayout.setColspan(emailField, 2);

Button createAccount = new Button("Create account");
createAccount.addThemeVariants(ButtonVariant.PRIMARY);
Button cancel = new Button("Cancel");

HorizontalLayout buttonLayout = new HorizontalLayout(createAccount,
        cancel);
```

**React** — `button-form.tsx`

```tsx
<VerticalLayout theme="spacing">
  <FormLayout responsiveSteps={[{ columns: 2 }]}>
    <TextField label="First name" value="John" />
    <TextField label="Last name" value="Smith" />
    <EmailField label="Email address" value="john.smith@example.com" data-colspan="2" />
  </FormLayout>

  <HorizontalLayout theme="spacing">
    <Button theme="primary">Create account</Button>
    <Button theme="secondary">Cancel</Button>
  </HorizontalLayout>
</VerticalLayout>
```

### <a id="buttons-in-dialogs"></a>Buttons in Dialogs

Buttons in dialogs should be placed at the bottom of the dialog and aligned right. Primary action should be last, preceded by other actions. Dangerous actions should be aligned left, to avoid accidental clicks, especially if no confirmation step is included.

**Lit** — `button-dialog.ts`

```html
<vaadin-vertical-layout theme="spacing" style="align-items: stretch;">
  <vaadin-form-layout .responsiveSteps="${[{ columns: 2 }]}">
    <vaadin-text-field label="First name" value="John"></vaadin-text-field>
    <vaadin-text-field label="Last name" value="Smith"></vaadin-text-field>
    <vaadin-email-field
      label="Email address"
      value="john.smith@example.com"
      colspan="2"
    ></vaadin-email-field>
  </vaadin-form-layout>

  <vaadin-horizontal-layout theme="spacing wrap">
    <vaadin-button theme="error">Delete</vaadin-button>
    <vaadin-button slot="end">Cancel</vaadin-button>
    <vaadin-button slot="end" theme="primary">Create account</vaadin-button>
  </vaadin-horizontal-layout>
</vaadin-vertical-layout>
```

**Flow** — `ButtonDialog.java`

```java
TextField firstNameField = new TextField("First name", "John", "");
TextField lastNameField = new TextField("Last name", "Smith", "");
EmailField emailField = new EmailField("Email address");
emailField.setValue("john.smith@example.com");
FormLayout formLayout = new FormLayout(firstNameField, lastNameField,
        emailField);
formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));
formLayout.setColspan(emailField, 2);

Button delete = new Button("Delete");
delete.addThemeVariants(ButtonVariant.ERROR);
delete.getStyle().set("margin-inline-end", "auto");

Button cancel = new Button("Cancel");

Button createAccount = new Button("Create account");
createAccount.addThemeVariants(ButtonVariant.PRIMARY);

HorizontalLayout buttonLayout = new HorizontalLayout(delete);
buttonLayout.addToEnd(cancel, createAccount);
buttonLayout.addThemeNames("wrap");
```

**React** — `button-dialog.tsx`

```tsx
<VerticalLayout theme="spacing" style={{ alignItems: 'stretch' }}>
  <FormLayout responsiveSteps={[{ columns: 2 }]}>
    <TextField label="First name" value="John" />
    <TextField label="Last name" value="Smith" />
    <EmailField label="Email address" value="john.smith@example.com" data-colspan="2" />
  </FormLayout>

  <HorizontalLayout theme="spacing wrap">
    <Button theme="error">Delete</Button>
    <Button slot="end">Cancel</Button>
    <Button slot="end" theme="primary">
      Create account
    </Button>
  </HorizontalLayout>
</VerticalLayout>
```

### <a id="global-vs-selection-specific-actions"></a>Global vs. Selection-Specific Actions

In lists of selectable items — such as in a [Grid](https://vaadin.com/docs/latest/components/grid.md) — that provide actions applicable to the selected item, buttons for selection-specific actions should be placed apart from global actions that aren’t selection-specific. They should be located below the list of selectable items.

In the example below, the global *Add User* action is separated from the selection-specific actions below the Grid:

**Lit** — `button-grid.ts`

```html
<vaadin-vertical-layout theme="spacing" style="align-items: stretch;">
  <vaadin-horizontal-layout style="align-items: center;">
    <h2>Users</h2>
    <vaadin-button slot="end">Add user</vaadin-button>
  </vaadin-horizontal-layout>

  <vaadin-grid
    .items="${this.items}"
    @selected-items-changed="${(event: GridSelectedItemsChangedEvent<Person>) => {
      this.selectedItems = event.target ? [...event.detail.value] : this.selectedItems;
    }}"
  >
    <vaadin-grid-selection-column
      auto-select
      @select-all-changed="${(event: GridSelectionColumnSelectAllChangedEvent) => {
        this.selectedItems = event.detail.value ? this.items : this.selectedItems;
      }}"
    ></vaadin-grid-selection-column>
    <vaadin-grid-column path="firstName"></vaadin-grid-column>
    <vaadin-grid-column path="lastName"></vaadin-grid-column>
    <vaadin-grid-column path="email"></vaadin-grid-column>
  </vaadin-grid>

  <vaadin-horizontal-layout theme="spacing wrap">
    <vaadin-button ?disabled="${this.selectedItems.length !== 1}">Edit profile</vaadin-button>
    <vaadin-button ?disabled="${this.selectedItems.length !== 1}">
      Manage permissions
    </vaadin-button>
    <vaadin-button ?disabled="${this.selectedItems.length !== 1}">
      Reset password
    </vaadin-button>
    <vaadin-button slot="end" theme="error" ?disabled="${this.selectedItems.length === 0}">
      Delete
    </vaadin-button>
  </vaadin-horizontal-layout>
</vaadin-vertical-layout>
```

**Flow** — `ButtonGrid.java`

```java
H2 users = new H2("Users");
Button addUser = new Button("Add user");
HorizontalLayout header = new HorizontalLayout(users);
header.addToEnd(addUser);
header.setAlignItems(Alignment.CENTER);
header.getThemeList().clear();

Button editProfile = new Button("Edit profile");
editProfile.setEnabled(false);

Button managePermissions = new Button("Manage permissions");
managePermissions.setEnabled(false);

Button resetPassword = new Button("Reset password");
resetPassword.setEnabled(false);

Button delete = new Button("Delete");
delete.setEnabled(false);
delete.addThemeVariants(ButtonVariant.ERROR);

Grid<Person> grid = new Grid<>(Person.class, false);
grid.setSelectionMode(Grid.SelectionMode.MULTI);
grid.addColumn(Person::getFirstName).setHeader("First name");
grid.addColumn(Person::getLastName).setHeader("Last name");
grid.addColumn(Person::getEmail).setHeader("Email");
grid.addSelectionListener(selection -> {
    int size = selection.getAllSelectedItems().size();
    boolean isSingleSelection = size == 1;
    editProfile.setEnabled(isSingleSelection);
    managePermissions.setEnabled(isSingleSelection);
    resetPassword.setEnabled(isSingleSelection);

    delete.setEnabled(size != 0);
});

HorizontalLayout footer = new HorizontalLayout(editProfile,
        managePermissions, resetPassword);
footer.addToEnd(delete);
footer.addThemeNames("wrap");
```

**React** — `button-grid.tsx`

```tsx
const items = useSignal<Person[]>([]);
const selectedItems = useSignal<Person[]>([]);

useEffect(() => {
  getPeople().then(({ people }) => {
    items.value = people;
  });
}, []);

return (
  <VerticalLayout theme="spacing" style={{ alignItems: 'stretch' }}>
    <HorizontalLayout style={{ alignItems: 'center' }}>
      <h2>Users</h2>
      <Button slot="end">Add user</Button>
    </HorizontalLayout>
    <Grid
      items={items.value}
      selectedItems={selectedItems.value}
      onSelectedItemsChanged={({ detail: { value } }) => {
        selectedItems.value = value;
      }}
    >
      <GridSelectionColumn />
      <GridColumn path="firstName" />
      <GridColumn path="lastName" />
      <GridColumn path="email" />
    </Grid>

    <HorizontalLayout theme="spacing wrap">
      <Button disabled={selectedItems.value.length !== 1}>Edit profile</Button>
      <Button disabled={selectedItems.value.length !== 1}>Manage permissions</Button>
      <Button disabled={selectedItems.value.length !== 1}>Reset password</Button>
      <Button slot="end" theme="error" disabled={selectedItems.value.length === 0}>
        Delete
      </Button>
    </HorizontalLayout>
  </VerticalLayout>
);
```

## <a id="related-components"></a>Related Components

| Component                                                         | Usage Recommendation                                                                                                                      |
| ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Anchor                                                            | Use anchor elements instead of buttons for navigation links.                                                                              |
| [Menu Bar](https://vaadin.com/docs/latest/components/menu-bar.md) | Overlay menus with items that trigger actions. This can also be used for single "menu buttons" and "button groups" without overlay menus. |

`8E1BE28B-D5F0-490C-A8FA-82975D9A3B43`
