Skip to content

Instantly share code, notes, and snippets.

@bendrucker
Created August 15, 2015 05:04
Show Gist options
  • Save bendrucker/c8c6ab6158370b316902 to your computer and use it in GitHub Desktop.
Save bendrucker/c8c6ab6158370b316902 to your computer and use it in GitHub Desktop.
Styling diffable/modular UIs

Styling Diffable UIs

Presentation is an important part of a UI component. Diffing makes it signifcantly easier to build well-encapsulated components, but styles still pose problems. CSS is global and hard to modularize. For simple cases, inline styles fix the problem easily:

var h = require('virtual-dom/h')

function render (state) {
	var style = {
		color: state.enabled ? 'red' : undefined
	}
	return h('p', {style: style}, 'Hello world!')
}

The only challenge around simple CSS is when using long lists with server-side rendering (solvable). Prefixing can be addressed via npm packages.

The more difficult cases are the features that can't be used in inline styles:

  • @media queries
  • @keyframe animations
  • pseudo-selectors

Media queries

matchMedia to the rescue! Browser support is good and we get the benefit of using inline styles and JS instead of writing CSS.

We can integrate matchMedia with a diffable UI as follows:

  1. Use an immutable data structure in your application like observ or Immutable.
  2. Use memoization to prevent unnecessary calls to your rendering functions (e.g. vdom-thunk).
  3. Create two immutatable states in your application: one for application state (e.g. user is logged in) and one for transient state (e.g. screen width < 500px).
  4. Use matchMedia with a set of predefined breakpoints (e.g. ergonomic-breakpoint) and update your transient state object in your handler (matchMedia(query).addListener(handler)).
  5. Pass your transient state into the render function of components, generally preceded by your application state.
  6. Render inline styles conditionally depending on the value of the matched media (transient state).

There are some solvable implementation challenges, including the need for a singleton delegator a la dom-delegator.

Keyframes

It's typically easier to get performant CSS animations versus pure JavaScript animation. If we can accomplish the following, we can stay modular while still using CSS:

  1. Build a CSS string in JavaScript, giving the keyframe a random name by appending a cuid
  2. Insert the CSS into a <style> tag
  3. Export the keyframe name from the module
  4. require it

Example:

bounce.js

var bounce = createKeyframes({/* keyframes to bounce element */})
require('insert-css')(bounce.css)
module.exports = bounce.name

component.js

var bounce = require('./bounce')

function render () {
	return h('button', {
		style: {
			animation: bounce + ' 2s infinite'
		}
	})
}

Pseudo Selectors

You should be able to easily avoid the use of virtually all pseudo-selectors with relatively simple JavaScript.

Take alternating row colors in a table:

tr {
	background: white
}
tr:nth-child(even) {
	background: gray
}

Instead:

function render (rows) {
	return rows.map(function (row, index) {
		return h('tr', {
			style: {
				background: index % 2 ? 'gray' : 'white'
			}
		}, row.map(renderColumn))
	})
}

function renderColumn (data) {
	// ...
}
@chinedufn
Copy link

Awesome answers Ben. Thanks that cleared things up.


Took the time to actually write styles in JS and the difference was immediately clear. I'm converting.

When you edit/publish this I'd keep a target audience in mind. I'd imagine that most readers will be very unfamiliar with this stuff. Comes down to whether or not you want to support them.

Thanks for sharing!

@chinedufn
Copy link

So since that last comment I've taken inspiration from the Media queries section and more or less directly copied the implementation shown in the Keyframes section.

Wouldn't have tried inline styles (which I now swear by..) or figured out how to address some of these problems without this write up.

Release this when you have the spare time! I'm sure it will inspire others.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment