Announcing jsxstyle 1.0
jsxstyle is now 1.0. We’ve been using it for over a year at Smyte, and we’ve learned a lot about what works and what doesn’t when it comes to inline styles.
Why inline styles?
Christopher Chedeau did a great presentation about the many advantages of using inline styles in your React apps. He also highlights many of the problems with traditional CSS techniques, including:
- Inheritance can change how a component looks depending on where it is used, making reuse more challenging.
- Rule specificity can change depending on when a stylesheet is loaded, making rendering unpredictable when lazy-loading CSS.
- CSS class names exist in one large, global namespace, making modularity difficult.
These problems stem from the fact that CSS was designed for documents, not for components.
Inheritance and specificity are part of the cascading and inheritance module of the CSS spec. In their book Cascading Style Sheets: designing for the Web, Hakon Wium Lie and Bert Bos (two of CSS’s original authors) described the cascade:
CSS had one feature that distinguished it from all the others: it took into account that on the Web the style of a document couldn’t be designed by either the author or the reader on their own, but that their wishes had to be combined, or “cascaded,” in some way; and, in fact, not just the reader’s and the author’s wishes, but also the capabilities of the display device and the browser.
While this was an incredibly forward-thinking idea when it was proposed in 1994, user stylesheets never really caught on and were removed from Chrome a few years ago.
The ideal React API
One observation I’ve made while building apps on the web over the years is that I have a few HTML elements that describe the semantic structure of my app, a few that implement interactive behaviors, and a lot of <div>
s and<span>
s that exist only for styling.
When building apps with CSS, these style-only <div>
s and <span>
s need their own unique class name. Since they exist only for styling, they tend to get confusing, meaningless names like outer-wrapper-container
or inner-content-area-layout
. If we replace these with inline styles, we can save a lot of typing and naming fatigue. To reuse styles, we simply reuse React components rather than reusing class names throughout the codebase.
After building apps with inline styles for a while, I realized that I always want to set a specific display
CSS property, and zero or more optional CSS properties.
I used to write code like this:
<div style={{border: '1px solid gray'}}>
<span style={{fontWeight: 'bold', color: 'gray'}}>{username}</span>:
<span style={{color: 'black'}}>{content}</span>
</div>
div
and span
simply indicate display: block
and display: inline
respectively, and style
is simply noise, since all of these elements are for layout only.
With jsxstyle
I’m writing:
<Block border="1px solid gray">
<Inline fontWeight="bold" color="gray">{username}</Inline>
<Inline color="black">{content}</Inline>
</Block>
It’s much easier to visualize the layout without running the code, isn’t it? Especially when compared with the stylesheet version using class names:
<div className="unit-wrapper">
<div className="unit-username">{username}</div>
<div className="unit-content">{content}</div>
</div>
Problems with the naive approach
There are a lot of problems with naively using inline styles:
- Performance: inserting huge strings of inline styles caused serious performance problems (several hundred millisecond pauses or more) at Smyte.
- Accessibility and semantics: if the tag name is no longer required, how do we encourage developers to think about semantics and accessibility?
- Pseudoclasses: how do you support hover states? Media queries?
The ideal inline style library would let you write code in the style above, but generate identical DOM structure and CSS stylesheets had you written separate stylesheets yourself.
The jsxstyle
approach
jsxstyle
isn’t perfect but gets closer than anything else to these goals.
To improve performance, rather than insert large strings of inline styles at runtime, jsxstyle
employs two main optimizations:
- It creates a unique hash of the style props being applied to a component. If that hash hasn’t been seen before, it inserts a new
<style>
tag with a short, unique class name corresponding to that hash. If the hash has been seen before, the existing class name is reused. This drastically reduces the number of styles added to the DOM. When a style is no longer used it is eventually reaped and removed from the DOM. - It includes an experimental webpack loader to extract literal styles into static stylesheets at build time, further reducing the amount of diffing and style insertion that needs to be done at runtime. Furthermore, it can evaluate simple expressions and known constants at build time. For example, it can convert
<Block width={GRID_UNIT * 2} border=“1px solid gray” color={color}>
to<Block className=“...autogenerated class name...” color={color}>
jsxstyle
views accessibility and semantics as orthogonal concerns as jsxstyle
components are purely for visual presentation (though you can provide an explicit component
prop if you’d like to change the underlying HTML tag). One should explicitly think about semantics and introduce those tags as appropriate. Additionally, accessibility needs to be a first-class concern during development and part of the QA process. Finally, I think there is great opportunity for accessibility testing; see the react-a11y project for one interesting idea that is much more powerful than the accessibility primitives built into the browser. This is the future we should be building towards.
Pseudoclasses are another problem. jsxstyle
addresses it using pseudoclass prefixes. For example, if you want to change the color of something when it’s hovered, simply do: <Inline color=“black” hoverColor=“yellow”>
.
Future work
There are a few things we’re going to do to improve jsxstyle
.
- Better support for server rendering. Right now the webpack loader does not pick up on all static styles, which can lead to excessive stylesheets being inserted into the DOM at runtime which can have suboptimal performance characteristics, especially with server rendering.
- Media queries. We still need to figure out a way to solve this problem.
- Mobile testing: Smyte runs only on desktop browsers, so we haven’t done extensive performance testing on mobile yet.
At this point, after over a year of dogfooding we feel that jsxstyle
is the best-in-class developer experience and is ready for widespread usage for client-rendered apps targeting desktop browsers. We hope you find it useful!