|
| 1 | +# RFC: Theming System Design for Design System SDK |
| 2 | + |
| 3 | +> Note: once an approach is approved, this document will be updated to document the consensus approach with more detailed guidelines. |
| 4 | +
|
| 5 | +## Background |
| 6 | + |
| 7 | +As we have scaled, it's become apparent that our current theming approach has some areas for improvement. The biggest thing is getting alignment with design and creating an approach that is consistent and predictable for engineers and easy to use for partners. |
| 8 | + |
| 9 | +## Should we even do this? A foundational theming question in an adapter world |
| 10 | + |
| 11 | +Since we've introduced adapters, there have been even more questions around the role of theming in the SDK. Consumers can now supply their own React components which allows exact consistency with UI in the rest of their application. |
| 12 | + |
| 13 | +There is a preliminary question here on if theming is even necessary in a world with adapters given anything that can be done with theming can be done more completely with component adapters. Based on prior team discussions, we considered the following: |
| 14 | + |
| 15 | +- Setting up adapters is more technically involved |
| 16 | +- Adapters represent visual updates on a component by component basis, it's harder to impact UI globally (vs. setting a single color variable that cascades everywhere) |
| 17 | +- We have some signal that partners with more bias towards speed to launch may be less particular and may prefer a lighter touch solution |
| 18 | + |
| 19 | +This document will assume we are moving forward with a theming approach even in a world with component adapters, but this can still be an open topic of conversation as a foundational concern. |
| 20 | + |
| 21 | +## Challenge with the Current Theming Approach |
| 22 | + |
| 23 | +- Inconsistent naming conventions (camelCase vs. kebab-case) |
| 24 | +- Inconsistent use of abbreviations (`bg`, `fg`, `tx`, etc.) |
| 25 | +- Inconsistent use of component variants in naming as well as inconsistencies in the CSS state |
| 26 | +- Difficult to know which theme variables a component should support |
| 27 | + |
| 28 | +Example inconsistencies: `checkbox-selectedColor` vs `checkbox-hover-checkedBackground` vs. `input-hovered-borderColor` |
| 29 | + |
| 30 | +## What We Want in a Solution |
| 31 | + |
| 32 | +- A consistent and predictable naming convention |
| 33 | +- Alignment between design tokens in Figma and code |
| 34 | +- Clear rules around what should be themed |
| 35 | +- Simple usage for partners |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## Proposed Approaches |
| 40 | + |
| 41 | +### 1. Global-Only Theming |
| 42 | + |
| 43 | +This approach restricts theming to a set of high-level, global tokens: |
| 44 | + |
| 45 | +- Colors (e.g. `--color-gray-100`) |
| 46 | +- Typography (e.g. `--font-size-regular`, `--font-weight-medium`) |
| 47 | +- Radius, spacing, shadows, etc. |
| 48 | + |
| 49 | +These variables are used by components internally but are not structured by component or interaction state. |
| 50 | + |
| 51 | +#### Example |
| 52 | + |
| 53 | +```ts |
| 54 | +const theme = { |
| 55 | + colors: { |
| 56 | + gray: { |
| 57 | + 100: '#FFFFFF', |
| 58 | + 1000: '#1C1C1C', |
| 59 | + }, |
| 60 | + success: { |
| 61 | + 500: '#0A8080', |
| 62 | + }, |
| 63 | + }, |
| 64 | + fontSize: { |
| 65 | + regular: '16px', |
| 66 | + small: '14px', |
| 67 | + }, |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +CSS output: |
| 72 | + |
| 73 | +```css |
| 74 | +:root { |
| 75 | + --color-gray-100: #ffffff; |
| 76 | + --font-size-regular: 16px; |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +Variables used in a component: |
| 81 | + |
| 82 | +```css |
| 83 | +.button { |
| 84 | + background-color: var(--color-gray-1000); |
| 85 | + font-size: var(--font-size-regular); |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +#### Pros |
| 90 | + |
| 91 | +- Easier to maintain and reason about |
| 92 | +- Smaller API surface |
| 93 | +- Good for simple themes or lighter branding control |
| 94 | +- High global impact with minimal configuration |
| 95 | + |
| 96 | +#### Cons |
| 97 | + |
| 98 | +- Less flexibility at the individual component level, no theming escape hatch for overrides |
| 99 | +- Difficult to support interactive or variant-based styling (e.g. hover, pressed) |
| 100 | +- Challenging to scale according to partner needs without introducing component level variables |
| 101 | + |
| 102 | +### 2. Structured Component-Level Theming |
| 103 | + |
| 104 | +This approach defines theme tokens at the component level, optionally scoped by variant and CSS state. Naming follows a standardized structure: |
| 105 | + |
| 106 | +```css |
| 107 | +[component](-[variant])?(-[css-state])?-[css-property] |
| 108 | +Tokens are stored in a deeply nested object and compiled to CSS custom properties. |
| 109 | +``` |
| 110 | + |
| 111 | +```ts |
| 112 | +const theme = { |
| 113 | + button: { |
| 114 | + primary: { |
| 115 | + background-color: '#1C1C1C', |
| 116 | + hover: { |
| 117 | + background-color: '#6C6C72', |
| 118 | + }, |
| 119 | + }, |
| 120 | + secondary: { |
| 121 | + background-color: '#FFFFFF', |
| 122 | + }, |
| 123 | + }, |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +CSS output: |
| 128 | + |
| 129 | +```css |
| 130 | +:root { |
| 131 | + --button-primary-background-color: #1c1c1c; |
| 132 | + --button-primary-hover-background-color: #6c6c72; |
| 133 | + --button-secondary-background-color: #ffffff; |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +Usage within a component |
| 138 | + |
| 139 | +```css |
| 140 | +.button--primary:hover { |
| 141 | + background-color: var(--button-primary-hover-background-color); |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | +#### Pros |
| 146 | + |
| 147 | +- Clear, predictable structure for every component |
| 148 | +- Tokens are directly linked to component APIs and design specs |
| 149 | +- Easy to scale to variants, states, and specific overrides |
| 150 | + |
| 151 | +#### Cons |
| 152 | + |
| 153 | +- Higher overhead to define and maintain |
| 154 | +- Larger token surface area |
| 155 | +- Slightly more complex theme authoring experience |
| 156 | + |
| 157 | +#### Open Design Consideration around global variables |
| 158 | + |
| 159 | +One option for simplification with this approach would be forgoing exposing the global variable set. Ex. currently we have things like `colors` exposed (`--color-gray-100`) in addition to the component level themes like `--button-primary-background-color`. We could just opt to have the component level variables with a very limited set of global items (for things like font). |
| 160 | + |
| 161 | +## Recommendation |
| 162 | + |
| 163 | +My recommendation is using option 2. At a minimum, option 1 would likely need to be modified to expose at least some component level variables. But working with design, we started with an option 1 approach and realized a system that didn't expose at least some variables for component level states (especially around interactive components, button/input) would likely not be viable for a partner. Global cascades are highly opinionated about our mapping and would make it challenging for a partner to get something resembling their own brand without being able to modify at the component level. |
0 commit comments