Proposal & Discussion: Theme Structure
See original GitHub issueHaving worked on and maintained our company’s internal component library for a while, I know how quickly a “theme” can become messy and difficult to maintain and understand. I think this comes from a lack of clear structure around the goals of the theme and how those goals are accomplished. It’s easy for a theme object to become a ‘junk box’ of values accumulated over time without much thought for consistency. I’d like to poke the bear a bit on this topic.
Disclaimer: I am not a designer. I act in the role of a designer sometimes in my own projects, but that does not make me qualified to speak as one. I would like to know the perspective of designers on this topic and I think it would be very valuable. But ultimately, the theme is code, and I believe its structure should serve developers more than accommodate non-technical users. Tools can always be made to make themes more audit-able by non-technical users if needed (and I know many designers these days are more than comfortable modifying JS).
Goals of a Theme
As a developer / product owner who is looking to adopt a component library, I have a few goals:
- I want to be able to modify the overall ‘look and feel’ of the components to suit my brand
- All the colors should align with my branding
- The effects used should be consistent with the tone and experience I am looking to create (for instance, an enterprise user may not be happy with ‘bubbly’ buttons or loud effects on focus or hover)
- I don’t want to have to poke around every corner of the library to do this. I want to essentially say, “Make the corners really round” or “Make the focus effect 50% transparent” and have my desire applied to the entire suite of components, even the ones I haven’t learned how to use yet
- I want to be able to tweak and customize the finest details of individual components to enable creativity and delight in all my app’s interactions
- I want my app to be opinionated about ‘special effects’ for individual controls, not my library. Don’t force fancy things on me which I may then have to go back and figure out how to remove. Do let me add any fancy thing I want to any individual component I want.
- Grommet already does this pretty well with the
extend
feature and a lack of “fancy” effects (lookin’ at you Material).
Where Grommet can Improve
I think the biggest area of improvement would be in Goal 1. Grommet currently treats both theme values (colors, etc) and components mostly as individual units. There are notable exceptions which I have found to this (theme.global.control
, for instance), but the fact that these exceptions are extremely limited in scope and not documented makes them more confusing than effective (if I want to change the Button border, do I use theme.global.control.border
or theme.button.border
? What is a ‘control
’? Is control
more specific or less specific than an individual component value?)
Suggestions
Currently the theme basically contains two abstractions:
- “Atomic” values (see:
colors.brand
, etc) which have names that describe the values themselves but do not explain their usage practically. Their scope is too high to be used to control component look and feel - if I want to change the color of the primary button, I can’t just changecolors.brand
, because that would also affect a ton of other components. These are for defining the “basics” of your branding only. - “Component” assignment values (see:
button.border.color
) which have names that precisely describe their practical usage, but are too granular and require the user to modify the same value in many places to achieve consistency.
I’d recommend establishing a new level of abstraction around theme values as they are used in “behavior classes” of components. This level would serve as the “base” styling for all components, and would allow overrides via the component-level style definitions as usual.
- Controls:
Button
,Anchor
,CheckBox
,RadioButton
,Tab
RangeInput
,RangeSelector
,Calendar
…- These components use color and shape to suggest user interaction. Their key focus is promoting interaction of various kinds, and they need to be clearly distinguishable from non-interactive content.
- Potential value customizations: borders, colors, interactive state stylings, focus and hover effects, animation timings and easings, padding…
- Inputs:
TextInput
,TextArea
,Select
…- These components also promote interaction, but they are more easily distinguished by the user and don’t necessarily have to rely on color. They also focus on content editing.
- Potential value customizations: borders, colors, interactive state stylings, focus and hover effects, animation timings and easings, padding, font sizes…
- Content:
Layer
,Box
,Grid
, maybeTable
…- These components are focused on providing a space for other content to live in. They should not use much color or draw attention away from interactive elements.
- Potential value customizations: borders, background and foreground colors, corners…
- Text:
Text
,Paragraph
,Heading
…- These components are typography-focused. Usage of color is limited for emphasis only. Sizing, weight and spacing are very important, though.
- Potential value customizations: fonts, sizes, letter spacing, line height, max width, emphasis styling…
- Visualization:
Chart
,Meter
,Diagram
…- These components are designed to display information in a way that makes it easy to consume for users according to specific scenarios. They will likely use a broad range of different colors to distinguish data points.
- Potential value customizations: thicknesses, color gradient values…
Those are just some initial ideas for “classes” of behaviors inside a component library.
From there, I’d propose orienting the theme definition explicitly around these classes of behavior, and pulling definitions of look and feel upward out of the “component” layer and into this new “behavior” layer, leaving the “component” layer almost exclusively with the powerful and useful extend
alone.
Now, as a user approaching the Grommet library with my own brand and desired experience, suppose I want to make my app feel more ‘playful and inviting.’ I review my theme options and decide that I want:
- both my Controls and my Inputs to have rounded corners
primary
Controls should use myaccent-1
color, anddefault
Controls should use myaccent-2
color, since in my look-and-feel I have a wide range of colors I want to show related to my brand- border thicknesses for Controls and Inputs to be thicker than default
- focus and hover effects should be fully opaque and use my
accent-3
color for even more color
Instead of having to modify individual values on Button
, Radio
, CheckBox
, TextInput
, TextArea
… etc, I can just change some defined values in theme.behaviors.controls
and theme.behaviors.inputs
.
Advantages: The theme specification should scale with components which may be added later, since the behaviors
abstraction can fit new behaviors as well as existing ones. If Grommet decides at some point to add a Toggle
component, it can pull its look and feel from the Controls behavior, and users with heavy customization may not have to modify their theme at all to start using it. This also applies to libraries which build higher-level components on top of Grommet; they can also use the standardized theme behaviors to make any novel components inherently theme-compliant.
Disadvantages: Defined behavior categories may not align with all types of components, or may not be able to provide the level of flexibility necessary to accomplish a particular design. Maybe a user wants their CheckBox to be a different color from their Button. However, at that point, they will be simply using the component-level extend
functionality anyways, which they can still do to override Behaviors.
Ok, I’ve talked enough! Would like to hear what you all think about it.
Issue Analytics
- State:
- Created 5 years ago
- Comments:5 (4 by maintainers)
Top GitHub Comments
Its good to keep a list of theme enhancements and i do like your items.
I would add
Unfortunately, I think such changes will have to wait for some time though, as they would break a bunch of existing apps and tools.
I like the primary proposal here. As you mentioned, we tried to head in this direction a bit with
global.control
andglobal.input
but agreed that we didn’t take it very far and it can be confusing to know when it comes into play.My initial thought is to align with how https://v2.grommet.io/components organizes components. Although, perhaps separating the “Input” category up would be needed, as you suggested putting Button and CheckBox together.
I appreciate the detail you’ve put into your proposal. I think we should sketch out a theme structure using that as a starting point. I think we can set a direction to head towards and then consider how to get there. It will be interesting to see how much can be leveraged across components and where components will still need specialization, as @oorestisime suggested.
My inclination is to create a separate branch to explore this further.