Skip to content

Instantly share code, notes, and snippets.

@bendrucker
Created August 15, 2015 05:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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

Thoughts

The pseudo selector bit is very clever!


Prefixing can be addressed via npm packages

Maybe list the ones that you have in mind?

Create two immutatable states in your application

Why not just use one state object? Seems very reasonable for screen dimensions to be a part of your application's state.

There are some solvable implementation challenges, including the need for a singleton delegator

When would you need a singleton delegator?


I don't (yet) think that writing non-modular CSS is anywhere near as much of a you'll-pay-for-this-when-you-want-to-add-more-features-later as, say, non-modular JS or markup/hyperscript.
So while this looks like a cleaner approach, I'd love for this draft to give me a better sense of what my gains are here. I'd love for this draft to really convince me of the benefits of keeping as much styling as I can in JavaScript as opposed to trying to tackle modularity with a pre-processor such as Sass.

ex:

CSS is global and hard to modularize

Why exactly do I care?


Awesome read!

@bendrucker
Copy link
Author

Maybe list the ones that you have in mind?

Have to make em, just saying that they are easy. Would probably write them before publishing.

Why not just use one state object? Seems very reasonable for screen dimensions to be a part of your application's state.

Because you don't want app state coupled to transient. If you decide to serialize and load both, it's easy enough. But when you build into state undo you don't want to undo window size changes. You certainly could, but that'd be hard and probably not desirable. Separation is a slight cost but can be a huge win if you avoid detangling.

When would you need a singleton delegator?

10 components want to know when the screen size is <480px. You want to do that with one DOM listener, not 10. Then you trigger 10 handlers in JS land.

trying to tackle modularity with a pre-processor such as Sass

Only way to do semi-modular SASS: https://github.com/thlorenz/sass-resolve. Still not very modular.

Why exactly do I care?

You're building well-encapsulated components that can be used in multiple apps. Then you're coupling them to a bunch of global stylesheets. Your components are no longer encapsulated, and moving them between apps is likely to cause pain. That pain ranges from UI quirks at the best (layout bugs) and critical bugs at worst (e.g. a CSS hide/show that doesn't fire properly because a class name changes).

@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