Button
The Button component allows users to perform actions. It comes in several different style variants, and supports icons in addition to 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 recommendations |
---|---|
Primary |
|
Secondary |
|
Tertiary |
|
Danger / Error Variants
A style for distinguishing actions related to dangers, warnings, or errors.
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);
Use this style for:
-
Dangerous actions, such as those that will lose or destroy data.
-
Primary danger buttons should only be used when the dangerous action is the most likely action, such as the affirmative Delete action in a deletion confirmation dialog.
-
Secondary and Tertiary variants can also be used for actions related to current errors (such as resolving them or viewing their details).
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 recommendations |
---|---|
Large | For especially important call-to-action buttons, where extra emphasis is needed. |
Normal | Default size. |
Small | Compact option for cramped parts of the UI, if a Tertiary variant is not deemed appropriate. |
Miscellaneous Style Variants
The Tertiary Inline variant omits all white-space around the label, which is useful for embedding a Button as part of text content or another component. It should not be confused with a Link, however.
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 due to its similarity to non-interactive text elements.
Buttons With Icons
Buttons can have icons instead of text, or on either side in addition to text:
new tab
Button plusButton = new Button(new Icon(VaadinIcon.PLUS));
plusButton.addThemeVariants(ButtonVariant.LUMO_ICON);
plusButton.getElement().setAttribute("aria-label", "Add item");
Button closeButton = new Button(new Icon(VaadinIcon.CLOSE_SMALL));
closeButton.addThemeVariants(ButtonVariant.LUMO_ICON);
closeButton.getElement().setAttribute("aria-label", "Close");
Button arrowLeftButton = new Button("Left", new Icon(VaadinIcon.ARROW_LEFT));
Button arrowRightButton = new Button("Right", new Icon(VaadinIcon.ARROW_RIGHT));
arrowRightButton.setIconAfterText(true);
Usage recommendations:
-
Use icons sparingly. Most actions are difficult to reliably represent with icons, and the benefit of icons in addition to text should be weighed against the additional visual noise this creates.
-
Icon-only buttons should be primarily used for extremely common recurring actions with highly standardized, universally understood icons (for example: a cross for close), and for actions that are repeated (for example: in lists and tables).
-
Icon-only buttons should provide a textual alternative for screen readers using the
aria-label
attribute.
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.
|
Buttons With Images
Images can be used similarly to icons. See icon usage recommendations.
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 are not 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 (such as when interactive UI elements are focused 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 an unavailable action entirely 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 indicates the presence of one or more invalid fields.
-
As a hidden button doesn’t occupy any space in the UI, toggling its visibility can cause unwanted changes in the layout of other elements.
Showing an Error on Click
As an alternative to hiding or disabling buttons, unavailable actions can instead be configured to show an error message when the button is clicked (using a Notification or an adjacent inline text element). This approach is the most accessible option, but may cause frustration in users who expect unavailable actions to be somehow distinguished from available actions.
Prevent Multiple Clicks
Buttons can be configured to become disabled automatically when clicked.
This can be especially useful for actions that take a bit longer to perform. Not only does this avoid the need for special handling of clicks while the action is in progress, but it also communicates to the user that the action was successfully received 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
Similarly to input fields, the focus ring is only rendered when the button is focused by keyboard or programmatically.
new tab
import { customElement, html, LitElement } from 'lit-element';
import '@vaadin/vaadin-button/vaadin-button';
import { applyTheme } from 'Frontend/generated/theme';
@customElement('button-focus')
export class Example extends LitElement {
protected createRenderRoot() {
const root = super.createRenderRoot();
// Apply custom theme (only supported if your app uses one)
applyTheme(root);
return root;
}
render() {
return html`
<vaadin-button focus-ring>Keyboard focus</vaadin-button>
`;
}
}
Best Practices
Button Labels
-
The label should describe the action, preferably using active verbs, such as "View details" rather than just "Details".
-
In cases where there is a risk for ambiguity, also specify the object of the verb, such as "Save changes" instead of "Save".
-
Button groups representing options, such as the buttons of a Confirm Dialog, should state what each option represents, such as "Save changes" instead of "Yes", as the latter forces the user to read the question being asked, and increases the risk of selecting the wrong option.
-
Keep labels short, ideally less than three words or 25 characters.
-
Use ellipsis (…) 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) like screen readers.
This is important, for example, for icon-only buttons that lack a visible label.
Buttons with regular, visible labels can also benefit from separate aria-label
s to provide more context that may otherwise be difficult for the AT user to perceive. In the example below, the buttons aria-label
specify which email address will be removed.
new tab
Button clearPrimaryEmail = new Button("Remove", event -> {
emailField.setValue("");
});
clearPrimaryEmail.getElement().setAttribute("aria-label", "Remove primary email address");
Button clearSecondaryEmail = new Button("Remove", event -> {
secondaryEmailField.setValue("");
});
clearSecondaryEmail.getElement().setAttribute("aria-label", "Remove secondary email address");
Buttons in Forms
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 should be placed below the form they’re associated with.
-
Buttons should be aligned left.
-
Primary action first, followed by other actions, in order of importance.
Buttons in Dialogs
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);
-
Buttons should be placed at the bottom of the dialog.
-
Buttons should be aligned right.
-
Primary action last, preceded by other actions.
-
Dangerous actions should be aligned left, to avoid accidental clicks, especially if no confirmation step is included.
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 separately from "global" non-selection-specific actions, preferably 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");