People who are building design systems in Compose:...
# compose
z
People who are building design systems in Compose: Are yall using prefixes/suffixes on your component functions to identify them as part of your design system? E.g.
Button
->
MaterialButton
. (Would love to hear your thinking about this in the thread! Why are you or aren’t you using prefixes?)
👌 2
🚫 11
k
We have a lint check to disallow using Material components outside of our design systems' modules. So for components we want to reuse from Material, we add simple delegating composables to our DS (e.g. our
Checkbox
delegates to
material.Checkbox
with appropriate
colors
set)
☝️ 1
k
The simple answer for me with Aurora is yes, I do use prefixes. I also added an explicit
exclude
block in the build script to prevent any accidental dependency on anything in the
org.jetbrains.compose.material
group. But going a bit deeper, my thinking is that I want to de-emphasize the focus on the components themselves. Whatever is displayed on the screen is not the source of truth of the underlying data. Neither should it replicate any parts of that data for its own purposes. Compose encourages that heavily with state hoisting to live outside of the UI representation of the data. A slice of your data may be represented by a button, or a checkbox, or a dropdown selector, or a slider. But that's all a component is - a transient (for the duration of the time the app is running) representation of the data, along with user-facing affordances to manipulate that data (sharing, printing, modification, etc). In Flamingo (my Swing library of components) and soon in Aurora, I call it projections. A slice of your data is projected to the screen as a button, a dropdown, a checkbox, etc. Nothing more. I am willing to go as far as to structure the API around it so that the composable functions that construct the specific UI components are not part of the public API. Instead, it's something like
DataSlice.project(PresentationConfiguration)
that takes a slice of your data, some presentation configuration (say, paddings, text style, icon tinting, ...) and calls whatever internal component composable is needed to represent that data projection on the screen.
z
that sounds like you’re not just building a design system anymore
k
I'd agree with that. It's a rather strongly opinionated library that lets you put stuff on the screen. And at the same time, I'm thinking about deemphasizing the focus on components themselves as top-tier API constructs in it.
👍🏻 1
j
Our goal is for all UI to consist of a single design system's components, ie we try not to mix-and-match Material and our internal components as we have a sibling implementation of components for SwiftUI regardless. We do not see any value in prefixing the names, and prefixes would feel like redundant noise. In practice, many of the component implementations do delegate to Material, and our theme maps closely (and sets the appropriate composition locals) to MaterialTheme. So even if we do choose to mix-and-match, the Material components fit right in and are themed correctly. The idea of a Lint rule to discourage this does seem intriguing.
y
For my own design system, I just add a prefix to all my composable functions (I use
Yasan
as a prefix since I don't really have a name for my design system lol). It makes it easier to use/find them when there are tons of other composable functions that I use much less frequently. I try to convert all the composable functions I need to use to
Yasan
composable functions and never directly use non-
Yasan
composable functions to keep my app's UI consistent and easy to update.
l
Part of the design goal for
androidx.compose.material
was that the Material library is an implementation of a specific design system - it isn’t the design system. This is why there is a strong separation between
foundation
and
material
, so that it is easy to build a different design system fully on top of
foundation
, without needing anything from
material
. As a result there are no prefixes, since the idea is that each design system just reserves the ‘best names’ (such as
Button
for itself. If you are just building something slightly different on top of Material, then keeping
androidx.compose.material
as an
implementation
dependency also allows you to use the simple un-prefixed names for your components. This also means you can’t accidentally use
Button
instead of
FooButton
, and means that all your components are consistently invoked
👍 4
👍🏻 1
c
@KoskiA share that lint check! 😄 Kidding, but it would be useful to many people I think. I know we're looking at implementing something similar.
z
So I get @YASAN‘s argument, but doesn’t that just result in a bunch of code where almost every composable call has a
Yasan
prefix? It seems to me that would make the code harder to read, since the prefix doesn’t add any semantic information given your entire app is using your design system and there’s no need to distinguish between components from different systems.
k
Is there an expectation that all the design systems share identical or very similar API surface? From the perspective of how many components they provide, and how close each component composable signature is to the material composables?
y
@Zach Klippenstein (he/him) [MOD] I only use the prefix for the composables that basically override the basic version of a composable. So I have a
YasanTextField
and I never use
TextField
. I think the issue you see in this method is because my previous comment made it look like all my composables have the prefix. I barely need to use the prefix for the "normal" parts of my app. I mainly just use it for my setting screens which have lots of custom switches, checkboxes etc. because I cant find a better way of handling it without potential issues/conflicts later on. It also feels like a filter to me as well since I can just see a list of all
yasan
composables once I write the prefix which lets me choose my custom composable easier. I get what your problem with this system is and to be honest it is getting a little bit messy as my app gets larger and I should probably try avoiding overusing the prefix as much as possible and only use it when needed. Based on the scale of the source code I guess it can be manageable if not overused. Also since I havent found a way to deal with this in a better way I dont really know what my other options are.
z
Ah, I see. Interesting insight on scaling too, thanks.
y
Compose is pretty new so we might discover new ways of handling these things with more experience I guess.
l
Is there an expectation that all the design systems share identical or very similar API surface?
The opposite, actually. That’s why ‘components’ only exist in
material
- trying to provide a generic set of components at a lower level would require building abstractions with an API surface that are universally applicable across all design systems. For public design system libraries that are offered to developers as an alternative to
material
, there might be some benefit in having similar concepts to make adoption easier, but the goal is that each design system uses names and API surfaces that make it easy to use that design system, rather than consistency across every design system library.
Also since I havent found a way to deal with this in a better way I dont really know what my other options are.
The best way of handling this is probably treating these custom components as your design system - and just make wrapper functions for components that you don’t change, in the same way. That way all your components appear the same, and if in the future you want to customize a component you didn’t change before, you don’t need to first migrate all usages to your custom-prefixed version. It’s more work up front, but it will save you time in the future