Skip to content

Instantly share code, notes, and snippets.

@JawsomeJason
Created September 25, 2016 10:03
Show Gist options
  • Save JawsomeJason/6bef728cf60367321504ec4d65f786ce to your computer and use it in GitHub Desktop.
Save JawsomeJason/6bef728cf60367321504ec4d65f786ce to your computer and use it in GitHub Desktop.
Create scoped themes using Sass and CSS Custom Properties
// options
// identifier for theme CSS custom properties (e.g. --theme__prop--theme-name)
$__theme-custom-property-prefix: theme !default;
// enable/disable CSS4 var(--custom-property) declarations
$__theme-enable-css-variables: true !default;
// enable/disable compatibility for legacy browsers.
// Warning: If enabled, cascade will not function, so you must inherit down
// the chain or re-declare themes where needed
$__theme-enable-decorator-fallbacks: true !default;
// color variable map in Sass
//stiletto, alpine, blue-bayoux
$__themes: (
// theme-name. "default" is required.
default: (
// properties should match up in each theme for theme-switching and cascading to work properly
background: #fff,
text: #333,
accent: blue
)
) !default;
/// Retrieves property/value from given theme
/// @param {string} $property-name Name of theme property
/// @param {boolean} $sass-value [false] Use hard value instead
/// of referencing CSS Custom Property
/// @param {string} $theme [local] Name of theme (default to local/current scope)
/// @return {string} the CSS Custom Property or value of theme property.
@function theme($property-name, $sass-value:false, $theme:local) {
@if $sass-value {
// local values don't exist in sass, so return to default if so
$theme: if($theme != local, $theme, default);
@return map-get(map-get($__themes,$theme),$property-name);
// if we're only returning the CSS4 variable
} @else if $__theme-enable-css-variables {
// return the requested theme property
@return var(--theme($property-name, $theme));
}
@return null;
}
/// Gets a theme property as a CSS custom property
/// @param {string} $property Name of theme property
/// @param {string} $theme-name Name of theme
@function --theme($property, $theme-name) {
@return --#{$__theme-custom-property-prefix}__#{$property}--#{$theme-name};
}
/// Default CSS to apply when including a theme
/// @access private
/// @param {string} $theme-name name of theme being applied
/// @param {boolean} $sass-value If enabled, include the actual
/// value instead of the CSS custom property
@mixin __theme-decorator($theme-name, $sass-value) {
@if ($sass-value and $__theme-enable-decorator-fallbacks)
or (not $sass-value and $__theme-enable-css-variables) {
background-color: theme(background, $sass-value, $theme-name);
color: theme(text, $sass-value, $theme-name);
::selection {
background: theme(accent, $sass-value, $theme-name);
color: theme(background, $sass-value, $theme-name);
}
::-moz-selection {
background: theme(accent, $sass-value, $theme-name);
color: theme(background, $sass-value, $theme-name);
}
}
}
/// Establishes a theme in the current Sass scope
/// @param {string} $theme-name The name of the theme being applied
/// @param {map} $config Configuration options
/// @property {boolean} $options.decorate [true] Include theme-decorator
/// @property {boolean} $options.local [true] Include as a local/scoped reference
/// @property {boolean} $options.include-css-variables [$__theme-enable-css-variables] Include scoped CSS Variables syntax
/// @property {boolean} $options.include-decorator-fallbacks [$__theme-enable-decorator-fallbacks] Include plain values (CSS fallback)
@mixin theme($theme-name, $config: ()) {
$defaults: (
decorate: true,
local: true,
include-css-variables: $__theme-enable-css-variables,
include-decorator-fallbacks: $__theme-enable-decorator-fallbacks
);
$settings: $defaults;
@each $property, $value in $config {
$settings: map-merge($settings, ($property: $value));
}
// spread settings to variables
$decorate: map-get($settings, decorate);
$local: map-get($settings, local);
$include-css-variables: map-get($settings, include-css-variables);
$include-decorator-fallbacks: map-get($settings, include-decorator-fallbacks);
// get theme name for scope
$prop-theme: if($local == true, local, $theme-name);
@each $property, $value in map-get($__themes, $theme-name) {
//set current theme
@if $include-css-variables {
#{--theme($property, $prop-theme)}: $value;
}
}
@if $decorate {
@if $include-decorator-fallbacks {
@include __theme-decorator($theme-name, true);
}
@if $include-css-variables {
@include __theme-decorator($prop-theme, false);
}
}
}
// if first request, initialize document root with theme variables
$__theme-initialized: false !default;
@if not $__theme-initialized {
:root {
/* establish theme variables in the root for reference */
@each $theme, $value in $__themes {
@include theme($theme, (local: false, decorate: false));
};
// establish default theme as top-level local theme
@include theme(default);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment