Skip to content

Commit b4f98cb

Browse files
committed
Edit pass on the post. More to come
1 parent 4664c52 commit b4f98cb

File tree

1 file changed

+88
-90
lines changed

1 file changed

+88
-90
lines changed

apps/docs/blog/2024-10-10-designing-for-composition.mdx renamed to apps/docs/blog/2025-01-10-designing-for-composition.mdx

Lines changed: 88 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -12,96 +12,99 @@ tags:
1212
- design
1313
---
1414

15-
## Style Composition has never been easy
15+
A previous article discussed how StyleX was initially created to address the
16+
needs that arose during the facebook.com rewrite. However, early on, StyleX
17+
looked quite different from what it is today.
1618

17-
Style composition has never been easy with CSS. Applying multiple CSS rules
18-
to the same HTML element usually leads to a constant battle with CSS order,
19-
selector specificity, and growing CSS files.
19+
StyleX was developed to replace our previous styling solution, which was
20+
similar to CSS modules. The biggest challenge we faced was the massive amount
21+
of CSS and the style recalculation costs of lazy loading styles. So, the very
22+
first version of StyleX generated atomic classes.
2023

21-
CSS has evolved to provide additional tools such as `@layer` which makes
22-
the problem slightly better. However, the problem persists and the responsibility
23-
for solving the problem remains on tools such as StyleX.
24+
In fact, that was *all* StyleX did for some time. We used utilities like
25+
`classnames` to compose styles just like we did with our previous styling
26+
system. The API looked quite different back then:
2427

25-
## Style Composition is desirable
28+
```tsx
29+
const styles = stylex({
30+
root: {
31+
color: 'white',
32+
backgroundColor: 'blue',
33+
}
34+
});
2635

27-
Despite massive challenges with style composition, developers have always
28-
wanted ways to compose styles. Even before CSS-in-JS libraries became
29-
popular, developers used packages such as `classnames` to try and apply
30-
multiple sets of styles to the same element, despite all of the challenges
31-
that come with it.
36+
<div className={styles('root')} />
37+
```
3238

33-
In any design system, it is common to modify or override certain styles
34-
of components within certain contexts. It is also useful to be able to use
35-
component libraries and customize them to fit the design of the application.
36-
The alternative leads to unnecessary duplication of styles and code.
39+
It was impossible to use `styles` outside of the file they were defined in.
3740

38-
The rise of runtime CSS-in-JS libraries was fuelled, in part, by the need
39-
to solve the style composition problem. By being able to generate and inject
40-
styles at runtime, these libraries were able to provide a way to compose
41-
styles in a way that was not possible with traditional CSS. However, this
42-
also came with performance trade-offs.
41+
## The Need for Style Composition
4342

44-
In recent years, just as we've seen a rise in the popularity of atomic CSS,
45-
we've also seen utilities that merge such styles gain prevalence. Many tools
46-
now offer a way to merge styles.
43+
Initially, it seemed like style composition was unnecessary. After all, we'd been
44+
able to get by with `classnames` to compose styles from CSS modules for years.
45+
But as we built a component library on top of StyleX, we realized that we *needed*
46+
style composition. We needed the ability to pass styles around as props
47+
to components. It became clear that style composition was already
48+
pervasive in our codebase. However, style composition with `classnames` was
49+
inconsistent and unpredictable, and we had learned to accept it.
4750

48-
Despite all the tools, style composition remains a challenge. From headless
49-
UI libraries to design system components that are copied to your own codebase,
50-
we believe many of these tools need to exist entirely because of the inconsistencies
51-
of merging styles.
51+
Style composition has never been easy with CSS. Applying multiple CSS rules
52+
to the same HTML element usually leads to a constant battle with CSS order,
53+
selector specificity, and growing CSS files. CSS has evolved to provide
54+
additional tools such as `@layer`, which makes the problem slightly better.
55+
However, the problem persists.
5256

53-
We believe that we've built the first solution with style composition at its very core.
57+
In any design system, it is common to modify or override certain styles
58+
of components within certain contexts. Component libraries need to be customizable
59+
to fit different application designs. Without proper style composition, we end up
60+
with unnecessary duplication of styles and code.
5461

55-
## Inline Styles have always been composable
62+
The rise of runtime CSS-in-JS libraries was fueled, in part, by this need
63+
to solve the style composition problem. By being able to generate and inject
64+
styles at runtime, these libraries provided a way to compose
65+
styles in a way that was not possible with traditional CSS. However, this
66+
also came with performance trade-offs.
5667

57-
Despite the challenges of composing styles with CSS, inline styles have always
58-
been composable. In HTML, `style` accepts a string with a list of styles. In
59-
this string, *the last style applied always wins*.
68+
## Learning from Inline Styles
6069

61-
Inline styles have their own limitations, too. They don't support pseudo
62-
classes, media queries, or other selectors. It is also difficult to enforce
63-
consistency when using inline styles directly in HTML.
70+
While exploring solutions, we made an interesting observation: inline styles have
71+
always been naturally composable. In HTML, `style` accepts a string with a list
72+
of styles. In this string, *the last style applied always wins*.
6473

65-
However, component-based frameworks, such as React, largely sidestep any
66-
architectural issues with inline styles. Components are a layer of abstraction
67-
that enables code reuse without needing to write the same styles over and over
68-
again. This change was noticed early and was described in the original
69-
[`CSS-in-JS` talk by _Christopher Cheadeu_](https://vimeo.com/116209150).
74+
Inline styles have their own limitations, too. They don't support pseudo-classes,
75+
media queries, or other selectors. It is also difficult to enforce
76+
consistency when using inline styles directly in HTML. However, component-based
77+
frameworks, such as React, largely sidestep any architectural issues with inline
78+
styles. Components are a layer of abstraction that enables code reuse without
79+
needing to write the same styles over and over again. This change was noticed
80+
early and was described in the original [`CSS-in-JS` talk by _Christopher Cheadeu_](https://vimeo.com/116209150).
7081

7182
So, when it came to designing StyleX, we decided to model our styles after
7283
inline styles. To form a mental model, it can be helpful to think of StyleX
7384
as "inline styles without the limitations".
7485

75-
StyleX gives you the ability to use capabilities such as pseudo classes and
76-
media queries, which are not possible with inline styles, all while maintaining
77-
the ability to compose styles in the same way that inline styles do.
86+
## Key Design Decisions
7887

79-
## Do "inline styles" need to be defined inline?
88+
### Static Style Definitions
8089

81-
By the time we made the conscious decision to model StyleX after inline styles,
82-
StyleX had already been in development for a while and had evolved organically
83-
inspired by React Native's `StyleSheet` API, which was itself inspired by inline
84-
styles.
90+
By the time we made this conscious decision, StyleX had already been in development
91+
for a while and had evolved organically, inspired by React Native's `StyleSheet` API,
92+
which was itself inspired by inline styles.
8593

86-
And so one of the first design decisions we reconsidered was the requirement to
94+
One of the first design decisions we reconsidered was the requirement to
8795
declare `stylex.create` before using it, without allowing the definition of styles
88-
inline. There are trade-offs to both approaches, we realised, that we *had* to
89-
allow the ability to statically define styles as JavaScript constants and be
90-
able to reuse them across multiple elements, multiple components, and
91-
even multiple files. Once we had this realization, we felt more comfortable
92-
not offering the ability to define styles inline. Even if occasionally inconvenient,
93-
it is better to have *one* consistent way to define styles.
96+
inline. We realized that we *had* to allow the ability to statically define
97+
styles as JavaScript constants and be able to reuse them across multiple elements,
98+
multiple components, and even multiple files. Once we had this realization, we
99+
felt more comfortable not offering the ability to define styles inline. Even if
100+
occasionally inconvenient, it is better to have *one* consistent way to define styles.
94101

95-
The requirement to define styles at the top-level of a module also enforces some of
96-
the requirements of the compiler without feeling *too* unnatural.
102+
### Pseudo-Classes and Media Queries
97103

98-
99-
## Pseudo Classes and Media Queries
100-
101-
Our next design question was to decide how to handle pseudo classes, media queries
104+
Our next design question was to decide how to handle pseudo-classes, media queries,
102105
and other at-rules in a way that *felt* like inline styles and enabled composability.
103-
Inline styles don't support pseudo classes or media queries, but it's possible
104-
to use JavaScript to read such 'conditions' and apply styles accordingly.
106+
Inline styles don't support pseudo-classes or media queries, but it's possible
107+
to use JavaScript to read such 'conditions' and apply styles accordingly:
105108

106109
```tsx
107110
<div style={{
@@ -115,20 +118,20 @@ to use JavaScript to read such 'conditions' and apply styles accordingly.
115118
```
116119

117120
This approach has obvious performance implications, but it also has some strong
118-
architectural benefits. The style object remains flat, making composition of
121+
architectural benefits. The style object remains flat, making the composition of
119122
styles more predictable. We avoid having to deal with complex rules for merging
120123
styles and dealing with specificity issues. We don't have to think about the
121124
'default color' or the 'hover color'. We just think about a single 'color'
122125
value that changes based on the conditions.
123126

124-
All values for a property being co-located can also lead to a more consistent
127+
All values for a property being co-located can also lead to a more consistent
125128
design system. Instead of thinking about mobile styles or desktop styles, this
126129
approach forces you to think responsively about the value of each property.
127130

128131
This realization led to one of our most unique design decisions. Instead of separating "base"
129132
styles and styles under a media query, which is common in almost every other CSS
130133
library, we decided to treat pseudo-classes and media queries as 'conditions' *within*
131-
the value of a property.
134+
the value of a property:
132135

133136
```tsx
134137
const styles = stylex.create({
@@ -149,44 +152,39 @@ We found a way to take the concept of JavaScript conditions from inline styles
149152
and express them declaratively and statically in a way that can be compiled to
150153
static CSS while keeping the architectural benefits.
151154

152-
## CSS Shorthands
155+
### CSS Shorthands
153156

154157
CSS shorthand properties introduce another factor of confusion when composing
155158
styles. This decision has remained controversial even within the team itself,
156159
and so we still support two different strategies for merging shorthands.
157160

158-
### React Native's Strategy
159-
160-
React Native pioneered the approach of merging purely by key, and assigning
161+
React Native pioneered the approach of merging purely by key and assigning
161162
different priorities to different properties. Longhand properties have a higher
162163
priority than shorthand properties. So, while you *can* end up
163164
with a style object that has both `padding` and `paddingTop`, `paddingTop` will
164165
take precedence regardless of the order in which they are applied.
165166

166-
### Inline Styles Strategy
167-
168167
Inline styles, on the other hand, always merge by order of application, and
169-
the last style applied always wins. Modelling this behaviour in library that
168+
the last style applied always wins. Modeling this behavior in a library that
170169
compiles to static CSS wasn't obvious at first. We started by expanding
171170
shorthands into longhand properties at compile time. This approach had many
172171
edge cases and couldn't be done reliably in all cases.
173172

174173
We eventually discovered a strategy where a shorthand property would also set
175174
its constituent longhand properties to `null`. This would, in practice, "unset"
176-
any longhand that may have been applied before. This achieves the exact the
177-
same behaviour as inline styles, where the last style applied always wins.
178-
179-
This approach has some performance trade-offs. It can result in larger JavaScript objects
180-
after compilation. However, as our overall design philosophy was to
181-
model StyleX after inline styles, we decided to make this the default behaviour.
175+
any longhand that may have been applied before. This achieves the exact
176+
same behavior as inline styles, where the last style applied always wins.
177+
While this approach can result in larger JavaScript objects after compilation,
178+
we decided to make it the default behavior as it aligns with our inline styles
179+
mental model.
182180

183-
## Dynamic Styles
181+
### Dynamic Styles
184182

185-
Any API modelled after inline styles must support dynamic styles, but we knew we
186-
needed to do it with care and intention. We wanted to make it possible to use
187-
dynamic styles when needed, but we also wanted to make it explicit when styles
188-
were dynamic. We don't want an API that makes it easy to accidentally create
189-
dynamic styles.
183+
Finally, we needed to handle dynamic styles. Any API modeled after inline styles
184+
must support them, but we knew we needed to do it with care and intention. We
185+
wanted to make it possible to use dynamic styles when needed, but we also wanted
186+
to make it explicit when styles were dynamic. We don't want an API that makes it
187+
easy to accidentally create dynamic styles.
190188

191189
StyleX allows dynamic styles by using functions. Instead of mixing inline objects
192190
with static styles created with `stylex.create`, functions let us define all kinds
@@ -203,13 +201,13 @@ certain compile-time optimizations possible, but that's its own story.)
203201

204202
CSS has been around for a long time now. It has evolved in many ways and is now both
205203
flexible and one of the most powerful layout models that exist. Yet, it remains a
206-
tool with sharp edges and it can be challenging to wield effectively.
204+
tool with sharp edges and can be challenging to wield effectively.
207205

208206
We've seen many tools and libraries that have tried to make CSS easier
209207
to work with. Over the years, many problems have been solved, but the problem of
210208
style composition has persisted to some extent. Even when it's possible to compose
211-
styles, there have always been inconsistencies and unpredictable behaviour.
209+
styles, there have always been inconsistencies and unpredictable behavior.
212210

213-
Yes, we are building a styling solution that is fast, scalable and easy to use.
211+
Yes, we are building a styling solution that is fast, scalable, and easy to use.
214212
But we are also building a solution that actually provides predictable style
215213
composition without any nasty surprises.

0 commit comments

Comments
 (0)