One Off the Slack: Designer-Resistant Composables

This week’s highlighted Slack thread started off with Jan Skrasek asking about styling of components:

I’d like to style my “components”. Let’s say I have specific color for my button.

Ideally, I like to call Button(backgroundColor = Color.MyColor) , but of course, also content color has to be handled. Explicit passing of such seems to be pretty verbose, but there is not probably any way to modify contentColorFor() behavior for my color. Other options is to create own Button implementation but that’s not much elegant.

Louis pointed out that wrapper functions are a recommended approach for this:

In general, it can be helpful to think of a ‘style’ as a new function, providing different defaults / values to the underlying component.

This is actually how internally some components are implemented, for example, OutlinedButton just calls Button with some different defaults - and the same works well in application code as well.

This also allows you to only expose the parameters / customization your LoginButton actually needs, so you don’t need to expose color customization if you want to ensure it will always be the same color, or you can even provide a higher level API that accepts some object (like a String) that will be internally converted to text, instead of a content lambda.

The other benefit is having semantically named components, rather than everything just being a Button with different theme / style configurations, that make it harder to understand what the component actually represents / how it appears.

Zhelyazko Atanasov pointed out that this approach will be needed to deal with designers who change their minds:

So if I have a relatively large app built with Compose and the UI designer wants to change the background of all the Buttons in the app, without affecting the rest of the components, I have to first create MyFunkyButton composable and replace all Button instances with it, right? If that’s indeed the case, probably it’s worth creating those semantically named “wrapper” composables that follow the company’s design system, saving us the headaches later when the company’s design system changes and it doesn’t 100% follow the Material guidelines

This concept has come up before on the #compose channel: teams creating CompanyButton(), CompanyText(), and so on as thin wrappers around stock composables that implement company (or project) style guides. The code that assembles the actual UI then refers to those, instead of “raw” Compose-supplied composables, so there is one place to make changes when those changes are needed.

Combining this with Louis’ recommendation, we may wind up with a three-tier solution: a LoginButton that configures a CompanyButton() that, in turn, is configuring a Compose Button. LoginButton represents customizations tied to a UI use case, while CompanyButton provides app-wide settings.


Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!