Inventar - A Framework to Keep Your Styles Tidy
Published: 2020-09-02 20:24:29

#### Keeping your styles tidy is hard.

You end up with duplicates (or worse, almost similar values) of colors and sizes, you have to guess your z-index and hope for the best, you have to duplicate your values if you need them anywhere else, and you're not sure if the same values have the same roles and now any change you have to do there is a terrible experience...

The solutions tend to either be forgotten, to be coupled with a very opinionated styling framework (which means a major refactoring and a lot of settling over other things), or not enforceable.

__CSS variables__ are beginning to be seen in the wild, and can solve some of these problems. On their own, however, they might just become a part of the mess.



#### That's where __Inventar__ comes in.

__[Inventar](https://www.npmjs.com/package/inventar)__ is a tool and a system to manage all of your design variables from a single place - no matter if you're using vanilla CSS, SASS, Styled Components, Tailwind or any other styling language/system(s).

As long as you can use TypeScript, JavaScript or CSS variables, Inventar could work for you.

Let's look at a basic example:

```javascript
import makeInventar from 'inventar'

const { jsInventar, cssInventar, inject } = makeInventar({
  warmGray: '#e0ded8',
  blue: '#2233aa',
})
```

`makeInventar` takes your configuration and returns three variables: `jsInventar`, `cssInventar` and `inject`.

* `jsInventar` returns the configuration in JS formatting conventions. For this configuration, it'll look the same as the input:

  ```javascript
  {
  	warmGray: '#e0ded8',
    blue: '#2233aa',
  }
  ```

* `cssInventar` returns the same configuration, but with CSS variables formatting conventions:

  ```javascript
  {
  	'--warm-gray': '#e0ded8',
    '--blue': '#2233aa',
  }
  ```

* `inject` accepts a DOM element and adds the css variables into its style attribute. You can provide your own custom injection logic as an option, and Inventar comes with an `injectRoot` function that you can import and provide there.

  ```javascript
  const wrapperElement = document.getElementsByClassName('wrapper')[0]
  inject(wrapperElement)
  ```

This means __you can use the same values everywhere__ - CSS will read the injected variables from a wrapping object, and JS styling solution (or dynamic logic based on styles) could use the JS Inventar object as a source.



#### Derivatives allow you to understand what's going on better

After defining two basic colors, I'd like to use them. I could just use them in CSS as they are, but this could make it harder to understand why a variable is used in every place, and could make refactoring hard.

Instead, Inventar allows you to define variables as functions of other variables:

```javascript
import makeInventar from 'inventar'
import zIndex from 'inventar-z-index'
import tinycolor from 'tinycolor2'

const { jsInventar, cssInventar, inject } = makeInventar({
  warmGray: '#e0ded8',
  blue: '#2233aa',
  
  backgroundColor: config => config.warmGray,
  linkColor: config => config.blue,
	linkHoverColor: config => tinyColor(config.linkColor).brighten(30).toString(),
})
```

`backgroundColor`, `linkColor` and `linkHoverColor` have functions as their values. These are __derivatives__ - they are used to derive a value from other values.

In this case, the configuration separates from the color definitions (hex value) to their roles (background, link), which makes things more understandable and much easier to change and refactor.

`linkHoverColor` has additional logic, to generate a brighter version of `linkColor`. You can use any calculations as long as a string or a number is returned.

This is what we're getting:

```javascript
{
	warmGray: '#e0ded8',
  blue: '#2233aa',
  backgroundColor: '#e0ded8',
  linkColor: '#2233aa',
  linkHoverColor: '#3b4cc3',
}
```



#### Transformers can generate, alter and remove variables

With some basics done, we'd like to use Inventar to store our z-index value from all around the object.

We CAN just add them...

```javascript
{
  zIndexHeader: 100,
  zIndexModal: 200,
  zIndexErrorMessage: 300,
}
```

...and having them all in one place is better than the [usual chaos](https://psuter.net/2019/07/07/z-index) of z-index values, but you might still find yourself changing other numbers in order to add a new layer between two existing ones. A transformer can make this a completely painless process.

__Transformers__ are Inventar's plugins. They can alter a variable's name and value, return one or more other variables, and even remove it if needed. In the example above, we provide a single transformer to a single variable. You can provide multiple transformers as well, or apply them [globally](https://www.npmjs.com/package/inventar#global-transformers). In this example, we'll use [inventar-z-index](https://www.npmjs.com/package/inventar-z-index), which takes in an array of layer names, and uses it, and the initial value and variable name, to generate the variables you need instead.

```javascript
import makeInventar from 'inventar'
import zIndex from 'inventar-z-index'
import tinycolor from 'tinycolor2'

const { jsInventar, cssInventar, inject } = makeInventar({
  warmGray: '#e0ded8',
  blue: '#2233aa',
  
  backgroundColor: config => config.warmGray,
  linkColor: config => config.blue,
	linkHoverColor: config => tinyColor(config.linkColor).brighten(10).toString(),
  
  zIndex: { value: 100, transformers: [zIndex(['header', 'modal', 'errorMessage'])] },
})
```

We'll end up with:

```javascript
{
	warmGray: '#e0ded8',
  blue: '#2233aa',
  backgroundColor: '#e0ded8',
  linkColor: '#2233aa',
  zIndexHeader: 100,
  zIndexModal: 200,
  zIndexErrorMessage: 300
}
```



There are many potential uses for transformers - removing values in certain cases, renaming them according to values or conventions, and linting are just a few.

And there you have it - a single place to manage your style variables in a clear, tidy way.

_____

I believe Inventar has many advantages over the existing solutions:



__Your values are now separated from your styling solution__

...and that's a good thing!

Having a single place where all of your colors, sizes, asset paths and other style variables allow you to use them __anywhere__. Vanilla CSS, SASS, JSS, Styled Components, Tailwind CSS, plain inline style (please don't)... As long as you can use CSS variables there, you can use it with Inventar. You can also use them in whatever JS logic you need.

This could also make future refactoring or styling language changes so much easier.



__Your values are now in a single place__

With a single source of truth, it is much easier now to enforce styles ("Hey, I see you used a color string outside of our Inventar"). Maybe you can even write a linter rule for that :)



__It's much easier to name things__

Instead of seeing a single color value scattered all over, you'll see names. The Inventar format allows you to separate names and roles, define which values are derived from other value, so you'll know exactly why is it there and what do you need to change.



__You can easily use, and reuse, style-values-dedicated logic__

Writing [derivatives](https://www.npmjs.com/package/inventar#derivatives) and [transformers](https://www.npmjs.com/package/inventar#transformers) is very easy. Also, if someone publishes their derivative or transformer, you could use them as well, no matter what's your style language/system. Inventar being a tiny library (<2kb gzipped at the time of writing), it might find use as transformers adapter.



There's also one disadvantage:

__This will only work in browsers that support css variables__

[CSS variables](https://caniuse.com/#feat=css-variables) and [proxies](https://caniuse.com/#feat=proxy) (which are used behind the scenes) are widely supported, but you won't be able to use Inventar with Internet Explorer and Opera Mini.

_____



One thing I hope to see is __tighter cooperation between developers and designers__. An Inventar configuration could be written in a pairing session, working on a single file and, in many cases, being simple enough for both participants to understand. Combined with easy enforcement, I believe this will prove to be a very effective way to enforce both the designer's wishes and future changes they might decide on.

In the future, an Inventar configuration can be the single source of truth for designers as well - maybe with plugins to their software or a dedicated UI, that reads the same file and submits pull requests with changes.



Currently, the project is in a very early stage. I'll appreciate any feedback in the comments :)