Best practices
Learning to think in CSS-in-JS
In the old days -- say, two years ago -- components used to respond to state changes by imperatively adding classes. Is your button selected? Push .selected
onto its class list. Is it an outline button? Add .outline
.
With Styletron, classes are no longer a reference point. Instead, you have to think in styles. Is your button selected? Change the background color and text color directly. Is it an outline button? Here are your override styles.
You're no longer in charge of classes. Only inline styles.
A major artifact of this new way of thinking, especially with Styletron, is that you can no longer use descendant selectors. You used to be able to put a class on a wrapper and have descendants respond. But now, since you aren't in charge of classes, you have to apply styles directly to your descendants.
This forces you to build sub-components, rather than a multi-layered component with multiple levels of styles. See the samples section for an example, as well as a counter-example.
Using the global meta
The global meta has some nice features:
- It is available to every component
- The user can override any of its values
This makes it a nice place to put global constants. Use these in your components, rather than hard-coding them in your style functions:
// here is the global meta that the library author might provide:
//
const libraryMeta = {
// color codes are an excellent use of the global meta, especially
// because of the middleware that we supply
colors: {
brandPrimary: '#003399',
brandAlt: '#006645',
warning: '#ff6633'
},
// see the middleware docs for how to create a font size map
fontSizeMap: {
large: '30px'
}
// you can also put reusable constants for timing, padding, etc
constants: {
transitionTime: '150ms' // for hover transitions
},
// or mixins
mixins: {
dropShadows: {
light: {
boxShadow: '0 0 4px rgba(0,0,0,0.2)',
border: '1px solid #aaa'
},
heavy: {
boxShadow: '0 0 8px rgba(0,0,0,0.4)',
border: '1px solid #666'
}
},
textShadows: { /* ... */ }
}
};
Anything you want to let the user override should be put either in the global meta or in the themes for your components.
Using the component meta
Let's repeat that: anything you want to let the user override should be put either in the global meta or in the component theme. Consider a simple button component:
const staticButtonStyles = {
background: '#aaa',
color: '#444'
};
function dynamicStyle({props, componentTheme}) {
return Object.assign({}, componentTheme,
props.disabled && {color: '#999'} // this is bad
);
}
By hard-coding the disabled color in your styling function, you prevent your users from overriding that value. Better to put it in the component meta:
const staticButtonStyles = {
background: '#aaa',
color: '#444',
meta: {
// users can now override the color in their theme
disabledColor: '#999'
}
};
function dynamicStyle({props, componentTheme}) {
return Object.assign({}, componentTheme,
props.disabled && {color: componentTheme.meta.disabledColor}
);
}
// a sample user theme:
const userTheme = {
Button: {
meta: {
disabledColor: '#aaa'
}
}
};
There are no rules about how you structure your meta. Carrying this example to the next level, you might define a disabled
object, rather than just a color key. This allows users to extend the styles applied when the button is disabled, beyond simply changing the text color:
const defaultButtonStyles = {
background: '#aaa',
color: '#444',
meta: {
// users can now add any style properties to the disabled
// state in their theme (border color, background, etc)
disabledState: {
color: '#999'
}
}
};
function dynamicStyle({props, componentTheme}) {
return Object.assign({}, componentTheme,
props.disabled && componentTheme.meta.disabledState // merge in the full disabledState object
);
}
// a sample user theme:
const userTheme = {
Button: {
meta: {
disabledState: {
color: '#aaa',
background: '#777' // user can add attributes you didn't plan for
}
}
}
};
We have a sample button component that shows how you might use a richly-designed component meta that gives your component user a high degree of flexibility.
Never reference static styles directly
Your dynamicStyle
function is always handed a fresh copy of the component theme. You should never write to this object. Instead, augment a copy of it with any props-dependent styles.
Also, never reference the default styles object directly. Always build off of the componentTheme
value, which takes into account the user's overrides.
// once this object is sent off to the <Styled> component for use
// as its staticStyle prop, you should never reference this object
// directly again. in fact, best not to name it, so you're not tempted!
export default {
background: '#aaa',
color: '#444',
meta: {
disabledColor: '#999'
}
};
// always build your dynamic style using "componentTheme" as the base
export function dynamicStyle({props, componentTheme}) {
return Object.assign(
// start with an empty object
{},
// add in the base styles (notice we're not referencing the default
// export object, above)
componentTheme,
// finally, merge any prop-dependent changes
props.disabled && {color: '#eee'}
);
}