Skip to main content

Styling components

··

style prop #

<div style={{ color: "red" }} />

style prop it the most basic CSS-in-JS solution. But it doesn’t support:

styled function #

const Box = styled.div`
  color: red;
`;
<Box />;

or

const Box = styled('div', {
 color: "red"
})
<Box />

There are a lot of implementation. Some examples:

Styles-as-props #

aka: Style Props. Styles are exposed as props:

<Box display="flex" position="absolute" top={0} />

There are a lot of implementation. Some examples:

Often combined with polymorphic as (is, component) prop and data prop.

<Box as="span" data={{ testid: "customIdentifier" }} />

Responsive styles-as-props #

Variation of styles-as-props to support size media-queries:

<Box padding={{ mobile: "small", tablet: "medium" }} />

There are a lot of implementation. Some examples:

sx prop #

aka: css-prop. Can be considered as a variation of style prop:

<Box sx={{ border: 1 }} />

But it resolves issues (depending on implementation) with:

  • mediaqueries
  • pseudo-classes
  • pseudo-elements
  • theming
<Box
  sx={[
    // theme prop
	color: 'primary.main',
	// or
    (theme) => ({ color: theme.palette.primary.main }),
    // responsive prop
    width: { xs: 100, sm: 200 },
    // or
    width: [100, 200, 300],
    // pseudo-classes
    '&:hover': { color: 'red' }
  ]}
/>

There are a lot of implementation. Some examples:

Variants #

aka: recipes. I call it “variants” for the lack of better name. Maybe “design system driven props”? It can look, for example, like this:

const Button = styled("button", {
  defaultVariants: {
    color: "accent",
    size: "medium",
  },
  variants: {
    color: {
      neutral: { background: "whitesmoke" },
      brand: { background: "blueviolet" },
      accent: { background: "slateblue" },
    },
    size: {
      small: { padding: 12 },
      medium: { padding: 16 },
      large: { padding: 24 },
    },
  },
});

There are a lot of implementation. Some examples:

Variants are often go hand in hand with theme (or design tokens):

export const { styled, css } = createStitches({
  theme: {
    colors: {
      gray500: "hsl(206,10%,76%)",
      blue500: "hsl(206,100%,50%)",
      purple500: "hsl(252,78%,60%)",
      green500: "hsl(148,60%,60%)",
      red500: "hsl(352,100%,62%)",
    },
    space: {
      1: "5px",
      2: "10px",
      3: "15px",
    },
  },
});

You can define aliases for tokens (to use semantic names):

colors: {
  background: "$gray500";
}

You can define a different theme, for example to support dark mode.

Tokens can be “smartly” mapped, e.g. system knows where (in which section of the theme) to lookup token depending on the property:

const Button = styled("button", {
  color: "$purple500",
});

You can define aliases for styles-as-props:

export const { styled, css } = createStitches({
  utils: {
    // Abbreviated margin properties
    m: (value) => ({
      margin: value,
    }),
    mx: (value) => ({
      marginLeft: value,
      marginRight: value,
    }),
    linearGradient: (value) => ({
      backgroundImage: `linear-gradient(${value})`,
    }),
  },
});

See:

Bonus: comparison to Tailwind #

background #

styles-as-props:

// with theme
<Box backGround={(theme) => theme.colors.slate100} />
// with smart mapping
<Box backGround="$slate100" />
// with alias and smart mapping
<Box bg="$slate100" />
// with dark mode mediaqueries
<Box bg={{ light: "$slate100", dark: "$slate600" }} />

And dark mode can be implemented at theme level.

sx prop:

<Box sx={(theme) => ({ backGround: theme.colors.slate100 })} />

Tailwind:

<Box className="bg-slate-100 dark:bg-slate-800" />

padding #

styles-as-props:

<Box p={{ sm: 8, md: 0 }} />

sx prop:

<Box sx={{ padding: { sm: 8, md: 0 } }} />

Tailwind:

<Box className="p-8 md:p-0" />

flex #

styles-as-props:

<Box flex={{ md: true }} />

sx prop:

<Box sx={{ flex: { md: true } }} />

Tailwind:

<Box className="md:flex" />

Summary #

To me they look the same, except that Tailwind doesn’t require runtime and works with server components Β―\_(ツ)_/Β―. Only variants look different.

Related:

CSSModules-vibe #

const styles = StyleSheet.create({ root: { flex: 1, opacity: 0 } });
const Component = () => <View style={styles.root} />;
export default () => (
  <div>
    <p>only this paragraph will get the style :)</p>
    <style jsx>{`
      p {
        color: red;
      }
    `}</style>
  </div>
);
const useStyles = createUseStyles({
  myButton: {
    color: "green",
  },
  myLabel: {
    fontStyle: "italic",
  },
});

const Button = () => {
  const classes = useStyles();
  return (
    <button className={classes.myButton}>
      <span className={classes.myLabel}></span>
    </button>
  );
};

Read more: Component libraries trends, Distributing CSS in npm package