Table of contents

July 6, 2020 ยท View on GitHub

Table of contents

Configuring your themes

We assume that you have at least read the Changing the theme file part of the documentation.

A theme is built with a Theme object that you can import from tailwindcss-theming/api. Multiple Theme object can be added to a ThemeManager object, which needs to be the only export of your theme file.

// theme.config.js

const { Theme, ThemeManager } = require('tailwindcss-theming/api');

// This is your main theme.
const base = new Theme();

// This is how you export `ThemeManager`.
module.exports = new ThemeManager()
  .setDefaultTheme(base);

Creating a theme

Colors

A Theme contains a list of colors, among other things. Use the addColors method to define your theme's colors. This method takes an object similair to Tailwind's color object syntax as a parameter. A color value can be any valid TinyColor input.

const base = new Theme()
  .addColors({
    background: '#212121',
    'on-background': '#ffffff'
  })

Every color will be added as a CSS variable to your themes. For instance, in the above configuration, assuming you only have a default theme, the following CSS will be generated:

:root {
  --color-background: 33, 33, 33;
  --color-on-background: 255, 255, 255;
}

The reason the colors are stored as an r, g, b string is that they are used in combination with the CSS rgba function to handle opacity. In this case, Tailwind's background color plugin will generate a .bg-background utility with a hardcoded opacity of 1:

/* ... */

.bg-background {
  background-color: rgba(var(--color-background), 1);
}

In order to change that value, opacity variants can be used, and opacity utilities support will be added once colors will be able to be defined as closures.

Variants

In the real world, you won't be able to just add a few colors and make use of them throughout your application. Some elements will need special tweaks, such as opacity changes, hue changes, or whatever make them look good. In order to avoid having to declare lots of colors for this, a variant system is used.

Note: This variant system is not to be confused with Tailwind's variants.

A color can have none, one or multiple variants, which will generate alternative utilities for that color.

const base = new Theme()
  .addColors({
    background: '#212121',
    'on-background': '#ffffff'
  })

  // Adds an opacity variant, which will generate alternative versions
  // of the `on-background` color utilities.
  .addOpacityVariant('hover', .75, 'on-background');

This configuration will generate an additional .text-on-background-hover utility, like so:

:root {
  /* ... */
  --color-on-background: 255, 255, 255;
  --opacity-variant-on-background-hover: 0.75;
}

/* ... */

.text-on-background-hover {
  background-color: rgba(var(--color-on-background), var(--opacity-variant-on-background-hover));
}

In a real application's context, use it in combination with Tailwind's hover: variant.

<span class="text-on-background hover:text-on-background-hover">Some text</span>

Multiple variants can share the same name if they are of different types (color, opacity, or custom) or if they target different colors.

Opacity variants

Opacity variants add an opacity variable to the themes' rules. You can declare an opacity variant with the addOpacityVariant method, which parameters are, in order, the name of the variant, its value (between 0 and 1), and optionally, either a color name or an array of color names. If the last parameter is empty, the variant will be applied to every color.

const base = new Theme()
  // ...
  .addOpacityVariant('hover', .75, 'on-background')
  .addOpacityVariant('hover', .5, ['on-surface', 'on-brand'])
;

Color variants

Color variants completely replace a color by the one you specify. You can declare an opacity variant with the addColorVariant method, which parameters are, in order, the name of the variant, its value (any TinyColor input), and optionally, either a color name or an array of color names. If the last parameter is empty, the variant will be applied to every color.

const base = new Theme()
  // ...
  .addColorVariant('hover', '#f9f9f9', 'on-background')
;

Custom variants

Custom variants completely replace a color by the one you specify. The difference with color variants is that the parameter is a closure that accepts the color the variants will be applied to, and that return a different color. The color received is a TinyColor object, so you can manipulate the input however you want.

const base = new Theme()
  // ...
  .addCustomVariant('hover', color => color.lighten(), 'on-background')
;

Extending Tailwind's configuration with CSS variables

A very handy feature is to directly extend any key of Tailwind's configuration. For instance, imagine that you want to change your font depending on your theme. You can do that using the setVariable method.

The following configuration will extend Tailwind's fontFamily plugin and replace its sans value to ['Roboto'].

// theme.config.js

const base = new Theme()
  .setVariable('sans', ['Inter', 'Segoe UI', 'Roboto'], 'fontFamily', 'font');

This is effectively the same as doing the following in your Tailwind configuration:

// tailwind.config.js

module.exports = {
  theme: {
    extend: {
      fontFamily: {                 // `fontFamily` is the third argument
        sans: 'var(--font-sans)'    // `sans` is the first argument
      }
    }  
  }
}

Except only doing this won't generate your CSS variable, --font-sans. This is why setVariable is useful. It will generate the following CSS:

:root {
  --font-sans: Inter, "Segoe UI", Roboto
}

/* ... */
.font-sans {
  font-family: var(--font-sans)
}

setVariable's parameters are, in order, the key of a plugin, its value, the plugin's name, and a prefix to be added to the CSS variable name. The following will generate a variable named --sans instead of --font-sans:

- setVariable('sans', ['Inter', 'Segoe UI', 'Roboto'], 'fontFamily', 'font')
+ setVariable('sans', ['Inter', 'Segoe UI', 'Roboto'], 'fontFamily')

Name and targetability

A theme can be named using the setName method. The name is used to determine the selector that will make your theme targetable. The selector is determined by the strategy.

By default, the strategy is set to data-theme-attribute, so a theme with the name dark will have a [data-theme='light'] selector, which mean you will have to add an attribute to your HTML to activate the theme.

<!-- ... -->
<body data-theme="light">
    <!-- ... -->
</body>

This is only possible if you specified that a theme should be targetable, using the targetable method on the Theme object.

// theme.config.js

const dark = new Theme()
  .setName('dark')
  .targetable()
  .addColors({ /* .. */ });

Exporting your themes

Once your themes are created, you need to register them in a ThemeManager, which needs to be the only export of your theme file. Multiple methods are available to specify the scope of each of your themes.

Default theme

The default theme will be the one that your users will see, by default, if they have no prefers-color-scheme preference set. The vast majority of users won't have one, so consider it the actual appearence of your application.

You can define your default theme using the setDefaultTheme method. It takes a Theme object as its only parameter.

// theme.config.js

const { Theme, ThemeManager } = require('tailwindcss-theming/api');

// This is your main theme.
const base = new Theme();

// This is how you export `ThemeManager`.
module.exports = new ThemeManager()
  .setDefaultTheme(base);

This configuration will generate your theme in a :root selector.

Default dark theme

tailwindcss-theming supports prefers-color-scheme as a first-class citizen. You can define a theme that will be applied by default to users that have defined their scheme preference to dark using the setDefaultDarkTheme method.

// theme.config.js

const { Theme, ThemeManager } = require('tailwindcss-theming/api');

// This is your main theme.
const base = new Theme();

// This is your dark theme.
const dark = new Theme();

// This is how you export `ThemeManager`.
module.exports = new ThemeManager()
  .setDefaultTheme(base)
  .setDefaultDarkTheme(dark)
;

This configuration will generate your base theme in a :root selector, and your dark theme in a :root selector under a prefers-color-scheme media query.

@media (prefers-color-scheme: dark) {
  :root {
    /* ... */
  }
}

Default light theme

Although this is less frequent, the same thing is possible for the light preference. If you have a site that is dark-schemed by default, you can serve a light version of it by using setDefaultLightTheme. It will do the same as before, replacing dark by light.

// theme.config.js

const { Theme, ThemeManager } = require('tailwindcss-theming/api');

// This is your main theme, dark-schemed.
const base = new Theme();

// This is your light theme.
const light = new Theme();

// This is how you export `ThemeManager`.
module.exports = new ThemeManager()
  .setDefaultTheme(base)
  .setDefaultLightTheme(light)
;

Additional themes

Sometimes, you need to handle multiple themes, but they are not the default of some scheme. Use the addTheme method to add such themes, and don't forget to name them and to make them targetable.

// theme.config.js

const { Theme, ThemeManager } = require('tailwindcss-theming/api');

// This is your main theme, dark-schemed.
const base = new Theme();

// This is a solar theme.
const solar = new Theme()
  .setName('solar')
  .targetable();
  
// This is a lunar theme.
const lunar = new Theme()
  .setName('lunar')
  .targetable();

// This is how you export `ThemeManager`.
module.exports = new ThemeManager()
  .setDefaultTheme(base)
  .addTheme(solar)
  .addTheme(lunar)
;

These themes will now be targetable using your defined strategy.

API reference

Note: Some methods are not documented here because they are not quite useful. Any changes made to them will still be notified, and breaking changes will only occur in new major versions.

Theme

MethodDescriptionParameters
setNameSets the name of the theme.name: string
targetableSets the theme as targetable.
setColorSchemeSets the theme's scheme.scheme: Scheme
lightSets the theme's scheme to light.
darkSets the theme's scheme to dark.
addColorsAdds colors to the theme.colors: TwoLevelColorObject
colorAdd a color to the theme.name: string, color: ColorInput
addVariantsAdd variants to the theme.variants: VariantsObject
addColorVariantAdd a color variant to the theme.`name: string, value: ColorInput: colors?: string
addOpacityVariantAdd an opacity variant to the theme.`name: string, value: number: colors?: string
addCustomVariantAdd a custom variant to the theme.`name: string, value: VariantTransformer: colors?: string
addVariantAdd a variant to the theme.`variant: Variant, colors?: string
setVariablesAdd a variable to the theme.`name: string, value: VariableInput

ThemeManager

MethodDescriptionParameters
setDefaultThemeSets the default theme of the application.theme: Theme
setDefaultLightThemeSets the default theme of the application for users prefering the light scheme.theme: Theme
setDefaultDarkThemeSets the default theme of the application for users prefering the dark scheme.theme: Theme
addThemeAdds a theme to the application.theme: Theme
addLightThemeAdds a theme to the application and change its scheme to light.theme: Theme
addDarkThemeAdds a theme to the application and change its scheme to dark.theme: Theme
setPrefixOverrides the configuration's prefix.prefix: string
setStrategyOverrides the configuration's strategy.strategy: Strategy
asDataThemeAttributesOverrides the strategy to data-theme-attribute.
asDataAttributesOverrides the strategy to data-attribute.
asClassesOverrides the strategy to class.
asPrefixedAttributesOverrides the strategy to prefixed-attribute.prefix: string
asPrefixedClassesOverrides the strategy to prefixed-class.prefix: string