Sx
:honeybee: Atomic CSS-in-JS (not only) for Next.js applications. This repository is automatically exported from https://github.com/adeira/universe via Shipit
Install / Use
/learn @adeira/SxREADME
In conventional applications, CSS rules are duplicated throughout the stylesheet and that just means wasted bytes. Instead, SX generates atomic stylesheet every time so that each rule is defined only once. Each rule has its own CSS class and components can pick up multiple classes to get the same effect as with traditional stylesheets. New JS code doesn't need to mean new CSS (size of CSS grows logarithmically).
Installation and Usage
First, install the package from NPM:
yarn add @adeira/sx
It's highly recommended (but optional) to use related eslint-plugin-sx as well:
yarn add --dev eslint-plugin-sx
Create a stylesheet and use it to generate className props for React:
import sx from '@adeira/sx';
export default function Example() {
// className={styles('example')}
// ↓ ↓ ↓
// class="_1DKsqE v2kHO stDQH"
return <div className={styles('example')}>example</div>;
}
const styles = sx.create({
example: {
fontSize: 32, // converted to PX units
textDecoration: 'none',
backgroundColor: 'var(--main-bg-color)', // CSS variables are supported as well
},
});
That's it. The example above will generate atomic CSS like this:
._1DKsqE {
font-size: 32px;
}
.v2kHO {
text-decoration: none;
}
.stDQH {
background-color: var(--main-bg-color);
}
It's highly recommended enabling server-side rendered styles for production use (see below).
SX Ecosystem
SX itself is just a core of the atomic CSS for JS. There are other projects helping with writing the SX code or building on top of it (sorted alphabetically):
- SX Design - inclusive design system based on SX
- SX Eslint - Eslint rules for SX
- SX Jest Snapshot Serializer - Jest serializer for SX (WIP 🚧)
Missing something? Let us know!
Features
Multiple stylesheets precedence
The final style depends on the order of styles arguments rather than the style definitions as it's usual in plain CSS:
export function ColorfulComponent() {
return (
<>
<div className={styles('red', 'blue')}>I am BLUE</div>
<div className={styles('blue', 'red')}>I am RED</div>
</>
);
}
const styles = sx.create({
red: { color: 'red' },
blue: { color: 'blue' },
});
This makes it very predictable and easy to use. Always call className={styles('red', 'blue')} instead of className={`${styles('red')} ${styles('blue')}`}! This is very important because the first call guarantees to resolve the CSS precedence correctly as opposed to the second call which does not and might behave unpredictably.
It works similarly for shorthand CSS properties (however, try to avoid them - see below):
export function ButtonsComponent() {
return (
<>
<button className={styles('button', 'primary')}>WITH margin top 10px</button>
<button className={styles('primary', 'button')}>WITHOUT margin top</button>
</>
);
}
const styles = sx.create({
primary: { marginTop: '10px' },
button: { margin: 0 },
});
It's better to avoid shorthand CSS properties in SX because it yields larger output. Using explicit properties like background-color, margin-top and similar will result in a smaller output since we can leverage browser defaults better. It's because all shorthand properties need to be expanded into their initial values (which is normally job of a web browser). Without expanding them, we could not resolve cases like this one:
const styles = sx.create({
bgBlue: { background: 'blue' },
bgNone: { background: 'none' },
});
<div className={styles('bgBlue', 'bgNone')}>I am blue or without background?</div>;
The div should not have a color. We achieve this effect by expanding the background property and merging it together with the other `background shorthand. The resulting style would be:
background-image: none
background-position: 0% 0%
background-size: auto auto
background-repeat: repeat
background-origin: padding-box
background-clip: border-box
background-attachment: scroll
background-color: transparent
Changing the styles order would result in a blue div (this is different from how CSS normally works but it's more obvious in CSS-in-JS context):
<div className={styles('bgNone', 'bgBlue')}>I am BLUE!</div>
Pseudo CSS classes and elements
export function LinkComponent() {
return <a className={styles('link')}>link</a>;
}
const styles = sx.create({
link: {
'textDecoration': 'none',
':hover': {
textDecoration: 'underline',
},
'::after': {
content: '"∞"',
},
},
});
Note that if you want to add some styling of the same property with different pseudo classes, there might be some specificity issues. Say you want to do this:
const styles = sx.create({
button: {
':hover': {
color: 'pink',
},
':active': {
color: 'blue',
},
},
});
These 2 rules will have the same specificity, and the one defined last in the stylesheet will win. It may or may not help to change order in this style, because the class could be created by a different rule. What will help is to raise the specificity of the active class. You can do this:
const styles = sx.create({
button: {
':hover': {
color: 'pink',
},
':active:hover': {
color: 'blue',
},
},
});
The :active:hover now has higher specificity than :hover and the result will be what you expected.
@media and @supports
export function MediaComponent() {
return <a className={styles('text')}>text</a>;
}
const styles = sx.create({
text: {
'fontSize': 16,
'@media print': {
fontSize: 12,
},
'@media screen': {
'fontSize': 14,
':hover': {
color: 'pink',
},
},
},
});
Media queries can also be nested (see: https://www.w3.org/TR/css3-conditional/#processing):
const styles = sx.create({
text: {
'@media print': {
'color': 'red',
'@media (max-width: 12cm)': {
color: 'blue',
},
},
},
});
The same rules apply to @supports at rule (including infinite nesting):
const styles = sx.create({
text: {
'@supports (display: grid)': {
display: 'grid',
},
'@supports not (display: grid)': {
float: 'right',
},
},
});
Keyframes
SX also has support for keyframes, it exports a function that generates an animation name from the input you give it. You can use it like this:
export function AnimatedComponent() {
return <div className={styles('text')}>text</div>;
}
const fadeIn = sx.keyframes({
'0%': { opacity: 0 },
'50%, 55%': { opacity: 0.3 },
'100%': { opacity: 1 },
});
const styles = sx.create({
text: {
// See: https://developer.mozilla.org/en-US/docs/Web/CSS/animation
animationName: fadeIn,
animationDuration: '2s',
animationFillMode: 'none',
animationTimingFunction: 'ease',
},
});
It also supports from and to for simpler animations.
const simple = sx.keyframes({
from: { opacity: 0 },
to: { opacity: 1 },
});
Composability and customizability
SX supports composability with external styles. Have a look at this base component example (uses Flow types):
import sx, { type AllCSSProperties } from '@adeira/sx';
type Props = {
+xstyle?: AllCSSProperties,
};
const styles = sx.create({ default: { fontSize: 16 } });
function MyBaseComponent(props: Props) {
return <div className={sx(styles.default, props.xstyle)} />;
}
Now, let's say we are building a design library and we want to affect the base styles externally. Here is how would our customized (wrapper) component look like:
const styles = sx.create({ spacing: { marginTop: 4 } });
function MyCustomComponent() {
return <MyBaseComponent xstyle={styles.spacing} />;
}
Always prefer this style of customization instead of concatenating or prop-drilling external CSS classes in your components. You can merge as many stylesheets as you want with sx(…) function.
Conditional styling
Sometimes it's necessary to apply styles conditionally. For example, changing button styles when the button should be disabled. Or different styles for active links in your menu. There are 2 way how to do it in SX:
- inline conditions
const styles = sx.create({
button: { margin: 4 },
disabled: { opacity: 0.5 },
});
function MyConditionalComponent({ isDisabled }) {
return (
<>
<button style={styles('button', isDisabled && 'disabled')} />
<button style={styles('button', isDisabled ? 'disabled' : null)} />
</>
);
}
- object conditions
// same styles as above
function MyConditionalComponent({ isDisabled }) {
return <button style={styles({ button: true, disabled: isDisabled })} />;
}
Both styles are equivalent, and it's up to you which one do you choose (they cannot be combined). The resulting style would be margin + conditional opacity.
Precise Flow types
SX knows about almost every property or rule which exis
Related Skills
node-connect
347.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
107.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
347.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
347.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
