Making an Enact Theme

Dave Freeman
Enact JS
Published in
6 min readNov 14, 2019
image with a pop culture reference for theme writing
I want you to write…a theme. Image from A Christmas Story © 1983 Warner Bros. Entertainment Inc.

Hello, Enact developers! This article will attempt to unravel the mysteries of theming Enact apps.

In the beginning, there was only one theme: Moonstone. It is the theme used for most system apps on LG’s webOS Smart TVs and it is designed with that user interaction model (“lean back”) in mind. It is a very opinionated theme and prescribes LG’s colors in “light” or “dark” mode. It was originally designed to make it difficult to modify the theme’s styles so that it would be easier to have consistency across the webOS TV platform’s apps.

As the framework has matured, we have had to support other screen sizes that do not work as well with the larger sized components that are used on TVs. Additionally, we have always recognized that developers like to customize things! To address this, we have implemented theming support in the framework.

Without going too deeply into everything, a couple of definitions will help to set the stage for how to go about creating your own theme.

theme: a collection of components, behaviors, and at least one skin

skin: colors and variables (LESS files) that control the appearance of components

Starting a new theme from scratch requires considerable planning. What components should the theme have? What behaviors? What should the components look like? How many skins should be supported? What are the skin colors?

Luckily, there is a starter theme to use as a jumping-off point to create new themes! It can be installed as a template for use with@enact/cli:

enact template install @enact/template-theme

Once that is complete, you can use the cli to create a new theme from that template:

enact create -t theme <theme-name> --skin <skin-name>

Use the following command to create a new theme called ‘uranium’ with a skin called ‘proton’:

enact create -t theme uranium --skin proton

Note: --skin is optional. If you don’t specify a skin name, you will end up with the default skin:my-skin.

Using the Theme

In order to make use of the theme, you’ll need to import it into your app. Before you can do that, you’ll need to build the app. The quickest way to test is to use the transpile task inside the theme directory and add it to your npm packages:

cd uranium
npm run transpile
npm link

You can take an existing (or new!) Enact app and install the new theme as a dependency. If you don’t have an app, you can use the default template to create a new app (not inside the theme folder!):

cd ..
enact create uranium-app
cd uranium-app

Then, you can link in the uranium theme:

npm link uranium

Next, replace any existing theme decorator (MoonstoneDecorator, for example) and any components with the new theme’s versions.

Before:

// App.js
...
import Panels from '@enact/moonstone/Panels';
import MoonstoneDecorator from '@enact/moonstone/MoonstoneDecorator';
...
export MoonstoneDecorator(App); // usage may vary
// MainPanel.js
import Button from '@enact/moonstone/Button';
import {Panel, Header} from '@enact/moonstone/Panels';
Screenshot of the Moonstone Hello World app

After:

// App.js
...
import Panels from 'uranium/Panels';
import ThemeDecorator from 'uranium/ThemeDecorator';
...
export ThemeDecorator(App); // usage may vary
// MainPanel.js
import Button from 'uranium/Button';
import {Panel, Header} from 'uranium/Panels';

But, it looks exactly the same as the starter theme. BORING!

Change Skin Colors

If you want a quick theme change, colors are a good place to start.

In styles/colors-proton.less, define two new colors in the Named Colors section:

@uranium-green:     #a7ff00;
@uranium-yellow: #edff50;

We’re going for that bright, toxic green look here.

Now change some existing colors and visual cues:

// Named Colors
@uranium-accent: #638e17;
@uranium-accent-lighter: lighten(@uranium-accent, 10%, relative);
@uranium-accent-darker: darken(@uranium-accent, 20%, relative);
@uranium-highlight: #96b361;@uranium-foreground: @uranium-green;
@uranium-foreground-focus: @uranium-yellow;
@uranium-text-shadow: 1px 2px 1px rgba(64, 64, 64, 0.5), 0 0 8px rgba(183, 163, 6, 0.5);...// ThemeDecorator
@uranium-bg-color: #718011;
@uraniuim-bg-image: linear-gradient(0deg, #283c15 0%, #596f5f 25%, #7b9c73 45%, #bcff03 46%, #b9d547 48%, #bed39e 60%, #a4c4bd 75%, #80d72c 90%, #7fdb26 100%); // whoa, that is long
@uranium-title-text-color: @uranium-yellow;
...// Spotlight and State
@uranium-spotlight-text-color: @uranium-yellow;
...// Checkbox
@uranium-checkbox-text-color: @uranium-yellow;
@uranium-checkbox-bg-color: fade(@uranium-yellow, 22%);
@uranium-checkbox-focus-bg-color: fade(@uranium-yellow, 40%);
...// RadioItem
@uranium-radioitem-icon-selected-color: @uranium-yellow;

Ta-da! After transpiling, you now have a garishly green and yellow skin to use in your Enact app:

Screenshot of the Hello World app using proton skin from uranium theme
Eye catching (on fire)

Add a New Skin

Not everyone is going to love the “proton” skin and having choices is generally a good thing, so Uranium should offer another skin. It will be called “neutron”.

Copy the proton LESS files in the styles directory to colors-neutron.less and variables-neutron.less. Then add two new colors (or change green and yellow, they’re out!):

@uranium-cream:     #d8d4b0;
@uranium-tan: #84764b;

Make some other color/visual cue changes:

// Named Colors
@uranium-accent: #a19f85;
@uranium-highlight: #939469;@uranium-foreground: @uranium-gray;
@uranium-background: @uranium-accent-lighter;
@uranium-foreground-focus: @uranium-cream;
@uranium-text-shadow: 1px 2px 1px rgba(182, 182, 182, 0.5), 0 0 8px rgba(162, 144, 174, 0.49);@uranium-gray: #778083;...// ThemeDecorator
@uranium-bg-color: #uranium-cream;
@uraniuim-bg-image: none;
@uranium-title-text-color: @uranium-cream;
...// Spotlight and State
@uranium-spotlight-text-color: @uranium-cream;
@uranium-spotlight-bg-color: @uranium-tan;
...// Button
@uranium-button-focus-bg-color: @uranium-tan;
...// Checkbox
@uranium-checkbox-text-color: @uranium-text-color;
@uranium-checkbox-bg-color: fade(@uranium-text-color, 22%);
@uranium-checkbox-focus-bg-color: fade(@uranium-text-color, 60%);
...// Panels
@uranium-panels-background-color: fade(white, 30%);
...// RadioItem@uranium-radioitem-focus-text-color: @uranium-checkbox-focus-text-color;
@uranium-radioitem-icon-bg-color: @uranium-checkbox-bg-color;
@uranium-radioitem-icon-selected-color: @uranium-radioitem-text-color;
@uranium-radioitem-icon-selected-focus-color: @uranium-radioitem-focus-text-color;

The neutron LESS files need to be imported in styles/skin.less

.applySkins(@componentRules) when (isruleset(@componentRules)) {
&:global(.proton) {
// Load the proton rules into this scope
@import "./colors-proton.less";
@import "./variables-proton.less";

@componentRules();
}

&:global(.neutron) {
// Load the neutron rules into this scope
@import "./colors-neutron.less";
@import "./variables-neutron.less";

@componentRules();
}
}

Note: Since no changes were made to the “variables” files in either skin, the rules could be contained in styles/variables.less and imported once at the top.

And, finally, add the neutron key/value to defaultConfig.skins in Skinnable/Skinnable.js

const defaultConfig = {
skins: {
// Name of the skin, referred to in the `skin` prop : skin className (in CSS)
// These can be different strings, they just happen to be the same in this case.
'neutron': 'neutron',
'proton': 'proton'
}
};

Behold What You Have Wrought

Re-transpile the theme and check out this startling result:

Screenshot of the Hello World app using proton skin from uranium theme
Exactly the same as before!

To switch skins, supply the skin prop to the App component. Without it, the default skin, “proton” will be used.

// index.js
...
const appElement = (<App skin="neutron" />);
...
Screenshot of the Hello World app using neutron skin from uranium theme
Less likely to cause eye damage

If everything looks good, remove @enact/moonstone as an app dependency and carry on.

If there are some components missing in the starter theme, potentially in the case of updating an existing app, the theme template’s README has a section discussing how we used @enact/ui to build the components for the starter theme. Additionally, we have some design guidelines that we feel are valuable to theme and component designers.

Wrap It Up

This concludes our discussion on how to make a new theme for Enact apps. Now, go forth and make a lot of cool themes! If you want to show them off, send us a link or screenshot on Twitter at @EnactJS. If you have questions, drop by our chat.

--

--

Dave Freeman
Enact JS
Writer for

LG Silicon Valley Lab; Enact JavaScript framework and Voice of Enact