Making an Enact Theme
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';
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:
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:
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" />);
...
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.