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
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:
- Use an immutable data structure in your application like observ or Immutable.
- Use memoization to prevent unnecessary calls to your rendering functions (e.g. vdom-thunk).
- 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).
- 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)
). - Pass your transient state into the render function of components, generally preceded by your application state.
- 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.
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:
- Build a CSS string in JavaScript, giving the keyframe a random name by appending a cuid
- Insert the CSS into a
<style>
tag - Export the keyframe name from the module
- 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'
}
})
}
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) {
// ...
}
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!