It’s 2020. Ember Octane has been released. IE11 is no longer widely used, and can generally be ignored. We are starting a new projects with this next generation framework. However, component libraries are largely still written with Ember Components and have the baggage of IE11 compatibility. How much baggage? Tons of polyfilled code and lots of components in the package we might not use. New technologies like Embroider with code splitting and Svelte builds might allow us to strip this all away, but only if we don’t use older features.

The CSS

What might a new UI component library look like in this new world? Let’s start with the CSS. We could use Sass or PostCSS. But why do we need to? Most of the technology in these transpilers are made for the days when newer style CSS wasn’t supported. If we used plain CSS, our users could choose to use whatever CSS framework and CSS transpiler they wanted, rather than be boxed in by our library. They could even use plain CSS and not pay for the wait.

What about themeing? We can’t do that without transpilers, right? Wrong. All modern browsers support CSS variables, CSS grid, CSS calc, not to mention so many other features that IE11 didn’t. We don’t need a grid framework any more, and we also don’t need a transpiler to theme.

What does this look like? Here’s an condensed example for a button:

/* these should be overridden by your theme */ :root { --glue-primary-bg-color: white; --glue-secondary-bg-color: #ddddee; --glue-primary-gradient: linear-gradient(180deg, #f4f4ff 0%, #ccccdd 100%); --glue-primary-fg-color: black; --glue-secondary-fg-color: #333333; --glue-primary-border-color: #666666; --glue-secondary-border-color: black; --glue-primary-border-radius: 20px; --glue-secondary-border-radius: 6px; --glue-primary-border-width: 2px; --glue-normal-font-size: 14px; --glue-primary-padding: 12px 20px; --glue-secondary-padding: 6px 10px; --glue-primary-box-shadow: 2px 3px 4px #999999; } .glue-button { box-sizing: border-box; background-color: var(--glue-secondary-bg-color); background-color: var(--glue-primary-gradient); border-color: var(--glue-primary-border-color); color: var(--glue-primary-fg-color); border-radius: var(--glue-secondary-border-radius); border-width: var(--glue-primary-border-width); font-size: var(--glue-normal-font-size); padding: var(--glue-secondary-padding); box-shadow: var(--glue-primary-box-shadow); }

You can learn about the CSS Variables specification from the MDN docs. TLDR; just use plain CSS!

The TypeScript

Now of course, everyone is moving to use Ember Octane. This means no Ember Object, get/set, computed, or any of that. We use modern classes with decorators written in TypeScript, of course! All the changes means this is not the Ember we remember. So if we’re implementing a menu, we build it using the @tracked decorator and @action decorator rather than computed properties or an actions hash. We don’t need to specify the dependencies any more, unlike traditional Ember. The result is code that looks even more modern than React or Vue:

import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { Dropdown } from 'ember-basic-dropdown/addon/components/basic-dropdown'; interface Args {} export default class GlueMenu extends Component<Args> { @tracked dropdown: Dropdown | null = null; constructor(owner: unknown, args: Args) { super(owner, args); } @action onOpen(dropdown: Dropdown) { this.dropdown = dropdown; } }

Clean, modern looking code that's just a plain ES6 native class is what we should look to create from now on.

The Template

Finally, in the template, we can write straight html with what looks like simple string replacement. With Ember Octane, our designers only need to know HTML, not JavaScript, in order to edit the template.

This doesn’t perform like string replacement or even DOM diffing. The templates compile to tiny bytecode (called the wire format) that is then converted to DOM by a very fast glimmer virtual machine. When the template is updated, there is no need to rebuild the DOM, or even DOM diff, because the glimmer engine knows exactly where the template can vary. This enables very fast rendering because no JavaScript needs to be parsed and executed on render, and no DOM needs to be diffed in order to avoid the expense of rerendering.

Here is an example template from the toggle switch component:

<label class="glue-toggle" ...attributes> {{#if hasBlock}} {{yield @label}} {{else}} {{@label}} {{/if}} <input type="checkbox" class="glue-toggle--checkbox" {{on "click" this.handleClick}}> <span class="glue-toggle--switch"></span> </label>

Our web designer doesn’t need to learn JavaScript in order to edit the templates to their liking.

Unlike older Ember Components, Ember Octane’s modern glimmer components do not create a wrapper element, so one can simply add attributes to the parent component when creating this toggle:

<GlueToggle title="My Title" @label="My Toggle" @handleClick={{action this.handleClick}} />

The `@` symbol is now used to differentiate component arguments from plain HTML attributes. "this" is bound automatically by @action in the helper, and is now used to differentiate between local variables and context properties, which is why the template is so easy to read. No JavaScript allowed to complicate it.

We should always strive to keep our templates simple, readable, and maintainable, and move anything complex to our TypeScript.

Other Concerns

Of course, our new library also needs to be responsive and accessible, as all modern component libraries are. We can sprinkle in some gradients, transitions, animations, and other CSS special effects to make things look modern. Add a choice of themes to let users quickly develop, and the ability to write one’s own theme to customize the look for their site or even entire company.

Another point is the library is broken up into many small packages, so that one does not need to include all of the library and can minimize the download size. It also uses module imports and avoids deprecations that enable tree shaking with Embroider.

These examples have been taken from the start of a next generation Ember UI library called ember-glue. It currently serves as my demonstration of what a next generation UI library could be. Contributions to make this a complete library would be greatly appreciated.