Style System: CSS-in-JS Workflow
See original GitHub issueHaiii!! š
Iāve been thinking and researching a lot in various technical areas for component libraries + css. The notable topics involve performance and user experience (users being designers/devs).
Performance
Since there will be many instances of these components, it is favourable that they have high performance out-of-the-box, with little to no overhead from consumers of the library.
User Experience
The trade-off for having high-perf typically comes at a cost of the internal code being more verbose. This ultimately degrades user experience - for contributors working on the internals and for consumers of the library.
css
vs styled
Without question, it appears that the (mostly) static css
is more performant. Thereās less computation, and there are far less component wrappers. styled
offers a much more fluid user experience when it comes to (automatic) prop filtering, theme
context consumption, composition and more.
For this āsystemā idea, Iāve attempted to go with css
.
Concept
I have a working example of this system
concept. The main workflow revolves around a (singleton) system
object.
From a component library workflow perspective, system
offers 2 things:
- A way to quickly access config values (e.g.
color
) - A way to render high performant (enhanced) React primitive components (e.g.
system.div
)
Example
import { css, system } from '@wp-g2/system';
const { get } = system;
// Defining component styles
const button = css`
background: ${get('colorBrand')};
border-radius: ${get('buttonBorderRadius')};
color: ${get('colorBrandText')};
display: inline-flex;
font-weight: 400;
justify-content: center;
padding: ${get('buttonPadding')};
user-select: none;
`;
const buttonLarge = css`
font-size: 22px;
`;
// component library level
// uses system.base component
const Button = ({ isLarge, ...props }) => {
return (
<system.button
cx={[button, isLarge && buttonLarge]}
{...props}
/>
);
};
// consumer level
// overrides can happen with special css prop, enabled by system.base
const Example = () => {
return (
<>
<Button css={`background: red;`} isLarge>
Example
</Button>
</>
);
};
I feel like this offers a balanced workflow!
- A āCSS frameworkā layer is not required. An example of this would be something like Taychons or Tailwind.
- Library level: Main difference would be using
system.div
vsdiv
. Otherwise, itās mostly the same. - Consumer level: Unchanged. A bonus would be the inclusion of a special
css
prop for ad-hoc modifications.
system.base
The system
primitives are light-weight wrappers around React.createElement
. They include a couple of special props that would be used internally.
cx
: Accepts styles generated bycss
. StreamlinesclassName
merging with incoming props
The result is a much lighter component tree stack.
Before (using styled
and BaseView
)

After (using css
and system
)

system.get
The idea for get
would be a hook-less way to retrieve (config) values for styling with css
.
System Config
In order for hookless features like .get()
to work, the system config exists as a singleton - not unlike (deep) emotion
internals.
Link to Storybook experiments: https://github.com/ItsJonQ/g2/blob/master/packages/components/src/__stories__/System.stories.js
ccāing @diegohaz ā¤ļø
Issue Analytics
- State:
- Created 3 years ago
- Reactions:2
- Comments:30 (21 by maintainers)
Extra Specificity (Automatically)
Oh boy! I just add a custom stylis plugin that boosts specificity of rendered styles.
That means, that (Emotion) generated styles automatically look like this:
This greatly improves the reliability of these Components rendering into an environment with existing CSS rules.
Everything still works as expected āļø
Note: This isnāt full-proof. Existing rules that are MORE specific (e.g. prefixed with an
#id
) still beat these.Also, another problem with the getter resolved at the module scope is that itāll only return the initial value. If the theme is updated at runtime, styles will not be affected.
So, if we want to support theme changes, we would have to resolve the getter at a later point anyway, even using a singleton. We just wouldnāt need to use a hook or hook into
React.createElement
(jsx
) to access the context. But we would probably need a hook to subscribe to the singleton changes. š