Go to main content

Doing more with a smaller team: a button component example

As humans, we can only hold so many concepts in our head. You can only deal with a certain amount of complexity at once. In computing, we have traditionally abstracted away complexity. Think about C-type code versus PHP. Think about ORM in the database world. Abstractions help us to not have to deal with a certain part of the work, and do less with smaller teams.

In the last post, I talked about how we are using Ember and how we are treating the Ember code as the design deliverable.

I provided this code example of a button:

<Button>Add</Button>

A person who wants to implement this button simply has to know how to copy and paste this code. Instead of a standard HTML button, we get a nicely styled button that fits the rest of the look of our app:

Now what about changing the style of the button? Instead of knowing about specific CSS classes, we can add an @skin argument, and we have a blue button:

<Button @skin="primary">Add</Button>

What if we want our button to contain an icon? Simple, we add an @icon argument and the icon appears instantly:

<Button @skin="primary" @icon="add">Add</Button>

And this is what it looks like:

There is a lot of logic behind this to make it work. But the person who is implementing the button doesn’t have to be aware of every implementation detail.

Here is the source code for the button component. We have a component that takes 2 arguments: skin and layout.

import Component from '@glimmer/component';

export default class Button extends Component {

  get skin() {
    if (this.args.skin)
      return "c-button--"+this.args.skin;
    else
      return "c-button--secondary";
  }

  get layout() {
    if (this.args.layout == "icon-only") {
      return "c-button--icon";
    }
  }

}

Then, a button.hbs file:

{{#if (eq @as "link")}}
    <LinkTo class="c-button {{this.skin}} {{this.layout}}" ...attributes>
        <Button::Innerlayout />
    </LinkTo>
{{else}}
    <button class="c-button {{this.skin}} {{this.layout}}" ...attributes>
       <Button::Innerlayout @layout={{@layout}} @icon={{@icon}}>
          {{yield (hash
              InnerLayout=(component 'button/innerlayout')
          )}}
       </Button::Innerlayout>
    </button>
{{/if}}

This button.hbs actually references a child component, inner-layout.hbs:

{{#if @icon}}
  <Icon @name={{@icon}} />
{{/if}}
{{#if (eq @layout "icon-only")}}
  <span class="u-sr-only">{{yield}}</span>
{{else}}
  <div class="c-button__label">
    {{yield}}
  </div>
{{/if}}

Which in turn, references a component called Icon, which once again has a Handlebars template:

{{inline-svg this.filePath}}

This code looks simple, but actually imports an entire addon. And there’s a Javascript logic behind it as well:

import Component from '@glimmer/component';

export default class Icon extends Component {

  get filePath () {
    return `icons/${this.args.name}`;
  }

}

All of this code works because it is contained in an Ember environment with its own code and file structure conventions. And all of this code exists so an author can write this…

<Button @skin="primary" @icon="add">Add</Button>

…and get back code like this:

<button class="c-button c-button--primary" type="button">
  <svg><!-- svg code for the add icon not here for brevity --></svg)
   <div class="c-button__label">Add</div>
</button>

The author does not have to worry about knowing BEM, about finding the icon SVG file, about concerns like whether or not you should add type="button" to a button element or not.

The code they have to write is much simpler. It’s much harder to make a mistake, because the component has its own simplified API.

All of this abstraction is effectively an enabler.

It means that anyone who doesn’t know about the minutiae of front-end development can still develop a pretty good looking UI.

Building blocks can be abstracted to a level that’s easy to use. And then it’s just a matter of putting the legos in the right order. It’s not that simple of course – you still have to use the system in the right way. Developing the system itself requires more thought and skill. But a good component system can allow more people to build fantastic looking UIs, that have more built-in qualities: being performant, being accessible, being beautiful.

I believe given the right tools, it also allows visual designers to prototype interfaces in HTML, whereas maybe before they were scared of not being able to write the right code.

And on top of that, with this being a component system, it comes with the obvious benefits of any component system: help a team to stay on the same page and reduce code duplication.

This new style of thinking about components has been propagating through all the major (JS) frameworks for a few years now. As a company, we’ve always had the goal to streamline design/development workflows.

I think this is a major breakthrough in how we can deliver design and streamline design deliveries. Some might say we’re actually doing the implementation. I say that we’re taking one step further to improve the quality of our deliverables. I’m excited to deliver more projects in this manner.

Johan Ronsse

About the author

Johan Ronsse is an interface designer who is trying to find the balance between aesthetics and usability. He tweets as @wolfr_2.

Subscribe to our newsletter

Receive blog highlights and fresh insights into UX/UI and front-end development.

Leave a comment

Your email address will not be published. Required fields are marked *