StyleX Unleashed: A New Era in Web Styling

2023-12-14 00:01:00
Dive into the future of web development with StyleX—an innovative CSS evolution by Facebook.

Facebook just launched another great open-source project tailored exclusively for CSS. The project aims to revolutionize CSS writing, akin to how React transformed front-end JavaScript coding.

In this blog post, we're going to discover the pros and cons of this library designed for big projects, and find insights to help you decide if it suits your needs.

History of CSS

Let's begin with the history of CSS. Initially, everyone wrote CSS directly in CSS files, imported them into projects, and dealt with cascading issues, overrides, and challenging removal of old CSS. Managing numerous classes and specificity problems often made overwriting CSS difficult.

This is why many CSS and JS libraries emerged, and CSS modules gained popularity. They addressed specificity issues and enabled a more component-based CSS approach, coexisting with JavaScript in the same files.

However, these approaches still faced challenges. CSS modules, in particular, grappled with specificity issues, making it easy to inadvertently modify elements beyond the intended scope of your component.

Historically, CSS and JS faced performance challenges due to the need for client-side calculations to render content. Innovations like Tailwind CSS, with a focus on utility, addressed this by minimizing client-side costs, leveraging standard CSS. However, scaling projects with Tailwind became challenging as overriding existing classes on components proved difficult. Solutions like Tailwind Merge and class variance authority emerged to tackle these issues, but it's definitely not perfect.

What Is StyleX?

StyleX aspires to be the next step in CSS evolution, bringing together the best elements from Tailwind CSS and CSS-in-JS. You can get more information about the StyleX library by visiting their clear and great documentation.

Basic Usage of StyleX

Let's begin with a straightforward example of incorporating StyleX into your project. In the App.js file of a React application, we'll style a heading element using the StyleX library.

App.tsx
import * as stylex from '@stylexjs/stylex'
 
// create your styles
const styles = stylex.create({
  heading: {
    color: 'red'
  }
})
 
const App = () => (
  <>
    <h1 {...stylex.props(styles.heading)}>Heading Element</h1>
  <>
)

This approach allows you to consolidate all your code in a single file, simplifying sharing and debugging. When you run the application, you'll notice that the h1 element now appears in red.

Let's add more complex codes to our example.

App.tsx
import * as stylex from '@stylexjs/stylex'
 
// create your styles
const styles = stylex.create({
	heading: {
		color: 'red',
		padding: '1rem'
	},
	active: {
		color: 'green'
	}
})
 
const App = () => (
	<>
		<h1 {...stylex.props(styles.heading, styles.active)}>Heading Element</h1>
	<>
)

In this instance, the color will be set to green. How does StyleX determine the color? Unlike regular CSS, where specificity is essentially the same, in StyleX, it is always determined by whatever you passed last. So the last thing that you pass to the stylex.props function will be applied to your element and overrides your style. So the padding is still applied to your element but just the color is changed in this case. in simple, it just combining styles together and always make sure to applies whatever the last style that have been passed.

How to Create Components with StyleX

Now let's try to create a Button component with StyleX.

Button.tsx
import type { ComponentProps } from "react";
import * as stylex from "@stylexjs/stylex";
 
type ButtonProps = {
  variant?: "primary" | "secondary";
  size?: `sm` | `md` | "lg";
} & ComponentProps<"button">;
 
// create your styles here ...
const btnStyles = stylex.create({
  // base styles of all buttons
  base: {
    padding: ".5rem 1.5rem",
    cursor: "pointer",
  },
  primary: {
    color: "white",
    background: "black",
  },
  secondary: {
    color: "black",
    background: "white",
  },
  sm: {
    padding: null, // padding: null is same as padding: 0 !
    fontSize: ".2rem",
  },
  md: {
    // padding not passed & means will take from (base) in above
    fontSize: ".6rem",
  },
  lg: {
    padding: "1rem 2rem",
    fontSize: "1rem",
  },
});
 
const Button = (props: ButtonProps) => {
  const { variant = "primary", size = "md", ...restProps } = porps;
  // the styles based on the passed props
  return (
    <button
      {...stylex.props(btnStyles.base, btnStyles[variant], btnStyles[size])}
      {...props}
    />
  );
};
 

Here we are applying different styles to our Button component based on passed props and also we have default styles which is base key in our btnStyles. Again if we're looking at this code, we're thinking that this kind of more complicated. Then just normal plain old TailwindCSS would be good, and you are correct. But as you start to get more and more complexity added into your component it will becomes easier to write this with StyleX.

The real key here even if it's not write it's much easier to read and maintain than tailwindCSS. Because changing complicated TailwindCSS code could be incredibly difficult, especially when you have to deal with merging of different things and conflicts between class names.

Applying Custom Styles to Our Component

Typically, in Tailwind CSS, applying custom styles requires tools like Tailwind Merge to facilitate the inclusion of custom styles. However, with StyleX, incorporating custom styles has become notably simpler and more enjoyable. Let's delve into that.

As we have the previous example:

App.tsx
import type { ComponentProps } from 'react'
import * as stylex from '@stylexjs/stylex'
 
type ButtonProps = {
	variant?: 'primary' | 'secondary',
	size?: `sm` | `md` | 'lg',
	styles?: stylex.StyleXStyles // custom styles
} & ComponentProps<"button">
 
// create your styles here ...
const btnStyles = stylex.create({
	// base styles of all buttons
	base: {
		padding: '.5rem 1.5rem',
		cursor: 'pointer'
	},
	primary: {
		color: 'white',
		background: 'black'
	},
	secondary: {
		color: 'black',
		background: 'white'
	},
	sm: {
		padding: null, // padding: null is same as padding: 0 !
		fontSize: '.2rem'
	},
	md: {
		// padding not passed & means will take from (base) in above
		fontSize: '.6rem'
	},
	lg: {
		padding: '1rem 2rem',
		fontSize: '1rem'
	}
})
 
const Button = (props: ButtonProps) => {
	const { variant = 'primary', size = 'md', styles, ...restProps } = porps
	// the styles based on the passed props
	return <button
				{...stylex.props(
					btnStyles.base,
					btnStyles[variant],
					btnStyles[size],
					styles // applying our custom styles
				)}
				{...props}
			/>
}

And pass just like this:

AnotherComponent.tsx
// in some another component
 
// your custom styles
const customStyles = stylex.create({
	custom: {
		color: 'orange'
	}
})
export default function AnotherComponent() {
	return (
		{/* whatever ... */}
		<Button styles={customStyle.custom}>Custom Styles<Button>
	)
}

That is it!.

Advanced Style Features

Let's take a look at how we can deal with thinks like media-queries and pusedo classes and so on ...

Button.tsx
import type { ComponentProps } from 'react'
import * as stylex from '@stylexjs/stylex'
 
type ButtonProps = {
  variant?: 'primary' | 'secondary',
  size?: `sm` | `md` | 'lg',
  styles?: stylex.StyleXStyles // custom styles
} & ComponentProps<"button">
 
// create your styles here ...
const btnStyles = stylex.create({
  // base styles of all buttons
  // ...
  primary: {
    color: 'white',
    background: {
      default: 'black',
      ':hover': '#00000040', // hover color
      ':focus': '#00000060', // hover when focus
      '@media(prefers-color-scheme: dark)': 'white' // background in dark mode
    }
  },
  outline: {
    default: 'none', // default is none
    ':focus': '1px solid black' // outline when focus
  }
  // ....
})
// ... rest of code
 

It's really cool that all these different features can work together and you can even nest them further for example what you can do also is:

Button.tsx
//....
const btnStyles = stylex.create({
  // base styles of all buttons
  // ...
  primary: {
    color: "white",
    background: {
      default: "black",
      ":hover": "#00000040", // hover color
      ":focus": "#00000060", // hover when focus
      "@media(prefers-color-scheme: dark)": {
        default: "white",
        ":hover": "red", // hover in dark mode
      }, // background in dark mode
    },
    outline: {
      default: "none", // default is none
      ":focus": "1px solid black", // outline when focus
    },
  },
  // ....
});
 
// ... rest of code

Design System Features

Final feature that you may be noticing if you're looking at all this that TailwindCSS does really well that so far StyleX doesn't is it has a really good built-in design system. For example; your padding can only be specified as like 1, 2, 4, 6 and so on ...

tokens.stylex.ts
import * as stylex from "@stylexjs/stylex";
 
const DARK = "@media (prefers-color-scheme: dark)";
 
export const colors = stylex.defineVars({
  primaryColor: {
    default: "#00AAFF", // the defaut color (light mode)
    [DARK]: "#00FFAA", // dark mode color
  },
  primaryDarkColor: {
    default: "#0076B2", // default color
    [DARK]: "#01AC01", // dark mode color
  },
});
 
export const spacing = stylex.defineVars({
  borderRadius: ".25em",
});

Every application should have a strong design system, is really important. StyleX has actually thought of that and the have their own idea of implementing essentially your own design system directly inside of it by creating called variables. These works pretty much exactly the same as creating a normal styles. But it's a style that you can reuse it anywhere you want in your application.

So we have defined some style variables in above code. Let's try to use them in our Button component.

Button.tsx
// Button.tsx
import { spacing, colors } from "./tokens.stylex";
//....
const btnStyles = stylex.create({
  // base styles of all buttons
  // ...
  primary: {
    color: "white",
    borderRadius: spacing.borderRadius, // use it from variable
    primary: {
      color: "white",
      backgroundColor: {
        default: colors.primaryColor,
        ":hover": colors.primaryDarkColor,
        ":focus-within": colors.primaryDarkColor,
      },
    },
  },
  // ....
});
 
// ... rest of code
 

The above code will be the final version of this code!.

Conclusion

In conclusion, the presented blog unravels the promising landscape of StyleX, showcasing its potential to redefine how we approach web styling. As you navigate through the examples and features, consider how this next-generation styling tool might elevate your projects to new heights of simplicity and maintainability.

Have a nice Coding! 💻