Theming with Enact

Ryan Duffy
Enact JS
Published in
3 min readApr 17, 2019

One powerful feature of Enact is the theming and skinning system. There are many parts to this system but we’ll try to hit the highlights on our way to creating a custom IconButton with some help from octicons.

TL;DR: The components within @enact/ui implement basic markup and behaviors and support a custom css prop to style them. By matching up class names with the API defined for the component, we can easily style these base components to create our own custom set with the styling of our choosing!

The Result

Custom Icon, Buttons, and IconButtons with Enact

Building components with @enact/ui

The @enact/ui package is a great place to start when building a custom component. It includes a host of unstyled components with predictable APIs that can be customized with our own styles and decorated with behaviors.

Creating our Icon

The@enact/ui package contains an Icon component that was designed to work with icon fonts but octicons provides SVGs. Fortunately, that doesn’t prevent us from building an Icon component.

The key to building components that integrate into the rest of the theming system is in the API. If we can fulfill the contract defined by the API, everything will work great!

Based on the documentation of ui/IconButton, we know that our Icon component must support the small prop and the children prop. Now we can build a basic component that accepts those two props and outputs the SVG!

If you’re not familiar with kind(), it’s a factory with some convenient sugar to aid in bootstrapping new components. There are some inline comments in the code to describe what’s going on but check out our docs for more info!

Source for src/components/Icon/Icon.js

Add to that some basic CSS to size the icon (for both regular and small) and we’ve built our Icon!

Enact uses CSS modules. It is a great way to decouple styling from markup and behavior and it helps to avoid naming clashes across components.

Later on, we’ll also find that it comes in very handy to streamline theming!

Source for src/components/Icon/Icon.module.css

The result is a component that, given the icon name in the children prop, will output a <span> with the relevant <svg>.

// JSX
<Icon>bell</Icon>
// Output
<span class="...">
<svg>...</svg>
</span>

Adding a Button

Our Button component will be relatively simple for now but could be extended if we were to build out a complete theme. In fact, all we need to do initially is pass our customized styles to ui/Button and we’re ready to move on!

Source for src/components/Button/Button.js

And the CSS!

Source for src/components/Button/Button.module.css

How theming in Enact works

You might notice that we’ve defined a few class names but didn’t reference them in our code above. This is where our theming system comes into play. Any component can choose to accept a css prop which contains a map of logical class names, like small or button, to their rendered class name.

With CSS Modules, the build tooling will usually generate this map such that when you use css.small in your code, the returned value is the rendered class name.

When a css object is passed to a component that supports theming, the passed map will be merged with the component’s map and a new map will be returned with each logical class name mapping to the rendered class name of both maps!

A quick example might help. The first css (css1) shows the class name for the ui/Button component. The second (css2) represents the css that is imported from our Button.module.less file. The final css represents the classes that will be applied to the output:

const css1 = {small: 'ui_Button_small_a4df2'};
const css2 = {small: 'my_Button_small_bde44'};
const css = mergeClassNameMaps(css1, css2);
// css = {small: 'ui_Button_small_a4df2 my_Button_small_bde44'};

This capability is built into @enact/core/kind but can be used anywhere with a little boilerplate using mergeClassNameMaps from @enact/core/util.

Wrapping up the IconButton

With both an Icon and Button built, we can pull them together to create an IconButton. Like Button, there’s very little we need to do to get a working component. This is where you start to see the component system’s power.

Like with Button, we’ll create a CSS file to define the style overrides:

Source for src/components/IconButton/IconButton.module.css

Then, we’ll pass the css object and our custom Button and Icon components to ui/IconButton and we’re done!

Source for src/components/IconButton/IconButton.js

Next Steps

There are a host of other components in @enact/ui that you can style for your applications. Give it a try and see what you can accomplish!

If you’re looking for inspiration, check out the fully themed @enact/moonstone library which uses all of the concepts illustrated above to build a UI library that is running on webOS TVs all over the world right now!

If you run into problems or have questions about the project, you can report issues on GitHub or find the Enact team on Gitter. We’d love to hear from you.

--

--