Dieser Artikel ist auch auf Deutsch verfügbar

User interfaces in web applications always consist of HTML and CSS, which describe the structure and appearance of the application. At this point, it doesn’t really matter whether we produce the HTML already on the server and send it directly to the browser or generate it within the browser via JavaScript.

In the past, the HTML and CSS were typically created page-by-page in the form of images using Photoshop or similar tools. These were then translated into HTML and CSS within the application, in many cases with pixel precision. Even with this approach, components were already used, even if only implicitly and generally hidden from view. If an element, such as a button, appeared on multiple pages, it would usually be standardized rather than designed entirely from scratch each time. Over time, we learned in this way that it was more useful and efficient to specify these components and then combine them to build the pages of the application.

Component libraries are created for this purpose, ideally through collaboration between people with experience in user experience, user interface design and front end development. There are many examples of such libraries. Perhaps the best known implementations are Bootstrap and Material UI. But there are also other open, inspectable component libraries such as Shopify Polaris, SAP UI5, and INNOQ.

Such a component library is expected to fulfil a number of purposes. First, it provides an overview of all available – and therefore usable – components. Each component then consists of the documentation, its interface, and the HTML and CSS required to use it in the actual application. The documentation should cover not only technical aspects but, in particular, the use cases for which the component is intended. Depending on the component library, it may even offer ready-made libraries for a specific template engine, such as Material UI for React.

The component interface

As already indicated, each component also includes an interface alongside the actual markup and styling. As an example, let’s take a look at the card component from Bootstrap, shown in Figure 1.

Fig. 1: Bootstrap card example

This is primarily a visual component that essentially places a block of text in a frame to emphasize the block. But it is also possible to use a header and footer. And the content is not limited to pure text. It can also contain components, in this case a button.

If we want to use exactly this card in an application, we must copy the documented HTML markup (see Listing 1) into the application. This markup consists of technical details, such as the specific CSS classes, and the actual content, including the text itself and the button. Before we blindly copy this code into our application, we have to consult the component documentation to work out which parts actually belong to the component itself, which parts are variable, and what the individual content is.

<div class="card text-center">
  <div class="card-header">
  <div class="card-body">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text ...</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  <div class="card-footer text-body-secondary">
    2 days ago
Listing 1: HTML markup for Bootstrap card example

If we need this card at multiple locations, we can enter the required markup at every point, generally via copy and paste, and adapt each instance with individual content. Of course, this poses the usual problems of such a method. Above all, copying and modifying the code is inefficient and error-prone. Plus, changing the markup of the component means a lot of work. Then we have to find every copy and touch it up. Anyone who ever had to migrate from Bootstrap 4 to 5 knows what I am talking about. In short, this approach runs up hard against the principle of Don’t Repeat Yourself.

For this reason, all newer JavaScript-based template engines, such as Angular, lit and JSX, include support for encapsulating such components. Not only does this free us from the need to copy large amounts of HTML markup, it also provides us with a clear and defined interface for using the component. Not only that, but the implementation can now be changed within the component without creating additional work everywhere the component is used. Depending on the specific technology, we can even change the interface and let a compiler conveniently help us find all the places that need to be changed.

Classic template engines

In contrast to the new JavaScript-based template engines mentioned above, “classic” template engines, such as jinjava, mustache.java and Thymeleaf, are generally used on the server side and don’t offer direct abstraction for writing and using components. Depending on the engine, however, we can make use of the available means to achieve a similar result. But that is not always possible.

To determine what works in which engine, and how, as well as to evaluate which engine is best suited for consistently implementing such a component approach, a few of my colleagues at INNOQ created a component challenge. Currently, the challenge defines six components to be implemented. These example components pose typical challenges that we frequently encounter in our projects. They are detailed below.

Components of the challenge

The first component is the Badge, a visual component that displays text on a colored background and frequently appears with rounded corners. Within the challenge, this serves primarily to check whether it is even possible to define and reuse a component within a template engine. Listing 2 shows the markup to be created and, as a comment, one possible syntax to be used.

<!-- <badge type="alert">Critical!</badge> -->
<span class="badge bg-danger">Critical!</span>
Listing 2: Badge component

The second component is a Button. This is based on one of the mightiest native HTML elements, the <button>. Even without making use of ARIA, this has a very large number of attributes. This component therefore checks whether the template engine allows a mechanism for passing essentially arbitrary attributes without having to explicitly define each of them in the interface. At first, that might sound like a hack or workaround, but it is often necessary in practice. Listing 3 shows an example of what the component should look like. The challenge itself lists even more combinations.

<!-- <mybutton cta class="text-uppercase">Click me!</mybutton> -->
<button class="btn btn-primary text-uppercase">Click me!</button>
Listing 3: Button component

Next up in the challenge is the Card component discussed previously. Its purpose is mainly to check whether it is possible to use named blocks. A card consists here of the two optional blocks header and footer plus a mandatory main block. The most important thing is that, in addition to accepting pure text, these blocks must also allow us to pass HTML markup, including additional custom components. This is essential for enabling a composition of various components. Listing 4 shows what a card might look like.

  Some content with a <badge>Badge</badge>
  <slot="footer">Footer with <mybutton>Button</mybutton></slot>
<section class="card">
  <div class="card-body">
    <p class="card-body">
      Some content with a <span class="badge bg-default">Badge</span>
  <div class="card-footer">
    Footer with <button class="btn">Button</button>
Listing 4: Card component

This List component is primarily intended to check whether it is possible to utilize more complex data types from the host environment within the component, such as lists or maps. This component can also be used to see if specific types can be forced and, if necessary, verified at compile time as well. Listing 5 contains an example of this.

<list ratio="1:3" items="{ 'key': 'value', 'more': 'content' }">
<dl class="row">
  <dt class="col-sm-3">key</dt>
  <dd class="col-sm-9">value</dd>
  <dt class="col-sm-3">more</dt>
  <dd class="col-sm-9">content</dd>
Listing 5: List component

Unlike the others, the Magic-Header component requires a state. This is needed to automatically assign the right level to headings within the component without having to specify them explicitly on use. Listing 6 shows what this looks like.

  <header>Heading (h1)</header>
    <header>Heading (h2)</header>
  <header>Heading (h1 again)</header>
<h1>Heading (h1)</h1>
<h2>Heading (h2)</h2>
<h1>Heading (h1 again)</h1>
Listing 6: Magic header component

The sixth and final component is the Field group. This doesn’t actually pose a new challenge that was not already tested by the previous components. Rather, it serves as a more complex example from the real world since such a component is required in almost every project. This component displays an <input> field, including a <label> and optional validation errors, within a <form>.

It can optionally also be implemented in an extended version, which should be able to interact heavily with the employed web framework and support aspects such as validation errors and internationalization.

This column explored the concept of UI components. In summary, these are encapsulated and therefore reusable elements of a user interface. They allow us to efficiently build user interfaces that are easy to maintain, in contrast to the copy and paste approach.

Because many template engines typically used in Java do not provide a direct concept for such components, we also learned about the INNOQ component challenge. This currently defines six example components in order to evaluate whether and how well the component approach works in a specific template engine. In addition to the challenge, a number of implementations are also provided.

Even if this column was somewhat more theoretical and less focused on Java, I hope you have learned something. I would also like to take this opportunity to express my thanks to my colleagues FND, Joachim, Lucas, Michael, and especially Till. This article contains the distilled essence of many conversations with all of you. I hope it lives up to your standards.