Skip to content

Commit 5c01808

Browse files
committed
docs: propose updated theming approach
1 parent c97f84e commit 5c01808

File tree

3 files changed

+195
-28
lines changed

3 files changed

+195
-28
lines changed

CONTRIBUTING.md

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,36 +55,12 @@ After writing the new translations, run `npm run interface` to generate the type
5555

5656
In your components, first use `useI18n` hook, which takes in component namespace and loads appropriate resource file into dictionary. After that use the `useTranslation` hook to access the translations in that component and any child components.
5757

58-
## Creating components
58+
## Contributing guides
5959

60-
### Block components
60+
We have some more specific contributing guides for best practices in the codebase. We recommend reviewing these prior to taking on a pull request:
6161

62-
Block components are focused, reusable components that serve specific functionality. They follow these patterns:
63-
64-
- **Single Purpose**: Each component should generally handle a specific task (e.g., list, form, etc.)
65-
- **Base Component**: Uses [BaseComponent](./src/components/Base/Base.tsx) for consistent behavior and error handling
66-
- **Compound Pattern**: Exposes subcomponents (Head, List, Actions) for flexibility and composition
67-
68-
#### Examples
69-
70-
- [DocumentList](./src/components/Company/DocumentSigner/DocumentList/DocumentList.tsx)
71-
- [LocationForm](./src/components/Company/Locations/LocationForm/LocationForm.tsx)
72-
73-
### Flow components
74-
75-
Flow components compose block components and other flow components together using state machines to manage transitions. They follow these patterns:
76-
77-
- **State Management**: Uses the [Flow](./src/components/Flow/Flow.tsx) component with a state machine to handle transitions
78-
- **Component Composition**: Can compose both block components (e.g., list → form) and other flow components
79-
- **Naming**: Suffix with "Flow" (e.g., `DocumentSigner`, `Locations`)
80-
81-
For example, `EmployeeOnboardingFlow` composes both block components (profile, taxes) and other flow components (document signer) to create a complete onboarding experience.
82-
83-
#### Examples
84-
85-
- [DocumentSigner](./src/components/Company/DocumentSigner/DocumentSigner.tsx)
86-
- [Locations](./src/components/Company/Locations/Locations.tsx)
87-
- [EmployeeSelfOnboardingFlow](./src/components/Flow/EmployeeSelfOnboardingFlow/EmployeeSelfOnboardingFlow.tsx)
62+
- [Component Structure](./contributing-docs/component-structure.md)
63+
- [Theming](./contributing-docs/theming.md)
8864

8965
## Testing locally
9066

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Component Structure
2+
3+
## Block components
4+
5+
Block components are focused, reusable components that serve specific functionality. They follow these patterns:
6+
7+
- **Single Purpose**: Each component should generally handle a specific task (e.g., list, form, etc.)
8+
- **Base Component**: Uses [BaseComponent](../src/components/Base/Base.tsx) for consistent behavior and error handling
9+
10+
### Examples
11+
12+
- [DocumentList](../src/components/Company/DocumentSigner/DocumentList/DocumentList.tsx)
13+
- [LocationForm](../src/components/Company/Locations/LocationForm/LocationForm.tsx)
14+
15+
## Flow components
16+
17+
Flow components compose block components and possibly other flow components together using state machines to manage transitions. They follow these patterns:
18+
19+
- **State Management**: Uses the [Flow](../src/components/Flow/Flow.tsx) component with a state machine to handle transitions
20+
- **Component Composition**: Can compose both block components (e.g., list → form) and/or other flow components
21+
22+
For example, `Employee.OnboardingFlow` composes both block components (profile, taxes) and other flow components (document signer) to create a complete onboarding experience.
23+
24+
### Examples
25+
26+
- [DocumentSigner](../src/components/Company/DocumentSigner/DocumentSigner.tsx)
27+
- [Locations](../src/components/Company/Locations/Locations.tsx)
28+
- [Employee.OnboardingFlow](../src/components/Employee/OnboardingFlow/OnboardingFlow.tsx)

contributing-docs/theming.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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

Comments
 (0)