Extensions & Integrations

Palette is a module that provides an in-context interface for changing the values of developer-set CSS properties.
Upgrade your project
This is a premium module and requires a Pro license. More here.


ApostropheCMS logo

Apostrophe Palette

An in-context interface for changing CSS

@apostrophecms-pro/palette is a module that provides an in-context interface for changing the values of developer-set CSS properties. The values are stored in an Apostrophe piece singleton (like @apostrophecms/global) and applied to the site whenever the stylesheet link is included in a template. Adjusting values via the palette interface renders changes to the site instantly.

Developers define properties that can be changed by configuring the fields section of the module. Fields are configured similarly to other Apostrophe schema fields with some extra properties.

The generated stylesheet is stored in the @apostrophecms/global document and is available everywhere.

Example configuration

modules: {
  // ... project level configuration
  // Activate the module
  '@apostrophecms-pro/palette': {
    fields: {
      add: {
        backgroundColor: {
          type: 'color',
          label: 'Page Background',
          selector: 'body',
          property: 'color'
        },
        imageWidgetMargins: {
          label: 'Vertical space between image widgets',
          type: 'range',
          selector: ['.c-image-widget', '.c-slideshow-widget'],
          property: ['margin-bottom', 'margin-top'],
          min: 0,
          max: 10,
          step: 0.1,
          unit: 'rem',
          mediaQuery: '(max-width: 59.99em)'
        },
        buttonShadow: {
          name: 'buttonShadow',
          label: 'Button Shadow',
          type: 'color',
          selector: '.c-button',
          property: 'box-shadow',
          valueTemplate: '0 0 7px 2px %VALUE%'
        }
      },
      group: {
        colors: {
          label: 'Colors',
          fields: [ 'backgroundColor' ]
        },
        space: {
          label: 'Spacing',
          fields: [ 'imageWidgetMargins' ]
        },
        buttons: {
          label: 'Buttons',
          fields: [ 'buttonShadow' ]
        }
      }
    }
  }
}

fields properties of interest

label

Normal field label.

type

A subset of schema field types. Can take string, range, color, and select. Using other field types is permitted but not guaranteed.

selector

A string or array of strings to be used as CSS selectors. These are printed as-is, so it is valid to pass things like body, .template p, #someId [data-foo], even :root, etc. All selectors will be used to target the property property and give the value of the field.

property

A string or array of strings to be used as CSS properties. These are printed as-is and are used in conjunction with all selector properties and the value of the field itself.

unit (optional)

A string that is appended after the value of the field is printed as a CSS rule.

valueTemplate (optional)

Instead of the property value being derived solely from the field value, a template can be passed where the %VALUE% get replaced with the field value. Useful for complex CSS values that aren't totally being controlled by palette, like box-shadow.

mediaQuery (optional)

A string used to wrap a rule in a CSS media query. The format is as follows @media YOURMEDIAQUERY { YOURSELECTOR { YOURPROPERTY YOURUNIT; } }

  • Note, using the media query property will only apply that field's value to that media query. You may need multiple fields to fill out the spectrum of sizes.

Nested grouping

Unlike normal Apostrophe schemas, palette is designed to allow a second level of field grouping. Top level groups are represented similar to tabs, while a second level allows you to group like-elements into collapsable accordions.

  // Example of nested grouping
  fields: {
    add: { ... }, // Your fields
    group: {
      tabOne: {
        label: 'This is Tab One',
        fields: [ 'background', 'border', 'buttonColor' ]
      },
      tabTwo: {
        label: 'This is Tab Two',
        group: {
          accordionOne: {
            label: 'Base Typography',
            fields: [ 'baseFont', 'baseSize' ]
          },
          accordionTwo: {
            label: 'Heading Typography',
            fields: [ 'headingFont', 'HeadingSize' ]
          }
        },
        fields: [ 'ungroupedField', 'stillInTabTwo'  ]
      }
    }
  }

Using CSS custom properties (variables)

In addition to targeting element selectors directly you can also target custom property variables. This is useful when allowing editors to control more general site properties that might be applied in several ways. For example, your site's accent color might be a button background for one element and a border in another.

  // Example of CSS variable
  fields: {
    add: {
      accentColor: {
        type: 'color',
        label: 'Site Accent Color',
        selector: ':root',
        property: '--accent-color'
      }
    }
  }

You can now access accentColor in your normal CSS.

  .button {
    background-color: var(--accent-color);
  }
  .body {
    border: 5px solid var(--accent-color);
  }

Including the interface and stylesheets in your template

You don't have to do anything. They are automatically injected.

What actually happens?

When the global document is modified, a new palette stylesheet property is generated. The rest of the time no work is needed on each page request, and browser-side caching is also used. A version identifier is included in the style tag to ensure no stale styles are served. To preview edits on the fly, a style tag is updated dynamically with the latest stylesheet.

Custom rendering

By default, the code in the render.js file of this module is used on both front and back end to convert palette properties to CSS with the specified selectors and properties.

This behavior can be customized in two steps:

  1. Enable the serverRendered: true option of this module.

  2. Override the getStylesheet(doc) method of this module to accept doc (the palette piece) and return the CSS markup of your choice.

When serverRendered: true is in use this method may be an async function (it may return a promise).

serverRendered: true must be set in order to ensure all rendering requests flow to your overridden server-side method, even if they come from the UI code during editing. By default, these are handled on the browser side, which is very fast but rules out overrides.

Note that the schema of palette fields can be accessed via self.schema inside your getStylesheet function.

Your function is called only when settings have changed, and invocations are automatically debounced to prevent simultaneous invocation and excessive CPU use while editing.

Updated

1 month ago

Version

4.2.0

Report a bug