Theming with Enact
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 customcss
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
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!
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!
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!
And the 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 usingmergeClassNameMaps
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:
Then, we’ll pass the css
object and our custom Button
and Icon
components to ui/IconButton
and we’re done!
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.