Styling components
Table of Contents
style
prop #
<div style={{ color: "red" }} />
style
prop it the most basic CSS-in-JS solution. But it doesn’t support:
- mediaqueries
- pseudo-classes
- pseudo-elements
- global styles
@font-face
@keyframes
* { box-sizing: border-box; }
- vendor-prefixes
- theming
- but you can still do it with CSS variables
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:
- stitches
- cva
- macaron
- aestheticsuite
- panda recipes
- vanilla-extract: recipes + sprinkles + dessert-box
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:
- stitches tokens
- styled-system theme specification
- theme-ui theme specification
- system-ui theme specification
- udt
- tailwind theme
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:
- clb
- windstitch
- tailwind-variants
- classname-variants
- @stitches/tailwind
- Don’t use Tailwind for your Design System
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