Part VI — A Glimpse into Our Baseline UI Customization Approach
This post is part 6 in a six-part series about our design system, Baseline UI.
So far, we’ve discussed various design and accessibility aspects of Baseline UI, which were critical considerations during its development. However, our work doesn’t stop there. A major reason for creating Baseline UI was to develop a library of components that enable easy customization. We’ve talked about theming and styling in the previous articles, but in this article, we’ll delve into a more complex use case — customizing the user interface (UI) for a specific product.
Background
The Baseline UI project is divided into multiple parts:
-
Core contains the core components and styles like button, menu, typography, etc. These components are designed to be used as building blocks for constructing UIs.
-
App contains the components specific to our product. These components are built using the core components and styles.
-
Recipes contains the different sections of our product like the sidebar, header, etc. These sections are built using the core components and styles.
Both the Core and App parts contain React components that can be customized using props. The rest of this article talks about how we’ll enable customers to customize the recipes for complex use cases.
Problem
In most frameworks, components are customized via props. While this method works well at a granular component level, where customization needs are predictable, it falls short for more complex layouts. For instance, predicting the customization needs for a TextInput
component is relatively straightforward. However, this predictability diminishes as the complexity of the layout increases. Consider the example below.
In the layout shown above, a user might want to move the button to anywhere in the layout, change the color of the button, or even replace the button with a different component. This is just one of the many possible use cases. Predicting these customization needs is challenging, and providing props for each of these use cases isn’t feasible. This is where the problem lies.
Our diverse customer base spans various industries, each with unique customization demands that are hard to foresee. The overall UI for one user can be vastly different from another’s, making it challenging to cater to all needs using traditional methods. Historically, this level of customization has been a prime demand from our customers. Therefore, when developing Baseline UI, we aimed to address this requirement effectively.
Traditional customization approaches wouldn’t suffice, so we devised an alternative method to enable users to achieve their desired UI without starting from scratch. We established the following constraints:
-
The API should be intuitive and functional across all frameworks and vanilla JavaScript.
-
Users should be able to achieve any UI they desire by modifying the default UI.
-
Baseline UI components should be usable in constructing their customized UI.
-
Users should be able to utilize existing state management or incorporate their own.
Open Source
One initial solution was to open source the UI of our product. This would allow users to fork the repository and modify the UI as they see fit, enabling us to claim that our UI is fully customizable. However, this solution had several drawbacks:
-
Users would have to maintain their fork, which is time-consuming and error-prone. This essentially shifts the burden of maintaining the UI to the users, which isn’t ideal for providing a seamless experience.
-
Users would need expertise in the framework we use to build the UI, creating a significant barrier for those unfamiliar with it.
-
Defining the ownership of the code would be challenging. If a user makes a change to the UI, who owns that change? This complicates providing support for the UI.
Due to these drawbacks, we decided that irrespective of whether or not we eventually open source the UI, this can’t be the first step toward a solution. We don’t want to shift the burden of maintaining the UI to the users and claim that the UI is customizable. Rather, we want to provide a seamless experience for them.
Solution
Another solution we devised, and the one we ended up using, was to provide a way to expose the component tree to the users. We created a custom JSX runtime as part of the Baseline UI project that allows users to:
-
Modify the component tree to achieve the desired UI.
-
Utilize existing state management or incorporate their own.
-
Use Baseline UI components in constructing their customized UI.
This solution offers several advantages:
-
Users can modify the UI without maintaining a fork of the UI.
-
Users can modify the UI without being experts in the framework we use.
-
We can provide support for the UI since the code is still owned by us.
Here’s a pseudocode example of the solution:
function increment(number) { return number + 1; }
createBlock(DefaultComponent, {}, ({ ui, state }) => { const stampsPresetButton = ui.getBlockById("stamp-presets-button") stampsPresetButton // Change the prop of a child component. .setProp("variant", "primary") .setProp("onPress", (...args) => { // Access the default prop of the component. stampsPresetButton.props.onPress(...args); // Create a new state variable and use it. state.set("pressed", !state.get("pressed"); }) return ui.createComponent(); } ).createComponent();
In this code, DefaultComponent
is the default UI component exposing the component tree to the users. The ui
is an instance of Block
that contains the component tree, and the state
object contains the state of the UI. Users can modify the component tree to achieve their desired UI. The ui.createComponent()
method creates the UI using the modified component tree.
This is just a glimpse of what the customization API can do. We’re currently working on integrating this solution into our Web SDK, and we’re excited to see how our users will create their own customized UIs with it.
Conclusion
In this article, we discussed the problem of designing a customization API that’s easy to maintain, yet flexible for complex layouts. We also highlighted the advantages of the component tree solution and how it’ll be integrated into our Web SDK. We’re eager to see how it’ll be received by our users and how they’ll use it. If you find this solution interesting and would like to know more about it, contact us. We’d be happy to discuss it further with you.
In future articles, we’ll delve deeper into the technical aspects of the customization API and provide more examples of how it can be utilized. Stay tuned!
To learn more about Baseline UI, you can read Part I, Part II, Part III, Part IV, and Part V of our series.