DEV Community

Miki Stanger
Miki Stanger

Posted on

Inventar - A Framework to Keep Your Styles Tidy

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 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:

import makeInventar from 'inventar'

const { jsInventar, cssInventar, inject } = makeInventar({
  warmGray: '#e0ded8',
  blue: '#2233aa',
})
Enter fullscreen mode Exit fullscreen mode

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:
  {
    warmGray: '#e0ded8',
    blue: '#2233aa',
  }
Enter fullscreen mode Exit fullscreen mode
  • cssInventar returns the same configuration, but with CSS variables formatting conventions:
  {
    '--warm-gray': '#e0ded8',
    '--blue': '#2233aa',
  }
Enter fullscreen mode Exit fullscreen mode
  • 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.
  const wrapperElement = document.getElementsByClassName('wrapper')[0]
  inject(wrapperElement)
Enter fullscreen mode Exit fullscreen mode

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:

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(),
})
Enter fullscreen mode Exit fullscreen mode

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:

{
    warmGray: '#e0ded8',
  blue: '#2233aa',
  backgroundColor: '#e0ded8',
  linkColor: '#2233aa',
  linkHoverColor: '#3b4cc3',
}
Enter fullscreen mode Exit fullscreen mode

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...

{
  zIndexHeader: 100,
  zIndexModal: 200,
  zIndexErrorMessage: 300,
}
Enter fullscreen mode Exit fullscreen mode

...and having them all in one place is better than the usual chaos 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. In this example, we'll use 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.

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'])] },
})
Enter fullscreen mode Exit fullscreen mode

We'll end up with:

{
    warmGray: '#e0ded8',
  blue: '#2233aa',
  backgroundColor: '#e0ded8',
  linkColor: '#2233aa',
  zIndexHeader: 100,
  zIndexModal: 200,
  zIndexErrorMessage: 300
}
Enter fullscreen mode Exit fullscreen mode

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 and 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 and proxies (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 :)

Top comments (0)