Web Frameworks: User Interface Development

Whether it is Top 40 or classical or R&B, artists and music have a recognizable look and feel. When looking at frameworks, some simply provide us with a bag of instruments, while others provide us with chord progressions and album covers we can customize.

This tooling forms the cornerstone of most web frameworks; an interactive user interface. In days of yore, we basically looked for a framework to provide us with a consistent set of instruments we knew we could play. Now we look for frameworks that allow us to create music tracks that we can then remix. In this article, we are going to dig through our collection, taking a closer look at what each framework provides to the band.

This blog post continues our series of exploring the considerations made when choosing a web application framework.

Element, component, widget

Almost all JavaScript application frameworks have a concept of elements, components, or widgets. These are essentially the same concept, simply with a different name. At a minimum they encapsulate the code needed to provide a visual representation with which the user can interact. In this post, we will refer to them as elements though frameworks may call them by a different moniker.

There are several elements that are native to browsers that are provided by HTML. For example the HTML tag of <button> will provide a user interface element of a button that can be clicked and has a label. Frameworks will often encapsulate these built-in elements and extend them with additional functionality.

Most frameworks have strong opinions about the role of elements. Some frameworks insist that elements be reactive, in the sense that they do not contain business logic, that they have properties or state that are set, and any changes to that state would be controlled external to that element. For example, if you had a textbox type element that could display to the user that the value they entered was not valid, the element would not decide the validity of this element itself. The textbox element would simply notify externally that a value is entered, and if that value is invalid, the textbox element would be set to an invalid state by an external controller.

Defining elements

Frameworks often take different approaches to defining elements. Typically defining an element class is different than defining an instance of that element class. However, some frameworks blur this line considerably, not really making a distinction between a class and an instance.

Most frameworks use a domain specific language (DSL) to either create instances of an element or in some cases define classes. Sometimes the DSL is a superset of HTML or formalized into a templating language.

Prior to ECMA Script 6 (ES6/ES2015), JavaScript had no formal syntax level class system. ES6 introduced the keyword class to define a instantiable class. Some frameworks leverage this syntax, whereas others use different mechanisms to define a class or class-like construct.

The examples for each framework below are minimalist in their approach and demonstrate the main code for defining a class of element and showing how to create an instance of that element. The examples are not intended to be stand-alone, fully-functional, or exactly the same across all frameworks, but are designed to give a flavor of what the source code might look like in that framework.

Out of the box

There is always a compromise between leveraging out of the box elements versus building them as part of the project. As native HTML elements have gotten more feature-rich over the years, many interactions have become easier, but almost every web application is likely to have requirements that exceed the capabilities of these built-in elements. Looking to the framework to provide out of the box elements usually helps engineers solve two challenges:

  • First, it allows development to be accelerated, being able to create feature-rich applications by just connecting together elements that usually share a common UX design.
  • Second, it gives more in depth patterns of how to build or extend additional components. Because many of features provided by frameworks transcend the capabilities of the browser, frameworks often solve these issues in radically different ways, many of which we will explore in greater detail in this series.

In this section we are going to analyze what you get out of the box (or otherwise adopt) with each of the selected frameworks.

Customizing Elements

Even if the framework provides out of the box elements, almost every project is likely to require a custom user interface element. The ability to take existing elements and customize them, or to use the out of the box elements as patterns for developing new components, is a common task. In this section, we will discuss the approach each framework takes to facilitate the creation of custom elements.

Jump to:

  • logo
  • logo
  • logo
  • logo
  • logo
  • logo

Angular 2+

Defining elements

Angular 2+ calls elements components. Reusable classes of components are defined using the ES6 class syntax. Typically these are defined as default exports of ES6 modules, authored in TypeScript and decorated as an Angular module. Components are often tightly coupled with their template, which defines their DOM structure, using the Angular template language. Angular 2+ can be used without transpiling via TypeScript, but is challenging and not recommended.

An example of defining a class of a todo list:

@Component({
    selector: 'todo-list',
    templateUrl: './todo-list.component.html',
    providers: [ TodoItemService ]
})
export default class TodoItem implements OnInit {
    items: TodoItem[];
    selectedItem: TodoItem;

    constructor(private service: TodoItemService) { }

    ngOnInit() {
        this.items = this.service.getItems();
    }

    selectItem(item: TodoItem) { this.selectedItem = item; }
}

An example of defining the template for a todo list class:

<ul>
    <li *ngFor="let item of items" (click)="selectItem(item)">
        {{item.label}}
    </li>
</ul>

An example of creating an instance of a todo list, typically in a parent template:

<todo-list [items]="todoItems"></todo-list>

Out of the box

Angular 2+ provides a set of Material Design components. It is an extensive set of components covering form controls, navigation, layout, buttons, indicators, icons, popups, and modals. Angular provides an extensive showcase of their components.

Customizing elements

Customizing a component in Angular 2+, specifically those from Material Design components, typically requires that you subclass the component, override any behaviors and use a decorator to override the reflection metadata associated with the class. The behaviors of components are not currently designed to be composable though the Angular team are looking at ways to break out these behaviors.

Realistically, outside of using the theming system, if the out of the box component does not do exactly what you need it to do, you would create a new custom component following the patterns and best practices of Angular 2+.

Building a user interface library

There are a couple of options to build a user interface library. You can build on top of Material 2, by adding custom components alongside the Material 2 components. In this case, you can leverage the theming system from Material 2. While Material 2 is a fairly complete set of UI components for most interactions, it lacks a high performance data grid.

The other option us to build components consistent with the architecture of Angular 2+. The Angular documentation caters well to detailing how to write components that interact with the application framework. While i18n and animations are provided by the main framework, much of the necessary functionality including accessibility, gesture support, and theming are part of Material 2, which means that those features would need to be adopted from Material 2 or alternative ways of accomplishing these would need to be considered.

Re-using and sharing elements

Components created in Angular 2+ are ngModules which fit into the Angular 2+ ecosystem. Components are usually distributed as transpiled modules in the CommonJS format, as this is often the easiest to be consumed by other applications. They can be published to npm and jspm can then be used for the dependency management.

There is not a consistent component style system, though, and there is the suggestion that the best way is to include CSS as inline styles of your component to make it easy to be imported and consumed. Our opinion is that this is not an ideal way to deal with CSS, and that it would be better to document how someone would include the component’s CSS in the build process, typically via webpack.

React + Redux

Defining elements

React provides the UI as part of a React + Redux application. React calls elements components. Reusable components are either functions or ES6 classes. React heavily leverages JSX, a syntax extension to JavaScript. Also, React expects to be used in an ES6+ compatible browser. Because no run-time environment supports JSX natively and because of the dependency on modern JavaScript, React is typically transpiled for production use. React can be used without modern JavaScript and JSX, but it is difficult and not recommended.

An example of defining a class of todo list as a function:

function TodoList(props) {
    const todoItems = props.items.map((item) =>
        <li>{item}</li>
    );
    return <ul>{todoItems}</ul>;
}

An example of defining a class of todo list as an ES6 class:

class TodoList extends React.Component {
    render() {
        const todoItems = this.props.map((item) =>
            <li>{item}</li>
        );
        return <ul>{todoItems}</ul>;
    }
}

An example of creating an instance of a todo list:

const items = [ 'item1' ];
ReactDOM.render(
    <TodoList items={items} />,
    document.getElementById('root')
);

Out of the box

React is primarily a library for building user interface components, not an existing set of components. There are an extensive set of third-party components that are designed to use React as its user interface library. One listing of these components is available at devarchy React.

Customizing elements

As mentioned above, React is a foundational set of APIs for writing user interface components. Any third party components you choose to utilize would be customizable in the way the component author sees fit. Typically though, because the UI is intended to be reactive, there is very little code or logic in components, and if a component does not do what you want it to do, you would write a custom component that meets your needs.

Building a user interface library

Base React is a set of APIs for rendering UI elements. It has a set of patterns to make higher order components to create a user interface library. The functionality provided by base React though is focused mainly on encapsulating a component which renders a DOM structure. Managing DOM events to translate into higher order concerns like gestures would need to be implemented or a third-party library would need to be utilized. Also, functionality like i18n would need to be integrated.

There are several third-party component libraries which can be leveraged as a foundation. These obviously have different focuses and have their own strengths and weaknesses.

Re-using and sharing elements

React components are typically distributed as JSX modules along with accompanying CSS styles. These are then integrated into a build pipeline for further processing and bundling. Components are typically published to npm and either npm or yarn is used for dependency management.

While not a requirement of the ecosystem, programmatic CSS/styles have become an increasingly popular way to create the styling needed, and to try to encapsulate that into a component. We do not think this is a good practice. Inline styles are always less performant than CSS that is loaded into the browser. Significant consideration should be given to how CSS is managed within reusable components.

Vue.js

Defining elements

Vue.js calls elements components. Reusable classes of components are defined via the Vue.component() function and instances use a custom tag in the HTML markup.

An example of creating a class of a todo list:

Vue.component('todo-list', {
    data: function () {
        return {
            items: [ 'item1' ]
        };
    },
    methods: {
        selectItem: function (item) {
            /* logic to select item */
        }
    }
    template: '<ul><li v-for="item in items" @click="selectItem(item)">{{item.label}}</li></ul>'
});

An example of creating an instance of a todo list:

<todo-list></todo-list>

While the Vue.js DSL for instantiating elements is similar to Custom Elements that are part of the Web Components specification, it is only conceptually aligned to the specification and does not leverage the Web Components technology at the implementation level.

Out of the box

Vue.js focuses more on providing an MVVM application framework. While it provides patterns for creating components, it does not provide a set of common out of the box components. One of the major unique selling points (USP) for Vue.js is its focus on incremental implementation and, in practice, many implementations will wrap other UI components and bind them to a Vue.js application.

There is an excellent blog post that compares some of the available component/design frameworks that have been built upon Vue.js or integrate with Vue.js. These are some that are available:

  • Element – Vue 2.0 based component library for developers, designers and product managers, with a set of design resources.
  • Framework7 – Full Featured HTML Framework For Building iOS and Android Apps. It relies upon PhoneGap to provide a native-like experience.
  • Keen UI – A lightweight collection of essential UI components written with Vue.js and inspired by Material Design.
  • Quasar – Build responsive websites, hybrid mobile Apps (that look native!) and Electron apps using same code, with Vue.js 2.
  • Vuetify.js – A library of Material Design Vue.js components.

Customizing elements

Vue.js does not provide a set of out of the box elements, therefore customizing elements would be up to whatever third-party UI framework you chose to use. Since Vue.js is often used to wrap other UI components, the customization would often follow that library or framework.

Building a user interface library

Vue.js is less opinionated about UI components than some of the other selected frameworks reviewed in this series. Most of the focus in on the MVVM framework, leaving most aspects of the UI implementation up to the framework user. Because component registration is a core part of the framework, creating a library is as straightforward as registering components and using them in pages. On the other hand, several concerns that are common within user interfaces, like event management, i18n, and accessibility, would need to be addressed directly or by using third-party libraries.

One other consideration though is that one of the strengths of Vue.js is the relative ease of wrapping UI elements to make them integrate with a Vue.js application. This allows an incremental refactoring of applications by slowly leveraging the Vue.js framework. If you already have a component/element library and Vue.js fits your needs, it maybe worth considering leveraging your existing UI library.

Re-using and sharing elements

There is no standardized way of distributing Vue.js components, though typically you will find component libraries published on npm. Awesome vue provides scaffolds that will work with vue-cli to create an initial pattern for sharing components. Because of the non-prescriptive nature of the UI aspects of Vue.js, there is also no standardized way of managing styles and other related content.

Dojo 2

Defining elements

Dojo 2 calls elements widgets. Reusable classes of widgets are defined using the ES6 class syntax. Typically these are defined as default exports of ES6 modules and authored in TypeScript. Dojo 2 can be used without transpiling via TypeScript, but it is challenging and not recommended.

An example of creating a class of a todo list:

export default class TodoList extends WidgetBase<WidgetProperties, WNode<TodoItem>> {
    render() {
        return v('ul', {
            key: this.properties.key
        }, this.children);
    }
}

An example of creating an instance of a todo list, in an application widget:

class App extends WidgetBase {
    render() {
        return v('div', {
            key: 'wrap'
        }, [
            w(TodoList, { key: 'todolist' }, [
                w(TodoItem, { label: 'item1' })
            ]);
        ]);
    }
}

You can also use TSX with Dojo 2. TSX is the TypeScript form of JSX. An example of a todo list written in a JSX form:

export default class TodoList extends WidgetBase<WidgetProperties, WNode<TodoItem>> {
    render() {
        return (
            <ul key="{this.properties.key}">
                {this.children}
            </ul>
        );
    }
}

While TSX can be used, Dojo 2 widgets typically do not use templates or JSX/TSX, but instead rely on simple pure functions.

Out of the box

Dojo 2 provides a set of out of the box widgets. Dojo 2 is currently in a beta phase and at this stage there are several form widgets, layout widgets, and a dialog widget. In addition there is a feature-rich data grid component (dgrid) which is in an alpha phase for the Dojo 2 release.

Customizing elements

Dojo 2‘s out of the box widgets serve two purposes. First they provide a level of functionality to make them useful in many cases, as well as serve as examples of patterns. The most common way to customize a widget, theming, is broken out into a mixin of which a theme can be applied to a widget. While still in beta, re-use is being worked on, with efforts currently underway to provide common behaviors and patterns as separate modules. In the end, instead of extending a widget class to customize it, it is far more likely that the pattern would be to reuse the functionality from widget-core to implement a widget that expressed the relevant functionality.

Building a user interface library

Dojo 2 is designed to make it easy to create additional components. It also provides an internationalization and localization library. While still in beta, Dojo 2 is working towards abstracting out DOM event management and has established patterns for good accessibility usage. While the out of the box widgets can be used as a base for a library, it is likely more common to pick and choose some widgets that meet your use case and build additional widgets that meet the UI/UX requirements of your application.

Re-using and sharing elements

Dojo 2 widgets are TypeScript modules where the default export extends WidgetBase. Also, the theming and style system for Dojo 2 widgets utilizes CSS Modules, meaning that a widget module explicitly imports the CSS, which is designed to flow through the build system and be available as CSS in the application bundle generated. Therefore, while there are some conventions, distributing and re-using widgets is as straightforward as using the ESM import Widget from 'my/widgets/Widget' statement. It is expected that shared/custom modules would be published on npm.

Ember

Defining elements

Ember.js calls elements components. Typically Ember.js uses Handlebars as its templating syntax. Ember.js also embraces Web Components and provides the necessary tooling to ensure that custom elements are managed within the application.

An example of defining a new component class:

$ ember generate component todo-list

And then defining a template (app/templates/components/todo-list.hbs):

<ul>
    {{#each items}}
        <li>{{this}}</li>
    {{/each}}
</ul>

An example of creating an instance of a todo list:

<todo-list items=model></todo-list>

And adding an index route:

export default Ember.Route.extend({
    model() {
        return this.get('store').findAll('item');
    }
});

Out of the box

Ember.js focuses more on providing an application framework than being opinionated about user interactions. While it has strong guidelines for how a component interfaces with the application, it does not provide a set of common out of the box components. There is a rich community of components at Ember Addons which can easily be included within your Ember application via the Ember.js tooling.

Customizing elements

Ember.js does not provide out of the box elements, therefore how to customize a component is determined by the creator of the third-party component.

Building a user interface library

Ember.js focuses on the application framework. Ember.js has a well defined component interface though, and the project provide guides with information on how to build components into a library. There is no default support for internationalization and localization, but there are several examples of injecting localized strings into components with Ember.js. Theming is also left as an exercise for component authors, and there are several different systems or patterns that have become common within the Ember.js ecosystem.

There are numerous third-party components available, and they can be managed with the EmberCLI. They can vary greatly though in quality and consistency. In creating a library, you would want to get a sense of how effective the existing third-party components are in meeting your needs, versus the effort to author a custom set of consistent components.

Re-using and sharing elements

Ember.js has the concept of addons and blueprints which are all accessible via the EmberCLI. By creating an addon with the appropriate components you can create a reusable library of tools. These addons can be added to blueprints to make it easy to scaffold out new projects with the appropriate components. These can be published to npm. While npm is used for dependency management for new projects, many Ember projects still use the now deprecated Bower for dependency management.

Ember has a well integrated toolchain and uses broccoli as an asset pipeline for creating web applications that contain all of their dependencies.

Aurelia

Defining elements

Aurelia calls elements components. Aurelia uses its own templating engine which is normally transpiled, but can also be run within the browser. Aurelia fully embraces Web Components and creating a custom element is accomplished by naming the JavaScript and HTML template file with the same name, but with appropriate extensions.

An example of creating a todo list class:

export class TodoList {
    constructor(items) {
        this.items = items;
    }
}
<template>
    <ul>
        <li repeat.for="item of items">${item}</li>
    </ul>
</template>

An example of creating an todo list instance in a containing template:

const todoList = new TodoList([ 'item1' ]);

Out of the box

Aurelia originally did not provide a set of out of the box components, but in late 2016 the project announced Aurelia UX, a set of components that provide higher order capabilities. There is a showcase application available which demonstrates the components that are currently part of the UX framework. At the time of this writing the framework contains the following categories of components:

  • Buttons
  • Checkboxes
  • Layout
  • Inputs
  • Text Area
  • Chips
  • Icons

Customizing elements

If you use Aurelia UX, components are provided as ES6 classes. The components have a fairly decomposed lifecycle and in theory a component could inherit from an ancestor and change the behavior, but the challenge is that because DOM interactions occur throughout the lifecycle of the component, overriding a particular method might cause surprising behaviors. Like other frameworks, it is likely that you would create a new component using the patterns and library available.

Building a user interface library

If you use Aurelia UX as the starting point for your library, you will have a reasonable set of components and a theming system. Internationalization is provided by the core packages of Aurelia by integrating i18next.

There are a few third-party libraries that have different sets of components with varying degrees of functionality as well as a few bridges to other UI libraries.

Re-using and sharing elements

Aurelia does not have any hard and fast rules around creating UI libraries. Dependencies are typically TypeScript components which have been emitted to JavaScript and are bundled with their definition files. They are then published on npm and can use npm for dependency management (or jspm or yarn).

The Aurelia CLI expects resources that are contained in the /src/ directory, and CSS files located in there will be built into the application and can be imported into a component or application.

Summary

We are going to summarize each of the frameworks from a UI perspective. One topic that was not covered in depth above is accessibility (a11y). None of the frameworks provide an automatic, completely accessible solution, because much of accessibility depends on how UI components are used in an application. There are ways to turn components into something a screen reader can properly read, and some of the out of the box component libraries do this and none of them prevent you from creating custom accessible components. Some frameworks actively promote accessibility, but none of them enforce it. Even if you have a component available in a screen reader, it does not make it 100% accessible. How you color your elements (therefore what styles you use) and keyboard navigation of your elements are also critical factors to having an accessible UI. With accessibility being a complex topic and it being difficult if not impossible for frameworks to enforce all rules, it is only really useful to mention accessibility here in the summary.

Angular 2+

Angular 2, by itself, is a framework for building components and applications. Some of the fundamental tools are available to build consistent UI components, like i18n, but some features like theming only come with adopting Material 2.

The Material 2 design gives many considerations to theming and work in an accessible way. At the very least, Material Design provides good patterns for building well behaved Angular 2+ components.

If you are sold on Material Design, you get a very extensive set of UI components. If you want to add additional custom elements to this collection of components or customize the ones available, you might find this challenging. If you are building a library of components, then you need to be prepared to adopt all of the the Angular 2+ approaches, but also add in theming and make sure you create accessible components.

React + Redux

React provides a foundational library for building components. Its focus is to provide a pattern that ensures that components play the right role in what the core React team considers to be a good web application. There is a large React community providing options for the other concerns around theming, event management, i18n, and localization. It is quite easy to create React components and in theory you could build up an extensive library over time. If you do try to use third-party components, though, you will need to verify their consistency and quality and it may be difficult to make a cohesive set of UI components from a visual/style/UX perspective without significant effort.

From an accessibility perspective, you will need to identify the patterns that make the web accessible as a whole and make sure those are incorporated in your components. Again though, there is a robust community to provide information and resources.

Third-party component libraries may suit your needs, but the extensibility and usability of those would need to be assessed in the context of your application requirements.

Vue.js

Vue.js is likely the least opinionated of the frameworks we have reviewed when it comes to the UI. It is best suited for a bring your own UI approach. If you are building a greenfield Vue.js application, there are many third-party component libraries that can use. You can also build your own library of components, where the only real opinion is how the component interacts with the application. There are also extensive third-party patterns and tools to connect Vue.js to other UI frameworks. If you are incrementally adapting an existing UI to a MVVM application framework, then Vue.js has several advantages to make this possible without throwing away your existing UI.

Because of this lack of opinion, all the other concerns, like accessibility, theming, and i18n are left to you, though in our research, for every problem there is a Vue.js community solution.

Dojo 2

Dojo 2 strives to provide a complete UI element solution. With Dojo 2 still in beta at the time of this blog, there are still some rough edges. Dojo 2 seeks to provide a solid core of default widgets which either form a foundation for a library, or serve as patterns for creating your own library. Theming, i18n, and localization are provided out of the box. Additional ways of abstracting common functionality are under development. The out of the box components are designed to be fully accessible and there is further functionality under development to make sure that focus management and other UI events can be managed without having to make sure that each widget has all the logic necessary to behave in a fully accessible way.

Ember.js

Like the rest of Ember, from a UI perspective, Ember has an opinion. Ember.js is a solid and good opinion and if you like the rest of the Ember.js application framework, then you are likely not going to find any issues with the UI patterns. Ember.js is much more mature than the other frameworks we are reviewing in this series, but at the same time the Ember team have made solid efforts to stay current and evolve as patterns and best practices change. Ember.js UI components are very closed aligned to Web Components and the Ember.js team still hopes that as Web Components become available in browsers, you will be able to just convert your Ember.js UI components to Web Components.

Aurelia

Aurelia UX provides a decent set of components, but is likely not sufficient to create a complex business application on its own. While Aurelia provides i18n and Aurelia UX provides a theming system, these out of the box components do not appear to be fully accessible at this time. There are some third-party UI libraries for Aurelia, but their features and capabilities are out of the scope of this analysis.

If you were using Aurelia as your application framework, it would make sense to strongly consider using the Aurelia UX component system.

Up next

Now we have flipped through our back catalog of user interfaces, next up on our hit list is delving deeper into how the surveyed frameworks can help us achieve our user experience goals. User experience is much more than a flashy album cover, it is the core of how users experience our product. It is one of the areas where design and engineering have to work together towards a common goal.

Other posts in the series

Comments