Skip to content

Theming

esr360 edited this page Nov 10, 2019 · 17 revisions

Theming is a generalised concept in web development. When using Cell, we might consider using a theme if we need to:

  • Share common properties between different modules
  • Change entire project look and feel on the fly by switching themes
  • Style individual modules

How Theming Works

Cell doesn't like to make too many assumptions about how your theme may ultimately exist; for example whether you are using React with a theme provider, or whether you simply have a static JSON file, Cell's goal is to let you use your existing theme(s) to style your Cell modules. You can of course also use plain Sass for your themes.

If you use JavaScript for theming, Cell will ultimately convert your theme to a Sass map and expose it to your Sass under the $theme variable (learn more). This variable is never looked up internally by Cell, but it can be accessed by your custom Cell modules.

Access Theme From Inside Modules

Cell provides the theme() utility function which is used for retreiving values from the theme. You can also use the map-get() or map-get-deep() functions to rereive a value from $theme:

Inside Your Module
font-size: theme('colors', 'primary');
Equivalent To
font-size: map-get-deep($theme, 'colors', 'primary');

Style individual modules

Thanks to the Cell Query Draft (CQD), you can write to CSS from configuration. This means we can store CQD configuration within a theme, and merge it into a module's configuration later.

It's important to only include cosmetic CSS properties in themes; manipulating layout properties with themes breaks theme semantics

In order to do this, your theme should have a modules property which stores your modules' CQD configuration (as well as any normal configuration for your module(s)):

Sass Theme
$theme: (
  'modules': (
    'myModule': (
      'someProperty': true,
      'font-family': 'Comic Sans MS',
      'padding': 20px
      ...
    ),
    'anotherModule': (
      ...
    )
  )
);
JavaScript Theme
export default {
  'modules': {
    'myModule': {
      'someProperty': true,
      'font-family': 'Comic Sans MS',
      'padding': '20px'
      ...
    },
    'anotherModule': {
      ...
    }
  }
}

Thanks to the Cell Query Draft (CQD), when the above theme is applied it would write the following to CSS:

.myModule, [class*="myModule--"] {
  font-family: 'Comic Sans MS';
  padding: 20px
}

Note that including CQD configuration within a theme alone does not write to CSS, it must be merged into the module's configuration (which is done automatically if using JavaScript themes and configuration)

Sass Themes

It's possible (and encouraged) to use JavaScript for theming instead

As Cell is a Sass library, the easiest (but least practical) way for a theme to exist is as a Sass map.

Theme
$theme: (
  'colors': (
    'primary': red,
    'secondary': blue
  ),
  'sizes': (
    'small': 12px,
    'large': 21px
  ),
  'modules': (
    'myModule': (
      'font-family': 'Comic Sans MS'
    }
  }
);
Module
// Default module configuration
$default: (
  'name': 'myModule',
   // `theme()` utility is used to get theme values
  'color': theme('colors', 'primary')
  ...
);

// Custom module configuration from theme using `theme()` utility
$custom: theme('modules', 'myModule');

// Merge $default and $custom using `config()` utility
$config: config($default, $custom);

// Create module styles
@include module {
  ...
}

See the theme() and config() utility functions for more information

Dynamically Evaluating Properties/Accessing Theme Values From Within Theme

Sometimes you may wish to access theme values within the theme itself, as in the below example:

Note: This will not work

$theme: (
  'colors': (
    'primary': red,
    'secondary': blue
  ),
  'modules': (
    'myModule': (
      'color': theme('colors', 'secondary')
    )
  )
);

See the theme() utility function for more information

This will unfortunatly error as undefined variable: $theme. It's possible to achieve this by dynamically evaluating properties later on. So we can pass a reference to the theme function along with our desired arguments, and have Cell evaluate it later when it's needed:

$theme: (
  'colors': (
    'primary': red,
    'secondary': blue
  ),
  'modules': (
    'myModule': (
      'color': ('theme', ('colors', 'secondary')) // trying to get 'blue'
    )
  )
);

When the above myModule map is inevitably passed to the config() utility, the ('theme', ('colors', 'secondary')) value will be treated as a function and executed like so:

  • The first item in the list (if it is a string and a function exists with the same name) will be passed to a get-function() call
  • This in turn will be passed as the first argument of a call() call
  • The second item in the list (if it too is also a list) will be spread as the subsequent arguments to the call() call
'color': call(get-function('theme'), 'colors', 'secondary')

This works with any Sass function, not just theme()

JavaScript/JSON Themes

Make sure to read the JavaScript configuration page for full context and setup instructions

Learn how to integrate Cell with React

If using JavaScript/JSON for your theme, in order to be exposed to Cell it should exist as one of the following:

  • Any valid .json/.json5 file
  • Any .js file that exports an object
  • Any .js file that exports a function which returns an object

Importing Theme Into Sass

For Cell to know that an imported file is intended to be a theme, it must either be called theme.{js|json} or have a direct parent/grand-parent directory called themes

@import 'themes/myTheme.js'; // `$theme` is now defined

You should ensure you import your theme into your Sass before importing your modules (you can also import your theme into each module individually)

Importing a theme into your Sass does a few things:

  • It converts the resultant object of your import into a Sass map and exposes it to your Sass under the $theme variable
  • It attaches the theme to your environment's global object under global.Synergy.THEME
  • ...where it can be read by subsequent JavaScript imports (namely your modules' config.js files)

Passing CQD Configuration

CQD configuration for your module is merged automatically from your theme into your module's configuration by Cell during compilation (provided you have also imported a JavaScript/JSON config file into your module's Sass).

Accessing Theme From Configuration

To access your project's theme within a module's configuration, your configuration should:

  • Exist as a function that returns an object
  • The function should accept a theme argument as the first argument
config.js
export default (theme) => ({
  name: 'myModule',
  color: theme.colors.primary,
  ...
});

When this function is executed by Cell, if you have previously imported a theme file into your Sass, then the evaluated theme will be supplied as the argument. The object returned from executing this function will be converted to a Sass map and made available to your Sass under the $config variable (learn more).

Dynamically Evaluating Properties/Accessing Theme Values From Within Theme

Sometimes you may wish to access theme values within the theme itself, as in the below example:

Note: This will not work

export default {
  'colors': {
    'primary': 'red',
    'secondary': 'blue'
  },
  'modules': {
    'myModule': {
      'color': theme.colors.secondary // trying to get 'blue'
    }
  }
}

This will unfortunatly error as Uncaught ReferenceError: theme is not defined. The desired behaviour can be achieved by ensuring that:

  • Your theme exists as a function that returns an object
  • The function should accept a theme argument as the first argument
  • Values requiring access to theme should exist as a function that returns the desired theme value
export default (theme) => ({
  'colors': {
    'primary': 'red',
    'secondary': 'blue'
  },
  'modules': {
    'myModule': {
      'color': () => theme.colors.secondary
    }
  }
});
Clone this wiki locally