Creating Custom Themes

You can start using Reshaped with the theme we provide, but at some point, you might want to apply custom values to the design tokens and align them with your brand. To solve that, Reshaped has a command-line interface for creating new themes.

Adding themes

You need to create a reshaped.config.js file with the theme definitions next to your project's package.json file to add new themes.

const config = {
themes: {
productTheme: {
color: {
foregroundNeutral: { hex: "#1a1a1a", hexDark: "#fff" },
},
},
},
};
module.exports = config;

In this example, we have defined a theme that will only change foregroundNeutral token value. All other values are inherited from the default Reshaped theme.

Theme fragments

In addition to themes, reshaped.config.js allows you to create theme fragments. Theme fragment is a subset of a theme values overrides. By using theme fragments, you can save bundle size as your theme output will contain only the tokens you have changed instead of the whole theme.

const config = {
themeFragments: {
twitter: {
color: {
backgroundPrimary: { hex: "#1da1f2" },
backgroundPrimaryHighlighted: { hex: "#1a90da" },
},
},
},
};
module.exports = config;

This is quite helpful when you're customizing a specific part of the product but don't need to apply this customization to the whole page. For instance, you can create a Twitter theme fragment to implement a TwitterButton component.

Another benefit is that it's easier to combine themes that way. For example, if your product has two themes and you need to render the TwitterButton in both themes, you won't have to create all combinations of themes yourself. Instead, you can create two main themes and a Twitter theme fragment that will inherit the correct token values from the currently used theme.

Adding custom tokens

Reshaped semantic tokens are aiming to provide a limited number of tokens that should cover most of the use cases for building interfaces. However, there will always be edge cases where semantic tokens might feel limiting. For example, imagine you're building a chart component. You need a few custom colors for it but you also want to make sure they support dark mode the same way all Reshaped components do.

To support that, you can add any custom key for all the tokens in the theme definition and they will be compiled to css alonside all other theme token values.

const config = {
themes: {
productTheme: {
color: {
foregroundNeutral: { hex: "#1a1a1a", hexDark: "#fff" },
chartTomato: { hex: "#ff6347", hexDark: "#b8412c" }
},
},
},
};

CLI

Now that we have a config file with theme definitions added, we can use Reshaped CLI to generate these themes. Let's add an NPM script to call the CLI to your package.json:

{
"scripts": {
"build:themes": "reshaped theming --output src/themes"
}
}

Running yarn build:themes or npm run build:themes now will take your added theme definitions from reshaped.config.js file and compile them into the src/themes folder. Script will create a folder for each theme and theme fragment with a variables files inside.

src
└── themes
├── productTheme
│ └── theme.module.css
└── fragments
└── twitter
└── theme.module.css

Using in the application

With the themes built, you can now import them into your code. We can start by picking the productTheme theme we've just built and pass it to the Reshaped provider:

import { Reshaped } from "reshaped";
import "themes/productTheme/theme.css";
const Application = ({ children }) => <Reshaped theme="productTheme">{children}</Reshaped>;

Our product now uses a custom theme and has a new foregroundNeutral token value available. It still uses other tokens from the default Reshaped theme, which means Button component uses a violet color for its background.

Let's create a TwitterButton component with a different button background color with a twitter theme fragment. We can use a ThemeProvider utility to define a theme just for the components rendered inside it.

import { Button, ThemeProvider } from "reshaped";
import "themes/fragments/twitter/theme.css";
const TwitterButton = (buttonProps) => (
<ThemeProvider theme="twitter">
<Button {...buttonProps} />
</ThemeProvider>
);

This concept is called Scoped theming, and you can learn more about it in a separate section.

Typescript support

Even though reshaped.config.js is a Javascript file, you can use comments to enable type checking for the config:

// @ts-check
/** @type {import('reshaped').ReshapedConfig} */
const config = {
themes: {
twitter: {
color: {
backgroundPrimary: { hex: "#1da1f2" },
backgroundPrimaryHighlighted: { hex: "#1a90da" },
},
},
},
};
module.exports = config;
  • // @ts-check enables type checking for the config file.
  • @type comment defines the type for the variable used next. This means that the config format will be type checked according to the type definition coming from the Reshaped package.

Tokens format

Theme is represented with an object that has token types as keys. Each token type contains a dictionary of token objects with their values.

{
color: {
backgroundNeutral: { ... },
...
},
unit: {
radiusSmall: { ... },
...
},
fontFamily: {
body: { ... },
...
},
fontWeight: {
regular: { ... },
...
},
font: {
display1: { ... },
...
},
shadow: {
base: { ... },
...
}
}

In addition to the tokens in theme defintion, we also generate automatically generate dynamic token values. You can find more about them in the Design Tokens section.

Color

Format:

{
color: {
foregroundNeutral: {
hex: '#000',
hexDark: '#fff'
}
}
}

Available token names:

foregroundNeutral
foregroundNeutralFaded
foregroundDisabled
foregroundPrimary
foregroundCritical
foregroundPositive
borderNeutral
borderNeutralFaded
borderDisabled
borderPrimary
borderPrimaryFaded
borderCritical
borderCriticalFaded
borderPositive
borderPositiveFaded
backgroundNeutral
backgroundNeutralFaded
backgroundDisabled
backgroundDisabledFaded
backgroundPrimary
backgroundPrimaryFaded
backgroundCritical
backgroundCriticalFaded
backgroundPositive
backgroundPositiveFaded
backgroundNeutralHighlighted
backgroundCriticalHighlighted
backgroundPrimaryHighlighted
backgroundPage
backgroundPageFaded
backgroundBase
backgroundElevated
black
white
  • All onBackground color tokens are generated automatically.
  • hexDark value is optional in case you're not using dark mode or if the values are same in both modes
  • black and white tokens should preserve their values in both light and dark mode

Unit

Format:

{
unit: {
radiusSmall: {
px: 2
}
}
}

Available token names:

base
radiusSmall
radiusMedium
radiusLarge
  • base unit contols how condensed your UI is
  • x1 - x10 unit tokens will be auto generated based on your base token px value

Font family

Format:

{
fontFamily: {
body: {
family: 'Arial, sans-serif'
}
}
}

Available token names:

body
display
  • Two font family types let you differentiate between regular text and headings but still keep the product styles consistent.
  • If you're using a custom font in this token, don't forget to include your font file into the product

Font weight

Format:

{
fontWeight: {
regular: { weight: 400 }
}
}

Available token names:

regular
medium
bold
black

Font

Format:

{
font: {
dispay3: {
fontSize: { px: 16 },
lineHeight: { px: 20 },
fontWeightToken: 'regular',
fontFamilyToken: 'body',
responsive: {
s: 'title1',
l: 'display3'
}
}
}
}

Available token names:

display1
display2
display3
featured1
featured2
featured3
title1
title2
title3
bodyStrong1
bodyStrong2
bodyMedium1
bodyMedium2
body1
body2
caption1
caption2
  • fontWeightToken refers to the font weight token names
  • fontFamilyToken refers to the font family token names
  • responsive is an optional field to make the typography responsive based on the viewport size
  • responsive values refer to the font token names and are mobile first. For example, in the format code snippet the token uses title1 for s-m screens and display3 for l+ screens.

Shadow

Format:

{
shadow: {
base: [
{
offsetX: 0,
offsetY: 1,
blurRadius: 3,
colorToken: "black",
opacity: 0.08,
},
{
offsetX: 0,
offsetY: 2,
blurRadius: 2,
colorToken: "black",
opacity: 0.06,
},
]
}
}

Available token names:

base
elevated
  • Uses an array of values to apply multiple shadows to the same element
  • colorToken is referring to a color token name