Renderless or Bust!
Why Renderless Component Libraries will Make Web Dev Easier and More Inclusive
Vue component libraries are popular.
They have cool names and sweet logos. They have sleek documentation. They’re made with ❤️, MIT-licensed, and open source.
Everybody loves component libraries!
If you’ve been living under a rock, or you’re just not a nerd, here’s a definition of component, paraphrased from the Vue.js documentation:
Components are reusable Vue instances with a name. You can use components as custom elements inside the templates of other Vue components.
In other words, components are shorthand, replacing longer snippets of HTML, CSS, and JavaScript, especially snippets that need to be used frequently.
When you create a bunch of components and stick them all in a folder, that’s called a component library. If your library is good, you can publish it on npm, and other people will download it and use it in their apps and websites.
If your library is really good, you can make money off of it — ask for donations, sell licenses, put tasteful ads in the documentation, and charge for consulting & training.
If your library sucks, don’t sweat it! Just download someone else’s library, and you’ll soon be churning out high-quality prototypes and products much more quickly than you used to. Sounds great, right?
Note: this article was originally posted here on the Vue.js Developers blog on 2019/02/12.
There’s a catch.
The component library ecosystem, in its current state, caters to a very specific kind of developer. That kind of developer:
- Is very comfortable implementing user interface logic
- Knows the ins and outs of one or more front-end frameworks
- Is well-versed in nifty third-party JavaScript libraries
- Might understand the value of accessibility and semantic markup, but doesn’t rank them as top priorities
- Most likely isn’t following a strict, unique style guide
To illustrate this point: imagine you’re building a simple user profile, and your users need to be able to edit their name, email, title, and a short bio. A slick way of doing this is to build an editable text component that meets the following specifications:
The user can…
- Edit a value by clicking on a field, or navigating to the field with the keyboard
- Press enter, click outside the field or click a Save button to save
- Press esc or click a Cancel button to revert to the previous value
- Press alt + backspace or click a Clear button to clear the input
The app can…
- Conditionally render the saved value when the field is not being edited, and an HTML text input — pre-filled with the saved value — when the field is being edited
- Conditionally render the Save, Cancel, and Clear buttons, only showing them when the field is being edited
This UI logic and UX is extremely common and is one of if not the most basic feature of any web app’s profile. So, let’s think about how you could implement it using a popular Vue component library.
Vuetify, Quasar, Ionic, Buefy, etc. have custom components for buttons and text inputs. That’s a great start — now, all we need to do is pull in those components, and…well…write all of the event handlers, state management, and save/cancel/clear methods from scratch. The only other viable alternative is to go dig around on the internet for a custom component or third-party JavaScript, review it for security risks, then npm install
your problem away.
To summarize: unless you have intermediate or advanced skills in vanilla JavaScript and Vue, your apps can’t have a nice, editable profile, and plenty of other basic and necessary features in modern UX. If you’re using a popular component library, you can’t get around this problem, except by installing more dependencies.
In less precise words, the component ecosystem serves JavaScript developers, especially those who come from a logic-heavy environment, like back-end programming.
And there’s absolutely nothing wrong with that! The ecosystem empowers those developers, and that’s a good thing. But what if you aren’t of that variety?
What if you have a mastery of semantic markup and CSS, but aren’t as confident in your JS skills? What if you’re an accessibility buff, and you need your markup to conform to the highest standards? What if you’re a designer, you see the UI as an extension of your brand, and you need complete control over the UI’s look and feel?
Do component libraries serve a wide variety of developers?
Nope, I don’t think so. Here’s why:
Popular component libraries often implement only basic UI logic, like how modals open and close, or how users move backward and forward through a slideshow.
So, they don’t serve developers who are still working on their JS skills — people who know how to close a modal or advance a slideshow, but have trouble implementing intermediate or advanced features, like updating records in a real-time database, or fuzzy searching a list of items, based on user input, and rendering the search results in the app.
Libraries make a ton of decisions about your markup and templates, including class lists, WAI-ARIA roles, HTML tags, DOM order, attributes, data attributes, etc. Most of this can’t be meaningfully changed except by editing the source code, which of course risks breaking other features, screwing up future updates, and other gnarly stuff.
So, they don’t give first-rate service to developers who are laser-focused on accessibility or semantics, developers whose class names collide with the library’s, or developers who just need to change the order of a few elements.
Finally, these libraries come with pre-written CSS, and usually icon sets, too. As a developer, you usually have three options on how to deal with this:
- Use the default CSS and icons
- Exclude the CSS and icons through some sort of config file
- Include the CSS and icons, then try to override them up with your own styles and icons.
For a developer with solid design skills and a unique brand, Option 1 is unthinkable. Option 2 is do-able, but it’s just another annoying config task, sucking up mental overhead and getting in the way of the real work. Option 3 isn’t horrible but might lead to naming collisions, specificity wars, and other violent-sounding problems.
So, popular component libraries don’t do a great job of serving developers who have their own design system.
Why and how do we change this?
I think the “why” is easy, and has two parts.
- Changing our popular tools to serve different kinds of developers will diversify and strengthen the entire industry and community.
- Component libraries are a product. There are unserved customers out there looking for a better product. 2 + 2 = 💰.
There are probably lots of ways the component library ecosystem could be changed and improved, but one idea that has caught my eye is to build new libraries, chock full of just one specific type of component: the renderless component.
What’s a renderless component?
A renderless component is any component that doesn’t render any of its own markup or contain any of its own styles. Loosely speaking, the component is shorthand for a snippet of JavaScript — no HTML or CSS allowed. In Vue.js specifically, a renderless component has no template, and instead uses a render function, usually rendering a scoped slot.
The concept of renderless components, especially the ones that render a scoped slot, can get pretty confusing — maybe a metaphor will help explain it better.
A renderless component is a transparent box. Developers can fill the box with HTML markup (including class lists) or even other components, which can be seen clearly from outside the box. The inner walls of the box (the “scope” in “scoped slot”) are lined with data and functions.
Anything inside the box can access that data and those functions — if the box is lined with an array, you can use v-for
to iterate over the array; if the box is lined with a function, you can use @click
to call that function when a button is clicked. You can even pass the data and functions down to child components through props.
Most importantly, you can do all of this just by placing the box anywhere in your app. You don’t have to know what the box is made of; you don’t have to know how it was manufactured; you don’t have to know how that data-rich lining was attached to the box’s walls.
That’s a powerful concept — to make sure it’s clear, I’ll break out of the metaphor: to use a renderless component, you just need to register it and create an instance. You definitely don’t need to understand the fancy JavaScript and niche Vue features used inside of a renderless component — after you register it and create an instance, you just need to know 3 more things:
- How to use basic Vue features, like template syntax,
v-on
,v-for
,v-model
, andv-bind
- How to pass data and functions to a scoped slot using
slot-scope
- What data and functions are available inside the renderless component
You can learn #1 from the introduction to Vue’s docs, and you can learn #2 from Vue’s scoped slot docs. You can usually learn #3 by reading the docs written by the component’s creators, or contacting them directly.
Do renderless components serve a wide variety of developers?
Yes! Here’s why:
Renderless components implement more advanced UI logic. Want to update a record in a real-time database? Cool, your transparent box is lined with create, read, update, and delete functions. Just put some buttons in the box, and call one of those functions when the end user clicks the button.
Need to fuzzy search a list? No sweat, the box is lined with a fuzzy search function and a list of search results. Put an input element in the box, and each time someone types in the box, call the fuzzy search function. Use v-for
to render a list of search results inside the box, just below the input.
Renderless components make no decisions about markup. Want your image slideshow captions to be accessible? Fill up the transparent box with your images and captions, set the images’ aria-labelledby
to anything you want. Attach the box's nextSlide
and previousSlide
functions to some buttons, to make sure the slideshow is interactive.
Need to change the order of an input and a Save button? Couldn’t be easier:
- Reach into the box
- Pick up the Save button
- Put it on the other side of the input
Renderless components don’t include CSS or icons. Bring your design system. You’re gonna need it (finally).
Renderless components can render other components. Bring your favorite old-school component library; nothing is stopping you from putting your favorite custom components into a renderless component.
That’s right, highly-logical JavaScript developers — this means renderless components are good for you too! And if you learn how to make them, you can take all of that logic you’ve been repeating across your apps, extract it into a new renderless component, and use that instead.
In conclusion
The developer industry and community is becoming more diverse, not just demographically, but also in terms of the skills people bring to the table. That’s good for everyone, and one way to speed that up is to rework popular tools to suit their needs.
Component libraries are one of those tools, and making libraries of renderless components is a great adjustment that serves a wider variety of developers, while also making life even easier for the developers that the ecosystem already serves.
Are you gonna plug something?
👌 You better believe it.
I’ve been working on my very own library of renderless Vue.js components, and just like the other libraries, it has a cool name (Baleada), a sweet logo (check it out), and sleek documentation (if I do say so myself).
Baleada is still very much a work-in-progress, but I’m planning to make it incrementally adoptable. I’d like for it to have a few different installation packages, starting with just the core library of renderless components, and steadily working up to a full-blown Nuxt.js template, configured for TailwindCSS (and a lot of other stuff), and designed to be deployed on Netlify.
And yes. It’s made with 💖, MIT-licensed, and open source. Enjoy.