Button
- Usage
- Styling
- Styles
- Buttons with Icons
- Buttons with Images
- Disabled
- Focus
- Keyboard Usage
- Best Practices
- Related Components
The Button component allows users to perform actions. It comes in several different style variants and supports icons as well as text labels.
new tab
Button button = new Button("Button");
Paragraph info = new Paragraph(infoText());
button.addClickListener(clickEvent -> {
counter += 1;
info.setText(infoText());
});
Styles
The following variants can be used to distinguish between actions of different importance in the UI:
new tab
Button primaryButton = new Button("Primary");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
Button secondaryButton = new Button("Secondary");
Button tertiaryButton = new Button("Tertiary");
tertiaryButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
Variant | Usage Recommendation |
---|---|
Primary | This is the most important action in a view or in part of one. It’s the main closure or continuation action (e.g., Save) in a form or dialog. Avoid presenting the user with more than one at any time. |
Secondary | This is the default style recommended for most actions. It can be the alternate or negative closure actions (e.g., Cancel) in a form or dialog. |
Tertiary | These are lower-importance actions — especially in parts of the UI with less space, such as cards, or repeated actions for items in lists, tables, etc. Caution: this can be mistaken for non-interactive text. |
Danger & Error Variants
This is a style for distinguishing actions related to dangers, warnings, or errors. Dangerous actions would be those that lose or destroy data.
new tab
Button primaryButton = new Button("Primary");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY,
ButtonVariant.LUMO_ERROR);
Button secondaryButton = new Button("Secondary");
secondaryButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
Button tertiaryButton = new Button("Tertiary");
tertiaryButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY,
ButtonVariant.LUMO_ERROR);
Primary danger buttons should only be used when a dangerous action is the most likely action. An example of this would be the affirmative Delete action in a deletion confirmation dialog. Secondary and Tertiary variants can be used for actions related to current errors, such as resolving them or viewing their details.
Warning Variant
This is a style for distinguishing actions related to warnings: for example, in dialogs that are intended to warn the user, or to provide information that requires extra attention.
new tab
Button primaryButton = new Button("Primary");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY,
ButtonVariant.LUMO_WARNING);
Button secondaryButton = new Button("Secondary");
secondaryButton.addThemeVariants(ButtonVariant.LUMO_WARNING);
Button tertiaryButton = new Button("Tertiary");
tertiaryButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY,
ButtonVariant.LUMO_WARNING);
Size Variants
The following size variants are available for Button instances whose size needs to be different from the default:
new tab
Button largeButton = new Button("Large");
largeButton.addThemeVariants(ButtonVariant.LUMO_LARGE);
Button normalButton = new Button("Normal");
Button smallButton = new Button("Small");
smallButton.addThemeVariants(ButtonVariant.LUMO_SMALL);
Variant | Usage Recommendation |
---|---|
Large | For important call-to-action buttons — where more emphasis is needed. |
Normal | Default size. |
Small | Compact option for cramped parts of the UI — where a Tertiary variant isn’t deemed appropriate. |
Tip
|
Customize Default Button Size
Size variants should only be used in special cases. See Size and Space for details on how to change the default button size.
|
Miscellaneous Style Variants
The Tertiary Inline variant omits all white space around the label. This can be useful for embedding a Button as part of text content or another component. It shouldn’t be confused with a link.
new tab
Button tertiaryInlineButton = new Button("Tertiary inline");
tertiaryInlineButton
.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
The Success and Contrast variants should provide additional color options for buttons.
new tab
Button primaryButton = new Button("Primary");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY,
ButtonVariant.LUMO_SUCCESS);
Button secondaryButton = new Button("Secondary");
secondaryButton.addThemeVariants(ButtonVariant.LUMO_SUCCESS);
Button tertiaryButton = new Button("Tertiary");
tertiaryButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY,
ButtonVariant.LUMO_SUCCESS);
new tab
Button primaryButton = new Button("Primary");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY,
ButtonVariant.LUMO_CONTRAST);
Button secondaryButton = new Button("Secondary");
secondaryButton.addThemeVariants(ButtonVariant.LUMO_CONTRAST);
Button tertiaryButton = new Button("Tertiary (avoid)");
tertiaryButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY,
ButtonVariant.LUMO_CONTRAST);
The Tertiary + Contrast combination should be avoided because of similarity to non-interactive text elements.
Tip
|
Customize Default Button Colors
The standard Button colors can be adjusted using the Lumo color properties. Therefore, these variants shouldn’t be used to replace standard buttons only to achieve a different color.
|
Buttons with Icons
Buttons can have icons instead of text, or they can have icons along with text.
new tab
// Icon button using an aria-label to provide a textual alternative
// to screen readers
Button plusButton = new Button(new Icon(VaadinIcon.PLUS));
plusButton.addThemeVariants(ButtonVariant.LUMO_ICON);
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_SMALL));
closeButton.addThemeVariants(ButtonVariant.LUMO_ICON);
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);
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 can be added to provide a description of the action that the button triggers (see the Close button in the previous example).
Note
|
Icon-Only Button Style Variant
Use the icon / LUMO_ICON theme variant on icon-only buttons to reduce the white space on either side of the icon. The Flow Button component automatically applies the icon variant if the icon is the only child of the component.
|
Buttons with Images
Images on buttons can be used like icons. See the icon usage recommendations for more information.
new tab
Image img = new Image(src, "Vaadin logo");
img.setWidth("100px");
Button imgButton = new Button(img);
imgButton.addThemeVariants(ButtonVariant.LUMO_ICON);
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", and is excluded from the focus order. This may be useful when you don’t want interactive UI elements to receive the focus using the tab key.
new tab
Button primaryButton = new Button("Primary");
primaryButton.setEnabled(false);
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
Button secondaryButton = new Button("Secondary");
secondaryButton.setEnabled(false);
Button tertiaryButton = new Button("Tertiary");
tertiaryButton.setEnabled(false);
tertiaryButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
Hidden vs. Disabled
Hiding entirely an unavailable action is often preferable to a disabled button, as this reduces UI clutter. However, in certain situations, this can be problematic. If the user expects a button to be present — such as at the end of a form — hiding the button can cause confusion, even if the form clearly shows the presence of one or more invalid fields. Also, since a hidden button doesn’t occupy any space in the UI, toggling its visibility can cause unwanted changes in the layout of other elements.
Show Error on Click
As an alternative to hiding or disabling buttons, configure instead unavailable actions to show an error message when the button is clicked by using a Notification or an adjacent inline text element. This approach is the most accessible option, but may be frustrating to users who expect unavailable actions to be distinguished somehow from available actions.
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.
new tab
Button button = new Button("Perform Action");
FakeProgressBar progressBar = new FakeProgressBar();
button.setDisableOnClick(true);
button.addClickListener(event -> progressBar.simulateProgress());
progressBar.addProgressEndListener(event -> {
button.setEnabled(true);
});
Focus
As with other components, the focus ring is only rendered when the button is focused by keyboard or programmatically.
Best Practices
Below are some best practice recommendations related to buttons and their labels.
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, 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 is used to confirm the action before it’s executed.
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:
new tab
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");
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.
new tab
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.LUMO_PRIMARY);
Button cancel = new Button("Cancel");
HorizontalLayout buttonLayout = new HorizontalLayout(createAccount,
cancel);
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.
new tab
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.LUMO_ERROR);
delete.getStyle().set("margin-inline-end", "auto");
Button cancel = new Button("Cancel");
Button createAccount = new Button("Create account");
createAccount.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
HorizontalLayout buttonLayout = new HorizontalLayout(delete, cancel,
createAccount);
buttonLayout.getStyle().set("flex-wrap", "wrap");
buttonLayout.setJustifyContentMode(JustifyContentMode.END);
Global vs. Selection-Specific Actions
In lists of selectable items — such as in a Grid — 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:
new tab
H2 users = new H2("Users");
users.getStyle().set("margin", "0 auto 0 0");
Button addUser = new Button("Add user");
HorizontalLayout header = new HorizontalLayout(users, 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.LUMO_ERROR);
delete.getStyle().set("margin-inline-start", "auto");
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, delete);
footer.getStyle().set("flex-wrap", "wrap");