Using Polymorph to Style a BEM Accordion (JavaScript Styles)

Edmund Reed
5 min readJun 16, 2019

Full disclosure: I am the author of Polymorph and this article is intended to promote the tool

A few months ago, I gave up on using Sass to style my components, despite being an avid contributor to the Sass community. I have a few reasons, but mainly it boils down to Developer Experience, and wanting to easily share configuration between my interactions and my styles.

I think using JavaScript for styling is still very much a maturing process; I’m really not too familiar with any of the popular tools but I gather that Styled Components and CSS Modules are two of the big players, with the former, I believe, being for React only.

View a live demo of Polymorph

I had a rough idea of what I wanted to do and the way I wanted to do it. For the sake of argument, we can consider an accordion using markup that follows the BEM naming convention:

If I were to use JavaScript to style this accordion, the most simple API I can think of to achieve this would be by using a plain JavaScript object that mirrors the BEM structure/interface, containing valid CSS key:value pairs. For a rudimentary example of what I have in mind, we can see the shape of the API develop:

Whilst this example is still primitive, and lacks things like the actual hiding/showing of the content depending on which panel is active, the reasons I like the direction it’s taking are:

  • It’s DRY — no keywords are repeated, and even the Block name of accordion isn’t even included, meaning we could change it at will without having to update the styles
  • It’s simple — it’s a plain JavaScript object with CSS key:value pairs, with the assumption that key names with objects for values should be treated as a BEM element names
  • Framework agnostic — the theory would be that you use this object to style DOM elements that are parent accordion BEM blocks, and most JavaScript frameworks allow you to access the underlying DOM nodes

With these benefits in mind (and the fact I hadn’t seen all of these benefits in other tools, which is fair enough as it relies on BEM so is quite opinionated), I thought it beneficial to explore this concept some more. Several months (and two rewrites) later I ended up with a finished tool called Polymorph, which allowed me to do exactly what I wanted to do the way I wanted to do it.

When all was said and done, I ended up something that would allow me to do some really cool stuff:

This example uses the sQuery library for working with BEM DOM elements

Some more reasons have arisen why I like having this as the API to style my accordion:

  • It allows a familiar “cascading” effect similar to CSS
  • It’s now a function that returns an object, aiding flexibility…
  • The function can expose the DOM node on which the styles are being applied, so rather than applying styles based on cascading rules, I can apply styles based on conditions if I wish
  • The function can expose a configuration object relating to the accordion
  • The function can expose a global theme object, useful for exposing global theming values like typography and colours (and if you’re familiar with React-Native, things like this)
  • The BEM element keys can be assigned functions exposing the DOM element of the BEM element
  • It can be incorporated into JavaScript libraries used for DOM querying such as sQuery like in the example (sQuery is like jQuery but for BEM)

The only thing I may question is the handling of modifiers: modifier(active). As simple as this is to understand, it’s essentially logic within a string, which I usually tend to hate as I consider it “magic”. A more verbose but less “magic” way could be by using Polymorph’s condition API.

To apply the above styles to the appropriate DOM elements, you can pass them to the polymorph function like so:

…where styles references our styles function, theme references the global theme (which can just be any valid JavaScript object), with the middle object being the instance configuration.

With a BEM mindset, this approach (from an architectural and DX perspective) feels quite intuitive and straightforward, and despite being opinionated, it seems flexible enough to handle any use-case I’ve required so far (including nested modules, sub-elements etc...).

Handling Interactions

The accordion works by adding/removing an active modifier to accordion panel elements. If a panel element has the active modifier, the corresponding content element will be shown, otherwise it will be hidden. This could be handled with the following JavaScript (just for demonstration purposes):

Polymorph will apply inline styles to the DOM elements (i.e it doesn’t generate any CSS). Each time the DOM is updated (e.g. to toggle the accordion__panel--active class), the styles will need to be re-applied so the DOM element(s) can be repainted (pseudo states like hover and focus toggle repaints automatically). Whilst you could just run the same code as before again after updating the DOM, this wouldn’t be ideal for performance. Instead, every Block and Element’s DOM node gets assigned a repaint method (i.e. Element.prototype.repaint), so the above code would need the following addition (line 17) in order to repaint the accordion panel after the modifier has been toggled:

Using sQuery

Using sQuery’s addModifier and removeModifier methods, the repaint method gets called automatically. If you were using sQuery you could reduce the above code to:

Checkout the Creating an Accordion with sQuery + Polymorph article for more information.

Using React + Lucid

Passing styles to Lucid’s <Module> HOC (or <Block> if using BEM) will allow the repaint method to be called on appropriate components in their componentDidUpdate lifecycle method. This means that whenever you update a modifier on a block or element using React’s state, the resulting DOM node will be repainted automatically. If you like the idea of using Polymorph with React, considering checkout out Lucid.

Sign me up!

First step is to install Polymorph:

If you’re not using NPM you can grab the bundle from Github

npm install --save @onenexus/polymorph

And then import it:

import polymorph from '@onenexus/polymorph';

The final step is to tell the environment to use the BEM preset (as it’s not the default convention for Polymorph — learn more):

window.Synergy = {
modifierGlue: '--',
componentGlue: '__'
}

If you’d also like to use sQuery like in the example, head over to the sQuery docs to get setup.

--

--

Edmund Reed

Design Systems Architect 🎨 UI•UX designer & developer 💻 I take front-end thought experiments too far 🧪 @valtech 💙