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 aString
) that will be internally converted to text, instead of acontent
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!