The Siren Song of Component Libraries
Large web applications are almost always built by multiple teams. When the scope or scale of an application grows, there’s just no way a single team can be responsible for the entire thing. How you divide an application in to teams effectively is beyond the scope of this post, but for now let’s stipulate that any reasonably large web property will have dozens of developers across myriad teams working on it.
Another near-universal truth is that the design team for a large web application wants a consistent look across the site. They’ve spent hours and countless meetings arguing over the hover state of the primary button, and the outline of the secondary button. They don’t want styles that sort of look right, with minor deviations across pages. They want pixel perfection everywhere. The marketing and branding teams are on board with this too. No point hiring that expensive branding agency if you don’t implement the right colors and fonts!
So how does the savvy development manager achieve a consistent UI and branding look across hundreds of pages built by multiple teams and dozens of developers? A UI component library of course!
Components All the Way Down
With modern frontend frameworks you use components to compose your UIs. Take the venerable <button>
for example. You’ve got a drop shadow, a hover state, disabled state, click animation etc. You want to make sure anyone putting a button in your UI is using the right button. In theory, a component is the perfect way to enforce this UI consistency. It’s a self contained bit of UI, logic, and styling that, depending on your skill with npm, can be easily leveraged by anyone to get useful functionality.
In the past we were stuck with CSS libraries like Bootstrap or Foundation. Great at handling the styling piece, but they required the developer to generate the correct markup and wire up the JavaScript appropriately. There was a lot of room for error. But with components we can disseminate these bits of UI out to our teams, and implementation will be a cinch, just plug ‘em in!
A Common Story
So imagine you’re a manager tasked with overseeing the development of a UI component library on a large site. You’re starting from scratch. Design has a fresh new look, and they are looking to you for implementation. They want the new brand and designs rolled out across the entire site ASAP. But, who’s going to build all the components?
Well, it turns out the existing teams of developers are busy doing important work. Work that makes the company money. They are adding new features to the app, fixing bugs, things like that. They don’t have time to stop everything and build a UI component library. You figure there’s no point in risking their productivity, and your yearly bonus, on this library idea. Common button styling is important, but revenue targets are rightly more important.
So you decide the best course of action is to spin up a new team. A team whose sole focus is building a library of reusable UI widgets. This lets the existing teams get on with their work, and once the component library is done they’ll just drop in the components and you’ll get all the accolades from the design team.
Fast forward a few weeks and the new UI component team is furiously writing components. They’ve got buttons, dropdowns, text boxes, everything. The developers on the other teams are going to be so excited to use these new components!
Implementation Is Easy
The first team goes to implement the new button component. All of the library components are written in React and the library is exposed as an ES6 module. Perfect, the company has settled on React as the framework du jour, and Webpack as the build tool. You’re all set.
Well, except that the first team out of the gate is using an old version of React. They can’t upgrade easily because they are using some now-deprecated component lifecycle methods. And the new button components require the newest React, the one with the official context UI. Ok no problem, that team will just have to upgrade and they can implement the new UI components in a few weeks when they’ve upgraded.
The second team jumps in. No React versioning issues here, because they’re not even using React! This team works on the billing screens, the ones that take credit cards. Infosec has not allowed these screens to be written in React. They don’t trust single page apps, and have a host of security concerns. So this team is still server rendering everything with Handlebars templates. Shoot! Well they will just need to get an exception from Infosec! (Hahahaha)
The third team tries to implement the new button. They get the component imported and on the page. Hooray! When someone clicks, their custom callback handler is run, and everyone is happy. Well… except they want to fire an analytics event every time a user clicks the button. Surely this is something that should be baked in to the button itself? The team just wants to send in a few event properties and have the button itself handle the analytics stuff. Your UI component team loves this idea too. They were getting bored fighting browser quirks, so writing a little analytics JavaScript is perfect. A new update is coming right up!
The fourth team to implement the UI component library has some ideas too. They want to show a loading spinner on the button while the call to save the form is in flight. Can there be a loading animation property as well?…
Death by a Thousand Business Requirements
So by now about half the teams are starting to integrate with the UI component library. There’s been a lot of back and forth, with lots of great ideas about how to extend and enhance the component library. The button component in particular has a wealth of new functionality. It can accept various analytics tags, and you can even tell it which kind of analytics you’re doing. It can use Tealium, Google Analytics, Omniture, and load arbitrary custom pixels from other third parties too! It’s hundreds of lines of very convoluted code, but no matter, it’s all in one place!
You can also supply asynchronous click handlers, error classes, validation logic, there’s even a property to get the underlying raw HTML <button>
element. There are accessibility properties developers can set. There’s language specific attributes, for those pesky right-to-left languages. There’s so many good ideas that the API surface area of the button component begins to explode. It seems like each team needs some new feature, or some tweak, to handle their specific business requirement.
Of course, many teams are requesting features and enhancements that are not compatible with each other. So it’s getting somewhat contentious. Who’s feature request is most important? What if this new feature breaks an old feature another team was using? How do we version these components? How do we handle backwards compatibility, browser quirks etc?
Also, a lot of teams are saying without a specific feature or enhancement, they won’t even try to implement the component library. The library dev team has a backlog that will take it months to do, but Design and Marketing are getting impatient! They are submitting the site for a re-branding award and the judging is beginning next week. They don’t understand why each team can’t just slap some HTML and CSS on a page and call it a day.
Cost vs. Benefit
I’ve seen this scenario play out first hand at many large companies. I’ve heard stories from dozens of others. Developers love to build frameworks and libraries - in isolation. It’s fun to manufacture theoretical use cases and solve them in elegant ways. The problem is that the real world is messy, and finding the right abstraction is difficult. It’s decidedly less fun to deal with real use cases from actual teams, who are laboring under dozens of competing requirements, regulatory concerns, accessibility directives etc.
Without working closely with each team that needs to integrate a UI component library, it’s unlikely the components will ever benefit anyone. By the time you’ve implemented 2 or 3 team’s worth of requirements, you might even conclude you’d be better off letting each team build it’s own set of components. The cost of building one-size-fits-all UI components is almost always greater than the benefit. Sometimes there isn’t a good way to represent every team’s needs in a single code base. Maybe that’s why they were separate teams in the first place?
Finally, UIs have a short shelf life, so what happens when it’s time for the site to be rewritten in React 2? Your components will be useless!
There’s no Easy Button
There is no perfect plan to achieve consistent UI appearance across a large site, but it’s also not that hard. Have a well documented styleguide. You don’t need drop-in components, but you do need an easy place for developers to find the right colors, fonts, margins, icons etc. This is as simple as having the design comp in PNG format, annotated with the relevant style information, shoved in a shared folder somewhere (or, god forbid, Confluence). If there are assets, cut them up and put them in a zip file next to it. That’s it.
The downside to this is someone from design will actually have to (gasp) use the site, and point out any places where the developers got it wrong once they’re done. This takes very little time, although the design team will likely complain that it’s not their job (it is). It’s not creative or fun work, so they’d prefer not to do it.
Consistent design is not something you can abstract away with components. You might even have some success with the component library approach, but at what cost? Wouldn’t it have just been faster and cheaper to tell the developers the background color for buttons is #0000ff, and then have a design person check the site once and awhile? Developers love to save 20 minutes of boring work by doing 40 hours of fun work. Don’t let this bite you.
And hey, regardless of which way you go, your website will still have bugs in production. Why not install TrackJS on there while you’re at it, and get notified when things are breaking?