SkillAgentSearch skills...

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/Sx
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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):

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:

  1. 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)} />
    </>
  );
}
  1. 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

View on GitHub
GitHub Stars11
CategoryDevelopment
Updated1y ago
Forks0

Languages

JavaScript

Security Score

75/100

Audited on Nov 1, 2024

No findings