Electrum provides support for styles and themes at the React component level. The current architecture is not yet fully finalized, but it is time for me to share some ideas.
What’s in a theme
An Electrum theme is basically a collection of various sets of properties:
- Palette →
theme.palette.accent1
. - Shapes →
theme.shapes.defaultBorderRadius
. - Styles →
theme.styles.resetLayout
. - Transitions →
theme.transitions.slid
. - Typo →
theme.typo.font
.
which are derived from more basic sets of properties:
- Colors →
theme.colors.purple600
. - Spacing →
theme.spacing.iconSize
. - Timing →
theme.timing.timeBase
.
The theme is used by the styling engine to derive the real styles
object which will be handed over to Radium
in order to produce a set of inline styles on the React element.
Customizing components
An Electrum component is implemented as a pure React component
and as a style function. The style function of a component
<Foo>
looks like this:
export default function (theme) {
return {
base: {
includes: ['resetLayout', 'defaultTypo'],
width: '100%',
backgroundColor: theme.palette.background,
color: theme.palette.text,
},
important: {
color: theme.palette.textAccent1
}
};
}
Note: the
includes: [...]
property tells the theme styling engine to inject partial styles taken fromtheme.styles
.
The style function takes a theme as its input and produces a style
collection which defines a base
style and possible multiple sub-styles
(such as important
in the example above).
Component <Foo>
can be used as is, by just using <Foo .../>
in any
other component’s render()
method. This will render the component with
its base style (base
).
The component can be customized by adding a kind='important'
property,
which will append to the base tyle the sub-style named important
, thus
resulting in the color
to be theme.palette.textAccent1
rather than
the default theme.palette.text
.
Global theme styles (found in the theme.styles
object) can be added
to component <Foo>
by just listing them styles={[foo,bar]}
.
Furthermore, local styles may be added using styles={{fontWeight: 200}}
or mixed with global theme styles by listing the local styles in the
array, such as styles={['foo', {fontWeight: 200}]}
.
<Foo id='123' theme={theme} value='hello'
kind='important'
styles={
['foo', {fontWeight: 200}]
} />
Too customizable?
I am not too happy with all this flexibility, though. Letting the
consumer of <Foo>
inject any style into the component could lead
to broken designs or unexpected behaviours. The same conclusion was
reached by the people at Polymer:
One solution the Shadow DOM spec authors provided to address the theming problem are the
/deep/
and::shadow
combinators, which allow writing rules that pierce through the Shadow DOM encapsulation boundary. Although Polymer 0.5 promoted this mechanism for theming, it was ultimately unsatisfying for several reasons:
- Using
/deep/
and::shadow
for theming leaks details of an otherwise encapsulated element to the user […][…]
For the reasons above, the Polymer team is currently exploring other options for theming that address the shortcomings above and provide a possible path to obsolescence of
/deep/
and::shadow
altogether
I am currently toying with the following idea: Let the user of a component specify a configuration property which could be fed to the component’s style function. This would ensure that only customizable areas would indeed get customized.
And another track to explore is parent-children style injection, e.g.
to manage complex layouts without having to patch the children in the
render()
function.