Theming

    Prerequisites

    You can the demo project and run to get started.

    The @dojo/cli command line tool should be installed globally. Refer to the Dojo local installation article for more information.

    You also need to be familiar with TypeScript as Dojo uses it extensively.

    Theming our widgets

    In Dojo, we differentiate between two types of styles:

    • Structural styles: these are the minimum necessary for a widget to function
    • Visual styles: these are themed
      The current CSS in the example app provides the structural styles, we will now review how to create and manage themes.

    In order to theme our widgets, we must ensure that they each apply the ThemedMixin and change the class name of the widget’s top-level node to root. The ThemedMixin provides a this.theme function that returns (or that will return) the correct class for the theme provided to the widget. The top-level class name is changed to root in order to provide a predictable way to target the outer-node of a widget, this is a pattern used throughout widgets provided by @dojo/widgets.

    Replace the contents of Banner.ts with the following

    Reminder
    If you cannot see the application, remember to run dojo build -m dev -w -s to build the application and start the development server.

    This Banner widget will now have access to the classes in banner.m.css and can receive a theme. We use the root class to ensure that the theme we create can easily target the correct node.

    Notice that this file and other css files now have a .m.css file extension. This is to indicate to the that this file is a css-module and should be processed as such.

    CSS Modules
    CSS Modules is a technique to use scoped CSS classnames by default.

    Create a new style sheet for the Banner widget named banner.m.css.

    We will create an empty root class for now as our base theme does not require any styles to be added to the Banner widget.

    1. File: src/styles/banner.m.css
      .root {
    2. }

    Now, let's look at changing the WorkerForm widget

    Fixed Classes
    Fixed classes apply styles that cannot be overridden by a theme, using a suffix is a convention that helps easily differentiate the intention of these classes.

    1. File: src/widgets/WorkerForms.ts
      return v('form', {
    2. classes: [ this.theme(css.root), css.rootFixed ],
    3. onsubmit: this._onSubmit
    4. }, [

    Replace all of the selectors containing .workerForm with the following rules in workerForm.m.css.

    1. File: src/styles/workerForm.m.css
      .root {
    2. margin-bottom: 40px;
    3. }
    4. .rootFixed {
    5. text-align: center;
    6. }
    7. .rootFixed fieldset,
    8. .rootFixed label {
    9. display: inline-block;
    10. text-align: left;
    11. }
    12. .rootFixed label {
    13. margin-right: 10px;
    14. }

    If you open the application in a browser its appearance and behavior should be unchanged.

    Now update worker.m.css and workerContainer.m.css to use .root and .rootFixed

    1. File: src/styles/workerContainer.m.css
      .rootFixed {
    2. display: flex;
    3. flex-flow: row wrap;
    4. justify-content: center;
    5. align-items: stretch;
    6. margin: 0 auto;
    7. width: 100%;
    8. }
    9. .root {
    10. }

    …and then update the associated widgets to use the new selectors.

    1. // WorkerContainer.ts
    2. // ...
    3. render() {
    4. // ...
    5. return v('div', {
    6. classes: [ this.theme(css.root), css.rootFixed ]
    7. }, workers);
    8. // ...
    9. }
    10. // Worker.ts
    11. // ...
    12. render() {
    13. // ...
    14. return v('div', {
    15. classes: [
    16. ...this.theme([ css.root, this._isFlipped ? css.reverse : null ]),
    17. css.rootFixed
    18. ]
    19. }, [
    20. // ...
    21. }

    Next, we will start to create a theme.

    Creating a Theme

    Create a theme directory

    Dojo provides a CLI command for creating a skeleton theme from existing Dojo widgets. To do this the command prompts the user to enter the name of the package that contains Dojo widgets and allows you to select specific widgets to include in the output.

    At the command line run dojo create theme —name dojo

    When prompted for the package to theme enter @dojo/widgets and then type N to indicate their are no more packages. You should now be presented with a list of widgets from @dojo/widgets that can be scaffolded. For this demo we need to select button and text-input using the arrow keys and space to select each one, press enter to complete the process.

    This should have created a themes directory in the project’s src directory, this will be where the custom theme is created.

    Dojo uses the concept of a key to be able to look up and load classnames for a theme, it is a composite of the package name and the widget name joined by . These keys are used as the keys to object that is exported from the main theme.ts.

    Theme the Worker widget

    In order to theme the Worker widget, we need to create worker.m.css within our themes/dojo directory and use it within theme.ts. As mentioned above, the naming of the key of the exported theme must match the name from the project’s package.json and the name of the widget’s style sheet joined by a forward slash (/).

    Let’s start by creating a red Worker and observe our theme being applied correctly.

    1. /* worker.m.css */
    2. .root {
    3. background: red;

    So for theme.ts, you need to add the import for the worker.m.css and the theme itself to the exported object using the key biz-e-corp/worker.

    1. File: src/themes/dojo/theme.ts
      import * as worker from './worker.m.css';
    2. import * as textInput from './@dojo/widgets/text-input/text-input.m.css';
    3. import * as button from './@dojo/widgets/button/button.m.css';
    4. export default {
    5. 'dojo-theming-tutorial/worker': worker,
    6. '@dojo/widgets/text-input': textInput,
    7. '@dojo/widgets/button': button
    8. };

    What is a registry?
    A registry provides a mechanism to inject external payloads into widgets throughout the application tree. To learn more, take a look at the container tutorial and .

    However an application can automatically inject a theme from the registry to every themed widget in an application tree. First, create a themeInjector using the registerThemeInjector function by passing a registry instance and theme. This will return a handle to the themeInjector that can be used to change the theme using themeInjector.set(), which will invalidate all themed widgets in the application tree and re-render using the new theme!

    Update our main.ts file to import our theme and create a themeInjector.

    1. File: src/main.ts
      import renderer from '@dojo/framework/widget-core/vdom';
    2. import { w } from '@dojo/framework/widget-core/d';
    3. import { registerThemeInjector } from '@dojo/framework/widget-core/mixins/Themed';
    4. import { Registry } from '@dojo/framework/widget-core/Registry';
    5. import App from './widgets/App';
    6. import theme from './themes/dojo/theme';
    7. const registry = new Registry();
    8. registerThemeInjector(theme, registry);
    9. const r = renderer(() => w(App, {}));
    10. r.mount({ domNode: document.querySelector('my-app') as HTMLElement, registry });

    Open the application in your web browser and see that the Worker backgrounds are now red.

    Use variables and complete the Worker theme

    The Dojo build system supports new CSS features such as css-custom-properties by using PostCSS to process our .m.css files. We can use these new CSS features to add variables to worker.m.css and complete its theme.Let’s create themes/dojo/variables.css (notice that this file does not have a .m.css extension as it is not a css-module file).

    In the above code you can see that we have created a number of CSS Custom Properties to be used within our theme and wrapped them in a :root selector which makes them available on the global scope within our css-modules.To use them, we can @import the variables.css file and use the var keyword to assign a css-custom-property to a css rule.

    Now we will use these variables in our themes worker.m.css to create our fully themed Worker.

    1. File: src/themes/dojo/worker.m.css
      @import './variables.css';
    2. .root {
    3. width: 320px;
    4. height: 370px;
    5. box-sizing: border-box;
    6. margin: var(--spacing);
    7. position: relative;
    8. }
    9. .image {
    10. background: var(--component-accent);
    11. border-radius: 50%;
    12. width: 250px;
    13. height: 250px;
    14. margin-bottom: var(--padding);
    15. }
    16. .imageSmall {
    17. background: var(--component-accent);
    18. border-radius: 50%;
    19. width: 72px;
    20. height: 72px;
    21. display: inline-block;
    22. margin-right: 24px;
    23. }
    24. .workerFront, .workerBack {
    25. position: absolute;
    26. top: 0;
    27. bottom: 0;
    28. left: 0;
    29. right: 0;
    30. border: 1px solid var(--component-accent);
    31. .workerFront {
    32. box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.35);
    33. padding: var(--padding);
    34. text-align: center;
    35. }
    36. .workerBack {
    37. box-shadow: 0 20px 35px 0 rgba(0, 0, 0, 0.15);
    38. background: var(--card);
    39. color: var(--body);
    40. }
    41. background: var(--accent);
    42. color: var(--card);
    43. padding: 24px;
    44. }
    45. .generalInfo {
    46. display: inline-block;
    47. vertical-align: top;
    48. line-height: 24px;
    49. }
    50. .generalInfo .label {
    51. display: none;
    52. }
    53. .tasks strong {
    54. background: var(--secondary-accent);
    55. color: var(--card);
    56. display: block;
    57. font-size: 14px;
    58. padding: 14px 24px;
    59. text-transform: uppercase;
    60. font-weight: 400;
    61. }
    62. .task {
    63. padding: 12px 24px;
    64. border-bottom: 1px solid var(--component-accent);
    65. }

    Thus far in this tutorial, we have themed our custom Worker widget, but we have not targeted Dojo widgets that are contained within our application. To demonstrate the styling of Dojo widgets, we will theme the workerForm widget as it contains both DOM nodes and Dojo widgets.

    Let's create workerForm.m.css

    1. File: src/themes/dojo/workerForm.m.css
      @import './variables.css';
    2. .root {
    3. font-family: var(--font);
    4. padding: 30px;
    5. box-sizing: border-box;
    6. font-weight: bold;
    7. }
    8. .nameLabel {
    9. font-size: 14px;
    10. }

    And include it in theme.ts

    1. File: src/themes/dojo/theme.ts
      import * as worker from './worker.m.css';
    2. import * as workerForm from './workerForm.m.css';
    3. export default {
    4. 'dojo-theming-tutorial/worker': worker,
    5. 'dojo-theming-tutorial/workerForm': workerForm,
    6. };

    This should be familiar from theming the Worker in the previous section. To theme the Dojo TextInput within our WorkerForm, we need to add to the skeleton text-input.m.css theme created by @dojo/cli-create-theme, this is already exported from theme.ts.

    1. File: src/themes/dojo/@dojo/widgets/text-input/text-input.m.css
      @import './../../../variables.css';
    2. .root {
    3. margin-right: 10px;
    4. display: inline-block;
    5. composes: nameLabel from './../../../workerForm.m.css';
    6. text-align: left;
    7. }
    8. .input {
    9. height: 38px;
    10. border: 1px solid var(--input-border);
    11. box-sizing: border-box;
    12. border-bottom-color: color(var(--input-border) blackness(60%));
    13. padding: 8px;
    14. min-width: 230px;
    15. }

    Notice the styling rule for the .root selector? Here we are introducing another powerful part of the Dojo theming system, composes. Composes originates in the , allowing you to apply styles from one class selector to another. Here we are specifying that the root of a TextInput (the label text in this case), should appear the same as the nameLabel class in our WidgetForm. This approach can be very useful when creating multiple themes from a baseTheme and avoids repetitive redefinition of style rules.

    In your web browser you will see the TextInput widgets at the top of the form have been styled.

    Add the following theme styles to the button.m.css theme resource

    Summary

    • How to create a theme
    • How to apply a theme to both Dojo and custom widgets using the themeInjector.
    • How to leverage functionality from the Dojo theming system to apply variables
    • How to use to share styles between components
      You can download the completed from this tutorial.