Why this guide?

In Vaadin you can do everything with Java and you do not have to know about HTML or CSS. Well, that is almost true. If you use for example Vaadin Grid then you’ll soon want to put buttons or icons into that grid. The way to do that is by using LitRenderers and Vaadin’s documentation does a great job of describing why they are superior to ComponentRenderers from performance point of view.

However, the problem with LitRenderers is that you need to construct the HTML yourself while Vaadin Flow is all about not having to do that.

This guide is intended to complement (not duplicate) Vaadin’s own documentation. It contains recipes which I could not find in Vaadin’s documentation .. or at least could not find easily. You are not supposed to be able to read this guide on its own as it doesn’t explain the concepts already described in Vaadin’s own documentation. For example, you should already be familiar with the concept of event handling in LitRenderer. Therefore: Read Vaadin’s documentation first:

The examples in this document have been created with Vaadin 23.

Quick recipes

Create an icon button

Creating a button with an icon in it: See Icons.

Create an icon button group

Creating an array of icon buttons: See Icon button groups (multiple icon buttons in a cell)

Create simple tooltip on an icon button

If you are like most people you will want to know what a button does before clicking on it. This is where tooltips helps. See Simple tooltip.

Icons

Vaadin and Lumo icons

Vaadin and Lumo icons must use an empty <vaadin-icon> element to render the icon, like this:

<vaadin-icon icon="vaadin:star"></vaadin-icon>

or

<vaadin-icon icon="lumo:clock"></vaadin-icon>

As a whole, a LitRenderer html template with an icon button with a car from the Vaadin icon set, would look like this:

<vaadin-button title="Order a taxi" theme="icon small" @click="${orderTaxi}">
    <vaadin-icon icon="vaadin:car"></vaadin-icon>
</vaadin-button>
right arrow
car icon button

Font icons

Font icons typically use an empty <span> element to render the icon, like this:

<span class="la la-pen"></span>

Alternatively, some developers use an empty <i> tag for the same purpose. You can find heated debates on the Internet about which tag is most suitable. Vaadin recommends the <span> element so that is what we use here.

As a whole, a LitRenderer html template with an icon button with a pen would look like this:

<vaadin-button title="Edit person" theme="icon small" @click="${editTheItem}">
    <span class="la la-pen"></span>
</vaadin-button>
right arrow
pen icon button

Icon button groups (multiple icon buttons in a cell)

Using multiple icons in a cell is common for "action buttons". The following Lit template will render 3 icons in a single cell:

<vaadin-horizontal-layout theme="spacing-xs" style="align-items: center;"> (1)
    <vaadin-button title="Edit person" theme="icon small" @click="${editTheItem}">
        <span class="la la-pen"></span>
    </vaadin-button>
    <vaadin-button title="Delete person" theme="icon small" @click="${removeTheItem}">
        <span class="la la-trash"></span>
    </vaadin-button>
    <vaadin-button title="Star person" theme="icon small" @click="${starTheItem}">
        <vaadin-icon icon="vaadin:star"></vaadin-icon>
    </vaadin-button>
</vaadin-horizontal-layout>
1 Enclose the button icons in an <vaadin-horizontal-layout>. This allows to align the icons on the horizontal axis and it allows to put some space between the button icons.
right arrow
icon button group

When put into a Grid using a LitRenderer, it will look like this:

img1

 

The above example mixes icons from different icon sets (Vaadin icons and Line Awesome icons). For aesthetics this is rarely a good idea. If you look closely you’ll notice that the star icon (from Vaadin collection) is slightly wider than the other icons.

Spacing between icon buttons

First of all you will need to wrap your <vaadin-button> buttons in a <vaadin-horizontal-layout> so that you create a horizontal button array. For actually setting the spacing, you have two options:

  • Using a Vaadin Lumo preset value for theme. Example:

<vaadin-horizontal-layout style="align-items: center;" theme="spacing-xs">
  <!-- icon buttons here -->
</vaadin-horizontal-layout>

You can use any of the following values: spacing-xs (extra small), spacing-s (small), spacing-m (medium), spacing-l (large) and spacing-xl (extra large). However, only spacing-xs really looks good. These presets were not made with button icons in mind which explains why they are less useful in this context.

  • Using explicit styling with the column-gap CSS property. Example:

<vaadin-horizontal-layout style="align-items: center; column-gap: 0.3rem;">
  <!-- icon buttons here -->
</vaadin-horizontal-layout>

Simple tooltip

You can use the html title attribute for simple tooltips on your icon buttons. The title attribute is a native html feature, it has nothing to do with Vaadin. As a poor man’s tooltip, it does the job.

The value of the title attribute isn’t interpreted as HTML by the browser, rather it is displayed as-is. However, linefeeds are allowed and must be represented by a &#10;.

Here is an example:

<vaadin-button title="Add to favorites.&#10;&#10;The person will be added in your Favorites folder under &#34;Buddies&#34;"
               theme="icon small" @click="${starTheItem}">
        <vaadin-icon icon="vaadin:star"></vaadin-icon>
</vaadin-button>

which in a Grid will look something like this:

Grid example
Do not use more than 1-3 lines of text for a title tooltip. In most cases a single word will be enough. If you have a need for something more elaborate then have a look at the Tooltip feature introduced in Vaadin 23.3.

 

By the way: The same trick can be used with pure-Java buttons, like this:

Button myButton = new Button(new Icon("lumo", "search"));
myButton.addThemeVariants(ButtonVariant.LUMO_ICON);
myButton.getElement().setAttribute("title", "Search for music");

Suppose you want to have clickable links inside a grid, perhaps as a way to navigate from one Grid to another. Like this:

clickable link

Here is how to do it with <vaadin-button>, styled so that it looks like a link.

grid.addColumn(
        LitRenderer.<Person>of(
                    """
                    <vaadin-button title="Go to ..." theme="tertiary-inline small"
                                   @click="${clickHandler}">
                        <span style="text-decoration: underline; cursor: pointer;">${item.id}</span>
                    </vaadin-button>"""
                )
                .withProperty("id", Person::getId)
                .withFunction("clickHandler", person -> {
                    Notification.show("Link was clicked for Person #" + person.getId());
                })
    ).setHeader("Id");

Templating the template (Internationalization)

If you load text strings from resource bundles and those text strings are needed in the Lit html templates, for example as the caption for buttons or as the title attribute value, then you’ll soon find the urge to use a template engine to make your code easier to read. This will then be double templating (ouch!). I propose Apache Commons Text for this purpose as it has a handy class StringSubstitutor which is a simple templating engine.

Below is an example where the Lit html template it first passed through a StringSubstitutor and tooltip values are loaded from an I18NProvider.

@PageTitle("Persons")
@Route(value = "persons")
public class PersonsView extends VerticalLayout {

    private static final String I18N_PREFIX = "personsview.";

    private static final String BUTTON_ICONS_TEMPLATE = """ (1)
            <vaadin-horizontal-layout theme="spacing-xs" style="align-items: center;">
                <vaadin-button title="$((button.edit.title:-Edit))" theme="icon small" @click="${editTheItem}"> (2)
                    <span class="la la-pen"></span>
                </vaadin-button>
                <vaadin-button title="$((button.delete.title:-Delete))" theme="icon small" @click="${removeTheItem}">
                    <span class="la la-trash"></span>
                </vaadin-button>
                <vaadin-button title="$((button.star.title:-Add to favorites))" theme="icon small" @click="${starTheItem}">
                    <vaadin-icon icon="vaadin:star"></vaadin-icon>
                </vaadin-button>
            </vaadin-horizontal-layout>
                            """;

    public PersonsView() {

        StringSubstitutor stringSubstitutor =
                new StringSubstitutor(new InternationalStringLookup(), "$((", "))", '$'); (3)

        Grid<Person> grid = new Grid<>();
        List<Person> personList = getPersonData();


        // Add columns
        grid.addColumn(Person::getId).setHeader("Id");

        grid.addColumn(
                LitRenderer.<Person>of(stringSubstitutor.replace(BUTTON_ICONS_TEMPLATE))
                        .withFunction("editTheItem", person -> {
                            Notification.show("Editing Person #" + person.getId());
                        })
                        .withFunction("removeTheItem", person -> {
                            Notification.show("Deleting Person #" + person.getId());
                        })
                        .withFunction("starTheItem", person -> {
                            Notification.show("Starring Person #" + person.getId());
                        })
        ).setResizable(false).setAutoWidth(true).setFlexGrow(0);

        grid.addColumn(Person::getName).setHeader("Name");
        grid.addColumn(Person::getEmail).setHeader("E-Mail");
        grid.addColumn(Person::getBirthday).setHeader("Birthday");

        // Finalize and display
        grid.setItems(personList);
        add(grid);
    }

    /**
     * Sanitizes the value used for 'title' attribute on a html element.
     */
    private String sanitizeTitle(String unsanitizedTitle) {
        return unsanitizedTitle
                .replace("\n", "&#10;") (4)
                .replace("\"", "&#34;");
    }

    /**
     * String provider for Apache Commons Text 'StringSubstitutor'.
     */
    private class InternationalStringLookup implements StringLookup {

        @Override
        public String lookup(String key) {
            String absoluteKey = I18N_PREFIX + key;
            String translation = getTranslation(absoluteKey);
            return absoluteKey.startsWith(I18N_PREFIX + "title")
                    ? sanitizeTitle(translation) : translation;
        }
    }

    private List<Person> getPersonData() { // irelevant to the example
       ...
    }
}
1 HTML template used for Apache Commons Text StringSubstitor and eventually by Lit also. Since it is used by both we have to make sure the syntax for the two do not clash.
2 We provide default values by using :- syntax. However, because of the way that Vaadin’s I18NProvider works (it never returns a null value), these defaults will unfortunately never have any effect.
3 StringSubstitutor which uses $((foobar)) syntax for replacement variables. The default would be ${foobar} syntax but this is exactly what Lit uses too, so we have to use something else.
4 Since our values come from a resource bundle file, we need to sanitize a bit.

With the above example, we can use resource bundle files like the ones below:

translate_en_GB.properties file:

personsview.button.edit.title = Edit
personsview.button.delete.title = Delete
personsview.button.star.title = Add to favourites.\n\nThe person will be added under "Mates".

translate_en_US.properties file:

personsview.button.edit.title = Edit
personsview.button.delete.title = Remove
personsview.button.star.title = Add to favorites.\n\nThe person will be added under "Buddies".

Recommendations

Use Java 17

By using Java 17 (or later) you can put your LitRenderer html text into a text block. It will be much easier to read that way, in particular because you won’t have to escape the quotes.

Explicit closing tags in HTML

Always use explicit closing tags, even for empty elements. If you come from the world of XML then you may be tempted to do:

<vaadin-icon icon="vaadin:star"/>

instead of

<vaadin-icon icon="vaadin:star"></vaadin-icon>

Don’t do that. While it may work, it is not valid HTML.

Function name uniqueness

Don’t overthink the name of the function as in @click="${editItem}". It only needs to be unique within the given LitRenderer. This is because Vaadin automatically (behind your back) prefixes each LitRenderer with a unique ID.

Consider HTML injection attacks

With dynamic Lit templates you need to be careful with where values for the template come from. They may need to be sanitized first to avoid the possibility of harmful HTML injection attacks. With Spring Boot, you already have Spring’s HtmlUtils on your classpath. It can be used like this:

String safeTxt = HtmlUtils.htmlEscape(unsafeTxt, "UTF-8");

You can now use safeTxt safely in your Lit html template.