Skip to main content

Element Styles

Element Styles — Vivid Module

Element styles is a set of utilities to apply styles to your React components and applications. It's specifially designed to easily apply style overrides to your apps or specific parts of your app, becoming a powerful tool for component libraries to be used for different brands.

Motivation

At rexlabs we have multiple products, all with completely different brands. While we love React and CSS-in-JS, this adds an extra challenge around styling to our component library. But not only for the usage of components in our apps, but also when using components as part of other components.

The goal was a system that allows us to target specific elements of our components at any application level with style overrides, while still having the usual abilities of theming solutions i.e. through tokens, helping us to build consistent design systems.

As usual, huge shout out to Lochlan Bunn here. Even though he's not working at rexlabs anymore and this library has been completely overhauled to use the new React Context API as well as adding extra functionality, the core concept he originally created is still the same.

Another goal of element styles is to be independent from the actual styling solution. This makes it possible to use it with whatever solution you prefer, be it CSS modules, styled components, emotion, some other CSS-in-JS solution, or even good ol' plain CSS 😊

Getting Started

Install dependencies

# Add element styles
yarn add @rexlabs/element-styles

# Add your css library of choice, e.g. styled components
yarn add styled-components

Usage Examples

import { css } from 'styled-components';
import { StylesProvider, styled } from '@rexlabs/element-styles';

function Example({ styles: s }) {
return (
<div {...s('container')}>
<span {...s('text')}>Example</span>
</div>
);
}

const defaultStyles = {
container: css`
padding: 20px;
background: orange;
`,
text: css`
font-weight: bold;
color: red;
`
};

const StyledExample = styled(defaultStyles)(Example);

function App() {
return (
<StylesProvider>
<StyledExample />
</StylesProvider>
);
}

Use style overrides

const components = {
Example: {
container: css`
background: lightblue;
`,
text: css`
color: blue;
`
}
};

function App() {
return (
<StylesProvider>
<StyledExample />
<StylesProvider components={components}>
<StyledExample />
</StylesProvider>
</StylesProvider>
);
}

Use "tokens"

function Example({ styles: s }) {
return (
<div {...s('container')}>
<span {...s('text')}>Example</span>
</div>
);
}

const defaultStyles = {
container: ({ token }) => css`
padding: ${token('paddings.xxs')};
`,
text: ({ token }) => css`
color: ${token('colors.primary')};
`
};

const StyledExample = styled(defaultStyles)(Example);

const tokens = {
paddings: { xxs: '5px' },
colors: { primary: 'orange' }
};

function App() {
return (
<StylesProvider tokens={tokens}>
<StyledExample />
</StylesProvider>
);
}

API Docs

StylesProvider

Context Provider, allowing you to pass in style overrides and tokens for the component sub tree. Overrides and tokens will be merged, when using nested StylesProviders.

modes

Object describing which modes are currently active for the subtree

const modes = { compact: true };

function Component() {
return (
<StylesProvider modes={modes}>
<SubComponent />
</StylesProvider>
);
}

tokens

Object that describes the token overrides for the subtree

const defaultStyles = StyleSheet({
container: {
marginRight: ({ token }) => token('button.margin.right', 20)
}
});

const Button = styled(defaultStyles)(({ styles: s }) => {
return <button {...s('container')}>Hello there</button>;
});

const tokens = {
button: {
margin: {
right: 100
}
}
};

function App() {
return (
<StylesProvider tokens={tokens}>
{/* <Button /> now has a margin-right of 100 instead of the default 20 */}
<Button />
</StylesProvider>
);
}

components

Object with style overrides. As key you can use the component names and/or multiple component names similar to css selectors, e.g.

const components = {
Foo: {
// overrides specific to the `Foo` component
}
};

The shape of each override value is the same as the shape styled takes to define your styles for your components in the first place, see below.

styled

React HOC that helps you to style your components. It takes an object defining the styles and returns a component that get's enhanced with a styles prop, a helpful util method to apply the styles to your components elements.

The object shape is as follows:

const styles = {
// key = identifier for the styles util method
// value = classname or function or array of these
container: 'container-class',
inner: [
'inner-class',
({ token }) => `dynamic-class-${token('colors.primary')}`
]
};

The styles util injected by the HOC makes applying the styles easy. You can simply spread its return value onto your element to apply the appropriate props, e.g.:

const defaultStyles = {
container: 'container-class',
containerBlue: 'container-blue-class',
inner: 'inner-class'
};

function Example({ styles: s, isBlue, textColor }) {
return (
<div {...s('container', { containerBlue: isBlue })}>
<span {...s.with('inner')({ color: textColor })}>Example</span>
</div>
);
}

styled(defaultStyles)(Example);

styles(...keys)

The keys can be either a string, relating to one of the identifiers in your styles object passed into styled, or an object. If you pass in an object, it will join all keys with a truthy value, just like classnames does. This can be really useful for conditional styles.

styles.with(...keys)(...inlineStyles)

styles.with allows you to easily apply inline styles easily. This can be helpful if the style value comes from your component's props (but not in a conditional way). E.g.

function Example({ styles, textColor }) {
return <span {...s.with('inner')({ color: textColor })} />;
}

Besides applying inline styles, styles.with also allows you to pass in extra class names to the styles util. This can be useful e.g. to pass class names from your components props through to a specific element.

function Example({ styles, className }) {
return <span {...s.with('inner')({ className })} />;
}

Debugging

If not in production mode (determined from process.env.NODE_ENV), the styles util will create add a few more helpful attributes to show the components path and the applied selectors.

UPDATES

25/05/2020

Breaking Changes

  • Completely removes path context - PathProvider is no longer exported and no longer rendered for each styled component

Development

Install dependencies

$ yarn

Available Commands

$ yarn test               # runs all units tests
$ yarn test:watch # runs unit tests when files change
$ yarn build # bundles the package for production

Roadmap

  • Create storybook plugin for theme support
  • Re-iterate over token functionality
  • Think about DX improvement to show which tokens have been applied
  • Create chrome extension for better debugging experience
  • Check what we'd need to do for RN support
  • Add perf tests, maybe somehow against old methods

Copyright (c) 2019 Rex Software All Rights Reserved.