Simple Props

Published See discussion on Bluesky

Edit: Updated on January 19th, 2021

A few recent experiments that I've been thinking on have all been around exploring CSS variables or custom properties, from those explorations I just published a new package called simple-props.

This package is heavily inspired by roginfarrer's system-props package (both in function and implementation), but instead of deriving a value from a theme object it derives CSS variables representing the value provided.

Let's see this in action, first with a pretty minimal setup:

1import createSimpleProps from 'simple-props'
2
3let simpleProps = createSimpleProps({
4 props: {
5 color: true,
6 bg: {
7 scale: 'color',
8 property: 'backgroundColor',
9 },
10 },
11})
1import createSimpleProps from 'simple-props'
2
3let simpleProps = createSimpleProps({
4 props: {
5 color: true,
6 bg: {
7 scale: 'color',
8 property: 'backgroundColor',
9 },
10 },
11})

In the above snippet, we've configured simpleProps to handle processing of both a color prop and a bg prop, for example:

1function Box(props) {
2 let styles = simpleProps(props)
3 return <div style={styles} {...props} />
4}
5
6render(
7 <Box color="$primary" bg="$background">
8 Hello World!
9 </Box>,
10)
1function Box(props) {
2 let styles = simpleProps(props)
3 return <div style={styles} {...props} />
4}
5
6render(
7 <Box color="$primary" bg="$background">
8 Hello World!
9 </Box>,
10)

The color and bg props in the above example may generate styles that look like:

1<div
2 style={{
3 color: 'var(--color-primary)',
4 backgroundColor: 'var(--color-background)',
5 }}
6/>
1<div
2 style={{
3 color: 'var(--color-primary)',
4 backgroundColor: 'var(--color-background)',
5 }}
6/>

Note: The simple-props library doesn't manage creating or defining the above CSS variables being used, that part is left up to the implementer

Color and background-color are both fairly basic style props that you could support, but you could support any prop at all through the config.

Responsive Styles

In addition to basic props, you can also support styles that change at different breakpoints too! To do this you need to pass in a breakpoints array to the createSimpleProps function:

1import createSimpleProps from 'simple-props'
2
3let simpleProps = createSimpleProps({
4 props: {
5 color: true,
6 bg: {
7 scale: 'color',
8 property: 'backgroundColor',
9 },
10 },
11})
1import createSimpleProps from 'simple-props'
2
3let simpleProps = createSimpleProps({
4 props: {
5 color: true,
6 bg: {
7 scale: 'color',
8 property: 'backgroundColor',
9 },
10 },
11})

This config will now allow the style props to accept an object mapping a breakpoint to a particular style value:

1let style = simpleProps({
2 color: {
3 _: '$primary',
4 200: '$secondary',
5 },
6})
1let style = simpleProps({
2 color: {
3 _: '$primary',
4 200: '$secondary',
5 },
6})

The _ key is a special reserved key that indicates the value at all breakpoints, from there the rest of the key-value pairs represent a breakpoint and the value at that breakpoint!

Pseudo Selectors

One additional feature that I stole from system-props (and that library stole from Chakra) is the concept of "pseudo-props", or applying some styles based on a pseudo state. This can be configured by providing the pseudoProps config to createSimpleProps:

1import createSimpleProps from 'simple-props'
2
3let simpleProps = createSimpleProps({
4 props: {
5 color: true,
6 bg: {
7 scale: 'color',
8 property: 'backgroundColor',
9 },
10 },
11 pseudoProps: {
12 _hover: '&:hover',
13 _focus: '&:focus',
14 },
15})
1import createSimpleProps from 'simple-props'
2
3let simpleProps = createSimpleProps({
4 props: {
5 color: true,
6 bg: {
7 scale: 'color',
8 property: 'backgroundColor',
9 },
10 },
11 pseudoProps: {
12 _hover: '&:hover',
13 _focus: '&:focus',
14 },
15})

You can now use those pseudo props within the simpleProps function:

1let style = simpleProps({
2 _focus: {
3 color: '$primary',
4 },
5})
1let style = simpleProps({
2 _focus: {
3 color: '$primary',
4 },
5})

Putting it All Together

For an example of a fully configured usage of the simple-props library, feel free to look at this CodeSandbox or this code snippet:

1import createSimpleProps from 'simple-props'
2
3// We call `createSimpleProps` with some config
4// All of this is optional besides the `props` config
5let simpleProps = createSimpleProps({
6 // a mapping of prop name to either a boolean or an object
7 props: {
8 // means that the styles generated will support processing
9 // a color prop that outputs: { color: `var(--color-[value])` }
10 color: true,
11 bg: {
12 // the variable scale to reference
13 scale: 'color',
14 // the CSS property to apply the styles as
15 property: 'backgroundColor',
16 },
17 },
18 // A mapping of pseudo-prop name to pseudo-selector
19 pseudoProps: {
20 _hover: '&:hover',
21 _focus: '&:focus',
22 },
23})
24
25// what we get back is a function that we can call with some props:
26let styles = simpleProps({
27 color: '$primary',
28 bg: 'tomato',
29})
30
31function Component() {
32 return <div style={styles}>Hello World!</div>
33}
1import createSimpleProps from 'simple-props'
2
3// We call `createSimpleProps` with some config
4// All of this is optional besides the `props` config
5let simpleProps = createSimpleProps({
6 // a mapping of prop name to either a boolean or an object
7 props: {
8 // means that the styles generated will support processing
9 // a color prop that outputs: { color: `var(--color-[value])` }
10 color: true,
11 bg: {
12 // the variable scale to reference
13 scale: 'color',
14 // the CSS property to apply the styles as
15 property: 'backgroundColor',
16 },
17 },
18 // A mapping of pseudo-prop name to pseudo-selector
19 pseudoProps: {
20 _hover: '&:hover',
21 _focus: '&:focus',
22 },
23})
24
25// what we get back is a function that we can call with some props:
26let styles = simpleProps({
27 color: '$primary',
28 bg: 'tomato',
29})
30
31function Component() {
32 return <div style={styles}>Hello World!</div>
33}

If you're using this new library and have feedback feel free to open an issue or tweet at me!