Skip to content

Type-safe CSS custom properties (CSS variables) for theming purpose

License

Notifications You must be signed in to change notification settings

pveyes/theme-in-css

Repository files navigation

theme-in-css

npm npm-dl



Why?

  • Strictly-typed design token, no more typo. As you might know: CSS is forgiving, but in this case we don't want that. We want it to fail at compile time and this library gives you that
  • Works in CSS and JS. Using CSS custom properties means you can reference your design token in both CSS and JS. Separation of concern!
  • UI Library agnostic. The only purpose of this library is to provide type-safe CSS custom properties in your TS modules, nothing less, nothing more.

Usage

First create your theme object, you can group theme variables by its function (color, spacing, etc). Think of it as design token

import { createTheme } from 'theme-in-css';

export const Theme = createTheme({
  color: {
    lightPrimary: '#fff',
    darkPrimary: '#000',
  },
  spacing: {
    xs: 2,
    s: 4,
    m: 8,
    l: 16,
    xl: 32,
  },
  typography: {
    family: {
      serif: 'Times New Roman',
      sans: 'Calibri',
      mono: 'Menlo',
    },
  }
});

// If you hate typing you can also use a shorter property name
// const t = createTheme({ c: { l1: '#fff', d1: '#000' } });

You can use any any UI libraries/framework that can define style in JS/TS, for example React and Lit.

// React
import React from 'react';
import { Theme } from './theme';

export default function Component() {
  // use css prop via emotion/styled-components
  // of course inline style works as well
  return (
    <div
      css={{
        backgroundColor: Theme.color.darkPrimary,
        color: Theme.color.lightPrimary,
        margin: Theme.spacing.m,
        fontFamily: Theme.typography.family.serif,
      }}
    >
      <h1>It works</h1>
    </div>
  );
}

// Lit
// You need to wrap Theme inside `unsafeCSS`
import { LitElement, html, css, unsafeCSS as cv } from 'lit';
import { Theme } from './theme';

export default class Component extends LitElement {
  static styles = css`
    div {
      background-color: ${cv(Theme.color.darkPrimary)};
      color: ${cv(Theme.color.lightPrimary)};
      margin: ${cv(Theme.spacing.m)};
      font-family: ${cv(Theme.typography.family.serif)};
    }
  `;

  render() {
    return html`
      <div>
        <h1>It works</h1>
      </div>
    `;
  }
}

CSS Integration

If you only create theme and use them in your app, you'll notice that your app now uses CSS variables to reference a value, but it doesn't work properly yet because you need to add the CSS into your stylesheet.

.css.string: string

theme-in-css provides .css.string property to dump all theme values as CSS properties. You can create 2 themes light and dark and output them in different style declaration, like this:

import { Theme, DarkTheme } from './theme';

const html = `
<!doctype head>
<html>
  <head>
    <style>
      :root {
        ${Theme.css.string}
      }

      @media (prefers-color-scheme: dark) {
        ${DarkTheme.css.string}
      }
    </style>
  </head>
  <body>
  </body>
</html>
`;

You can open example to see it in action.

.css.properties: Array<[key: string, value: string]>

You can also use .css.properties if you want to update the CSS custom property manually using JS.

const root = document.documentElement;

theme.css.properties.forEach(([key, value]) => {
  root.style.setProperty(key, value);
});

If you prefer Record<string, string> instead, you can use Object.fromEntries

const obj = Object.fromEntries(theme.css.properties);

License

MIT