Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Styling React Components

Ad9e927bce4bd0519631296b5af7af83?s=47 Michael Chan
November 18, 2015

Styling React Components

The styling of React components is a contentious topic in 2015.

It challenges everything we know about styling apps. In this talk, we’ll take a practical look at styling components in JavaScript: good practices, libraries, testing, and how to expose great APIs for distributed components.

We’ll explore when styling in JavaScript is a great fit and when it might be best to "just use CSS."

Ad9e927bce4bd0519631296b5af7af83?s=128

Michael Chan

November 18, 2015
Tweet

Transcript

  1. STYLING REACT COMPONENTS We're going to talk about Styling React

    Components.
  2. STYLING REACT COMPONENTS If you came for "Styling React Component",

    don't worry, you're in the right place...
  3. I figured people wouldn't know it was me talking unless

    the title of my talk had a spelling error. Things are better if you pretend English is my second language.
  4. I SUCK AT PREDICTING THE FUTURE Let's talk about other

    things I suck at. I suck at predicting the future.
  5. WE SUCK AT PREDICTING THE FUTURE But I don't feel

    too bad about it because we all suck a little bit at predicting the future.
  6. Obviously, this photo was not taken two weeks ago.

  7. OUR PREDICTIONS ARE ASPIRATIONAL Our predictions are aspirational. We look

    into the future hopefully.
  8. The overboard is an aspirational prediction. Thinking about it, a

    overboard has very few similarities with a skateboard. The only real similarity is that you stand on it the same way. A naive view of the future given that this is a pretty uncomfortable way to stand on something that moves itself.
  9. While we didn't get hoverboards, we got these smart boards

    (or whatever they're called). And while these don't look anything like a overboard, they fit the the aspirational need for a hoverboard, in a practical way.
  10. THE FUTURE IS CRAFTED BY PAIN And if you're watching

    carefully, you see that the future is crafted by pain...
  11. PAIN IS UNPREDICTABLE and pain is unpredictable.

  12. 2011 Let's talk about pain a little. In 2011, data-binding

    was the name of the of the game. All of these JavaScript frameworks were competing on data-binding. This in response to the pain of marshaling real-time updates to the DOM consistently.
  13. O.o 2012 Object observe was created as a standards proposal

    to bring this idea to JS. It was a first-class way to watch for mutations on objects and arrays.
  14. 2013 By 2013 React entered the seen and said that

    "shared mutable state is the root of all evil." Make a new DOM every time something changes. Look, we did it!
  15. 2015 By 2015, all the competing frameworks had been convinced

    and adopted this radical way of thinking was indeed the future.
  16. O.o 2015 And in just four years, we moved in

    and out of an entire class of pain.
  17. 2015 And the O.o proposal was withdrawn.

  18. THE WEB PLATFORM IS MOVING FAST We have HTML5, ES2015

    and attention is turning once again to CSS...
  19. CSS IS THE NEW JS Smart people are asking "Now

    that JS is good, how do we move stylesheets forward?"
  20. HOW TO WAIT HAPPILY This talk is about how to

    wait happily.
  21. ⚽ GOAL: ONE THING YOU CAN USE TODAY But my

    goal is that you can walk away with 1 practical way to improve your styling game today.
  22. MY PAIN All solutions are contextual. Here's what's guiding our

    solutions.
  23. PLANNING CENTER I work at a company called Planning Center.

    We make software for churches. It's weird, I know, but don't worry about it.
  24. 8 APPS I work on frontend tooling for these 8

    apps... 7 available now. 1 next year
  25. 10 YEARS OLD The oldest app 10 years old. So

    supporting a legacy system is crucially important.
  26. 2 APPS IN 2013 When I started, we had only

    two apps. My job became creating a way to share assets, styles, and components between these apps and the 6 we would come to build.
  27. RAILS + REACT Pairing down has been instrumental. All the

    apps are very standard Rails apps, with React powering interactive UI.
  28. > 7 FLUX IMPLEMENTATIONS Now we haven't quite paired everything

    down. As I speak we use no less than 7 flux implementations.. some apps with 2 or more implementations.
  29. HURRAY So hurray for that.

  30. BEM So, it's 2013 and we started doing all new

    CSS in BEM.
  31. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } For the uninitiated, this is what a BEM object might look for a todo list item
  32. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } < BLOCK Our block is the root element of this CSS object.
  33. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } < MODIFER You can use these additional classes, called modifiers. modifiers allow you to make contextual changes to an entire block. This is the only appropriate way to cascade changes in BEM.
  34. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } < ELEMENT
  35. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } < DESCENDENT In some BEM derivatives, this is better known as a descendent. But I suppose "BDM" doesn't sound as cool. In any case, everything is namespaced, making this some type of fake CSS module. Let's add some state to this...
  36. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } .todo-item.is-complete { color: #aaa } BEM is not opinionated about state because it was designed for layout. I use a convention Necolas Gallagher's sets out in SUIT CSS: .is- prefixed state classes. This has a couple benefits: * doesn't conflate layout and state modifiers
  37. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } Now that we have our state selector, we need to cascade changes down to certain descendants. `mark` is going to change from asterisk to a check.
  38. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } .todo-item.is-complete .todo-item__name { text-decoration: line-through } We'll add a new descendent, for `name.` In the complete state, we'll change the mark to have a line-through.
  39. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } .todo-item.is-complete .todo-item__name { text-decoration: line-through } .todo-item.is-due .todo-item__name { color: red } Finally, we'll add a state for an item that is due. Due items will be red.
  40. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } .todo-item.is-complete .todo-item__name { text-decoration: line-through } .todo-item.is-due:not(.is-complete) .todo-item__name { color: red } And we'll add a `:not` pseudo-element so items that are done, but due, will just look done. It doesn't matter if an item is `due` if it's done.
  41. .todo-item { color: #111 } .todo-item--small { font-size: 80% }

    .todo-item__mark::before { content: '*' } .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } .todo-item.is-complete .todo-item__name { text-decoration: line-through } .todo-item.is-due:not(.is-complete) .todo-item__name { color: red } This is our complete CSS BEM object. And for those yelling at me for not doing it in sass...
  42. $module: ".todo-item"; #{$module} { color: #111; &--small { font-size: 80%;

    } &.is-complete { color: #aaa; } &__mark::before { content: '*'; #{$module}.is-complete & { Here it is in all of it's two-slide un-grepable glory
  43. And here's what it looks like in the browser. Now

    This is an OBJECTIVELY trivial example. Even for a todo example. But there's already a lot happening in CSS land.
  44. const TodoItem = ({name, due, complete}) => ( <div className="todo-item">

    </div> ); We'll start our component by rendering out the block and block classname.
  45. const TodoItem = ({name, due, complete}) => ( <div className="todo-item">

    <span className="todo-item__mark" /> <span className="todo-item__name">{name}</span> </div> ); We'll add the two descendants, with classnames.
  46. const TodoItem = ({name, due, complete}) => ( <div className={classnames({

    "todo-item": true, })}> <span className="todo-item__mark" /> <span className="todo-item__name">{name}</span> </div> ); We'll throw Jed Watson's `classnames` library in there.
  47. const TodoItem = ({name, due, complete}) => ( <div className={classnames({

    "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span className="todo-item__name">{name}</span> </div> ); And add our state classes.
  48. const TodoItem = ({name, due, complete}) => ( <div className={classnames({

    "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span className="todo-item__name">{name}</span> </div> ); Now, we can see how classes get applied but not what they do. We have to consult the stylesheet for that.
  49. const TodoItem = ({name, due, complete}) => ( <div className={classnames({

    "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span className="todo-item__name">{name}</span> </div> ); Also, this bit of markup is completely opaque. The implementation lives completely within the stylesheet.
  50. WHAT WOULD HAPPEN IF WE LET REACT HANDLE STATE-STYLES?

  51. .todo-item { color: #111 } .todo-item__mark::before { content: '*' }

    .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } .todo-item.is-complete .todo-item__name { text-decoration: line-through } .todo-item.is-due:not(.is-complete) .todo-item__name { color: red } Let's start by removing this most complicated selector—the selector for styling a due item.
  52. .todo-item { color: #111 } .todo-item__mark::before { content: '*' }

    .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } .todo-item.is-complete .todo-item__name { text-decoration: line-through } [deleted]
  53. const TodoItem = ({name, due, complete}) => ( <div className={classnames({

    "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span className="todo-item__name">{name}</span> </div> ); Back in our component...
  54. const TodoItem = ({name, due, complete}) => ( <div className={classnames({

    "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span className="todo-item__name">{name}</span> </div> ); Here's the line we're going to change.
  55. const TodoItem = ({name, due, complete}) => ( <div className={classnames({

    "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={(due && !complete) ? {color: "red"} : {}} className="todo-item__name">{name}</span> </div> ); We add a state prop with a simple ternary. If the item is `due` and not `complete`, use the object with `color: red`, otherwise apply no styles. That was easy.
  56. .todo-item { color: #111 } .todo-item__mark::before { content: '*' }

    .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } .todo-item.is-complete .todo-item__name { text-decoration: line-through } Back to our stylesheet
  57. .todo-item { color: #111 } .todo-item__mark::before { content: '*' }

    .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } Let's get rid of the `name` descendent all together...
  58. const TodoItem = ({name, due, complete}) => ( <div className={classnames({

    "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={(due && !complete) ? {color: "red"} : {}} className="todo-item__name">{name}</span> </div> ); Back in our component...
  59. const nameStyles = (due, complete) => { return (due &&

    !complete) ? {color: "red"} : {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={nameStyles(due, complete)} className="todo-item__name">{name}</span> </div> ); This function is about to get more complex. Let's upgrade it to a proper function.
  60. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={nameStyles(due, complete)} className="todo-item__name">{name}</span> </div> ); Replace the turnery with a proper if statement...
  61. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={nameStyles(due, complete)} className="todo-item__name">{name}</span> </div> ); and add our second condition for `complete`
  62. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={nameStyles(due, complete)}>{name}</span> </div> ); now we can remove the className entirely. This feels pretty good.
  63. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-due": due, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={nameStyles(due, complete)}>{name}</span> </div> ); And `is-due` can be removed from the block.
  64. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={nameStyles(due, complete)}>{name}</span> </div> ); We've completely localized the way this element gets styled. It's now on the element and this function is very easy to reason about.
  65. .todo-item { color: #111 } .todo-item__mark::before { content: '*' }

    .todo-item.is-complete { color: #aaa } .todo-item.is-complete .todo-item__mark::before { content: '✔' } Back to our stylesheet. Let's get rid of the `mark` descendent. With React components, pseudo-elements are just elements.
  66. .todo-item { color: #111 } .todo-item.is-complete { color: #aaa }

  67. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={nameStyles(due, complete)}>{name}</span> </div> ); Back in our component.
  68. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-complete": complete, })}> <span className="todo-item__mark" /> <span style={nameStyles(due, complete)}>{name}</span> </div> ); Let's focus on the markup for our `mark` descendent.
  69. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-complete": complete, })}> <span>{(complete) ? '✔': '*'}</span> <span style={nameStyles(due, complete)}>{name}</span> </div> ); And use a simple ternary to change the character based on state.
  70. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-complete": complete, })}> <span>{(complete) ? '✔': '*'}</span> <span style={nameStyles(due, complete)}>{name}</span> </div> );
  71. .todo-item { color: #111 } .todo-item.is-complete { color: #aaa }

    Let's take that last state class out.
  72. .todo-item { color: #111 } [deleted]

  73. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div className={classnames({ "todo-item": true, "is-complete": complete, })}> <span>{(complete) ? '✔': '*'}</span> <span style={nameStyles(due, complete)}>{name}</span> </div> );
  74. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div style={(complete) ? {color: "#aaa"} : {} className={classnames({ "todo-item": true, "is-complete": complete, })}> <span>{(complete) ? '✔': '*'}</span> <span style={nameStyles(due, complete)}>{name}</span> </div> ); Again, we'll use a ternary to apply this style...
  75. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div style={(complete) ? {color: "#aaa"} : {} className="todo-item" > <span>{(complete) ? '✔': '*'}</span> <span style={nameStyles(due, complete)}>{name}</span> </div> ); And lose our need for `classnames`
  76. const nameStyles = (due, complete) => { if (due &&

    !complete) return {color: "red"}; if (complete) return {textDecoration: "line-through"}; return {}; }; const TodoItem = ({name, due, complete}) => ( <div style={(complete) ? {color: "#aaa"} : {} className="todo-item" })}> <span>{(complete) ? '✔': '*'}</span> <span style={nameStyles(due, complete)}>{name}</span> </div> ); To me, this is much easier to reason about than our CSS. We can use JavaScript to resolve styles. As the examples get more complicated, JS does a better job of scaling to the challenge and keeping isolated decisions isolated.
  77. TESTABILITY It's really easy to break a UI, when the

    functionality lives in CSS State changes are UI changes. The should happen in JavaScript and they should be testable.
  78. it("looks crossed-out, when complete", () => { }); Let's write

    a quick test for this...
  79. it("looks crossed-out, when complete", () => { shallowRenderer.render(<Item name="" complete={true}>);

    }); We can use the shallow renderer, we don't need a DOM.
  80. it("looks crossed-out, when complete", () => { shallowRenderer.render(<Item name="" complete={true}>);

    const result = shallowRenderer.getRenderOutput(); }); grab the result
  81. it("looks crossed-out, when complete", () => { shallowRenderer.render(<Item name="" complete={true}>);

    const result = shallowRenderer.getRenderOutput(); assert.deepEqual( result.props.style, { textDecoration: "line-through" } ); }); assert that the resulting object has style text-decoration "line-through"
  82. it("accepts a completeStyle prop", () => { }); You have

    some contextual styles you want to add? We can write a test for that...
  83. it("accepts a completeStyle prop", () => { const mergeStyles =

    { color: "silver" }; }); draw up an object with some arbitrary styles...
  84. it("accepts a completeStyle prop", () => { const mergeStyles =

    { color: "silver" }; shallowRenderer.render( <Item name="" complete={true} completeStyle={mergeStyles}> ); }); shallow render with the styles...
  85. it("accepts a completeStyle prop", () => { const mergeStyles =

    { color: "silver" }; shallowRenderer.render( <Item name="" complete={true} completeStyle={mergeStyles}> ); const result = shallowRenderer.getRenderOutput(); }); get the result...
  86. it("accepts a completeStyle prop", () => { const mergeStyles =

    { color: "silver" }; shallowRenderer.render( <Item name="" complete={true} completeStyle={mergeStyles}> ); const result = shallowRenderer.getRenderOutput(); assert.deepEqual( result.props.style, { color: "silver" } ); }); and assert that they made it through to the component. Easy
  87. So, what's good about this? A lot of things.

  88. SQUEAKY CLEAN CSS This makes CSS easy again. You're only

    concerned with APPEARANCE, not functionality.
  89. PRECISION It's easier to resolve complex style resolution in a

    function. It becomes much clearer what's happening and you have an entire programming language at your disposal. If you've worked in a large CSS codebase, you know that state-classes just SPRAAAAAWL.
  90. TESTABILITY If you can break functionality by changing CSS, you're

    component is broken. You don't have the right separation of concerns. If an item is done, it should look done, regardless of the style.
  91. LESS POLICING STYLESHEET PATTERNS Less policing around CSS * you

    can stop enforcing diligence around stylesheet structure * don't have to decide what a component's initial state is. * you don't have to decide how to represent state.
  92. WHAT WOULD HAPPEN IF WE DID ALL OUR STYLES THIS

    WAY? This worked so well that we started to ask: "What would happen if we did all of our styles this way?"
  93. INLINE-STYLES

  94. CSS in JS Last year, Christopher Chedeux gave a talk

    called CSS in JS
  95. CSS in JS It was insanely popular. His slide deck

    from the talk has had over half a million views.
  96. 1.Global Namespace 2.Dependencies 3.Dead Code Elimination 4.Manification 5.Sharing Constants 6.Non-deterministic

    Resolution 7.Isolation And this list started to show up everywhere.
  97. 1.Global Namespace 2.Dependencies 3.Dead Code Elimination 4.Manification 5.Sharing Constants 6.Non-deterministic

    Resolution 7.Isolation SOLVED BY INLINE-STYLES The premise was that all of these problems go away when elements styled inline and that React afforded you a way to do this in a performant way.
  98. This talk was an eye opener. Because it showed the

    problems off CSS at the scale of Facebook. I don't think you should be burdened by the problems Facebook faces until you're running at that scale. But when you've run into a problem as many times as they have, you get names for them, like "non-deterministic resolution," which i'm still not smart enough to understand.
  99. Responses were mixed. Joy, fear, anger. You name it. It

    was a watershed moment for CSS. Tons of libraries and posts came out of this talk. I gave of those talks.
  100. As it shook out, there were a couple hard parts...

  101. BROWSER EVENTS ARE HARD

  102. MEDIA-QUERIES ARE HARD

  103. .list-item:hover { background-color: rgba(0,0,0,0.1); } Let's compare a few examples:

    This is what's required to do :hover in CSS...
  104. class ListItem extends React.Component { constructor(props) { super(props); this.state =

    { hovered: false }; this.handleMouseEnter = () => { this.setState({ hovered: true }); } this.handleMouseLeave = () => { this.setState({ hovered: false }); } } render() { return ( <li style={Object.assign({}, styles, this.state.hovered && {backgroundColor: "rgba(0,0,0,0.1)"} )} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} {...this.props} /> ); } } This is the boilerplate for handling the hover event in a React component. This isn't better.
  105. .list-item:hover { background-color: rgba(0,0,0,0.1); } If we want to add

    a media-query to our CSS, that's pretty simple...
  106. .list-item:hover { background-color: rgba(0,0,0,0.1); } @media (max-width: 480px) { .list-item

    { width: 100%; } }
  107. class ListItem extends React.Component { constructor(props) { super(props); this.state =

    { hovered: false, smallScreen: false }; this.handleMouseEnter = () => { this.setState({ hovered: true }); } this.handleMouseLeave = () => { this.setState({ hovered: false }); } this.handleMatchMediaChange = (e) => { return this.setState({tabletContext: e.matches}); } } componentWillMount() { this.handleMatchMediaChange(window.matchMedia("(max-width: 480px)")); window.matchMedia( "(max-width: 480px)") .addListener(this.handleMatchMediaChange); } componentWillUnmount() { window.matchMedia("(max-width: 480px)") .removeListener(this.handleMatchMediaChange); } render() { return ( <li style={Object.assign({}, styles, this.state.hovered && {backgroundColor: "rgba(0,0,0,0.1)"} )} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} {...this.props} /> ); } } ... but it's cripplingly painful in a React component.
  108. So libraries came along to make this easier, my favorite

    is Radium.
  109. const styles = { color: '#0074d9', ':hover': { color: '#0088FF',

    }, '@media (max-width: 480px)': { width: '100%', } } .my-class { color: #0074d9; &:hover { color: #0088FF; } @media (max-width: 480px) { width: 100%; } } Radium SCSS It's modeled after Sass and, if you squint, the shape of these objects is identical.
  110. If you like the approach but don't like Radium, they

    have a tremendously detailed library comparison on their repository. https://github.com/FormidableLabs/radium/blob/master/docs/ comparison/README.md
  111. ??? Is Radium a part of our stack?

  112. NO At the end of the day, it didn't work

    for us.
  113. ALL SOLUTIONS REQUIRED NEW TOOLING I wasn't willing to add

    libraries with this much churn into all of our apps.
  114. 8 COULDN'T SHARE ABSTRACTIONS Couldn't share abstractions with Rails.

  115. But these solutions are really great for certain teams.

  116. JS-HEAVY TEAM This is great fit where you have a

    new app and a js-heavy team. You have the ability to move so quickly with an approach like this, using just JavaScript. I see a lot of promise for this technique.
  117. WEB + REACT NATIVE It's great for teams that are

    building a web and react-native app side-by- side. React Native doesn't have CSS. Inline-styles become a transferrable skill.
  118. WHERE AM I AT? So, where does that leave me?

    Where am I at now?
  119. I DON'T LIKE THE ALTERNATIVE The alternative was to keep

    writing BEM. But there was another problem that really started to get to me.
  120. The :rolling_eyes_face: pattern.

  121. Thing.jsx You write your component...

  122. Thing.jsx Thing.scss you write your stylesheets in a file with

    the same name...
  123. Thing.jsx Thing.scss Thing Component And the these two things become

    the component abstraction your application is concerned with.
  124. SUFFICIENTLY LOCAL CSS IS INDISTINGUISHABLE FROM INLINE-STYLES. Jed Schmidt, BrooklynJS

    HTTPS://WWW.YOUTUBE.COM/WATCH?V=XJK03IF9O7S Jed Schmidt addressed this in a way that resonates with me. If you've modularized well enough, you're not actually getting the benefits of re-use from your CSS.
  125. Thing.jsx Thing.scss Thing Component We're never going to use this

    again. Nor should we. If it's part of the component implementation, it should be considered private API.
  126. RE-USE IS A MYTH

  127. Thing.jsx Thing.scss Thing Component I don't want this abstraction and

    I don't need it. Think about all the coupling: * React component filename * CSS object filename * Use of the string classname in the component * Implementation of the classname in the CSS object file
  128. Thing.jsx I want this abstraction...

  129. HOW DO WE GET THIS WITHOUT INLINE-STYLES?

  130. ATOMIC CSS* This term means a lot. So I'm going

    to show you what I mean by it.
  131. Markup <div> <div></div> <div></div> <div></div> <div></div> </div> This is our

    markup. I know: "really exciting examples today."
  132. (default) This is how it would layout by default

  133. .d-f we're going to use some shorthand classes that map

    to a single property- value. .d-f => { display: flex }
  134. .d-f .f_1 .f_1 => { flex: one } I'm using

    a convention here to affect direct-children. An underscore will apply the style to children.
  135. .d-f .f_1 .fd-c .fd-r@md I can use an @ symbol

    for media queries. So, I want flex column when we're below my medium breakpoint and a row at the medium breakpoint.
  136. .d-f .f_1 .fd-c .fd-r@md p_1@md and we can add some

    padding at the medium breakpoint as well.
  137. There are some really nice benefits here, if you've already

    bought into the inline-styles idea.
  138. Defer design The worst part of CSS is that you

    have to name things first and there's no true modularization.
  139. Style without CSS Defer design This frees up developers to

    style without writing CSS
  140. Server-rendered friendly Style without CSS Defer design server-rendered friendly. HIGHLY

    cacheable
  141. Inline workflow + breakpoints Server-rendered friendly Style without CSS Defer

    design solves the problem of inline-styles having trouble with breakpoints.
  142. HISTORICAL CRITICISM...

  143. WHERE DOES THE ‘SCOPE’ OF THIS SECTION OF DOM START

    AND STOP? Harry Roberts, @csswizardry HTTP://CSSWIZARDRY.COM/2015/03/CAN-CSS-BE-TOO-MODULAR/ http://csswizardry.com/2015/03/can-css-be-too-modular/ 8 March, 2015
  144. ARE ANY OF THE CLASSES THEMATICALLY RELATED? Harry Roberts, @csswizardry

    HTTP://CSSWIZARDRY.COM/2015/03/CAN-CSS-BE-TOO-MODULAR/
  145. DO THESE CLASSES APPEAR... PURELY COINCIDENTALLY? Harry Roberts, @csswizardry HTTP://CSSWIZARDRY.COM/2015/03/CAN-CSS-BE-TOO-MODULAR/

  146. ARE ANY OF THE CLASSES OPTIONAL AT THIS POINT? Harry

    Roberts, @csswizardry HTTP://CSSWIZARDRY.COM/2015/03/CAN-CSS-BE-TOO-MODULAR/
  147. IF ONE OF THOSE CLASSES CHANGES... WILL THEY NEED TO

    BE CHANGED IN EVERY SIMILAR PIECE OF DOM? Harry Roberts, @csswizardry HTTP://CSSWIZARDRY.COM/2015/03/CAN-CSS-BE-TOO-MODULAR/
  148. WE HAVE TINY CSS, BUT OUR HTML HAS... LOST ITS

    ABILITY TO BE READ, OR TO SELF-DOCUMENT. THIS IS TOO EXTREME. Harry Roberts, @csswizardry HTTP://CSSWIZARDRY.COM/2015/03/CAN-CSS-BE-TOO-MODULAR/
  149. "MY HTML IS SO NON-SEMANTIC!" You might hang your head

    in shame for even having this thought.
  150. "WHY CAN'T I CONVEY MEANING??"

  151. "WHY DO I MAKE BAD LIFE CHOICES?"

  152. "WAIT... WHAT HTML?" I'm writing components. I use JSX. My

    HTML is an artifact. This article was written about layout.
  153. "ISN'T HAVING A COMPONENT CLASS AND A CSS CLASS REDUNDANT?"

    We're implementing a class in two different languages. Isn't this redundant?
  154. "AREN'T COMPONENTS THE ABSTRACTION I WAS AFTER ALL ALONG?"

  155. YES Now, to be fair, in Harry's says that creating

    UI components in Rails would offer a decent abstraction, if it weren't so cumbersome.
  156. http://tachyons.io Tachyons Modular, like Lodash. Get just what you need.

  157. http://jxnblk.com/gravitons/ Much smaller focus but also really great, if you

    have small needs. Also, follow Brent Jaxon. He does great work.
  158. MINIONS.CSS To call this a library would be a gross

    overstatement.. It's a thought experiment that we use in production.
  159. https://github.com/chantastic/minions.css/ Here's how it differs.

  160. "GUESSABLE" NAMING I want the classes to be guessable. You

    shouldn't have to memorize new class-names to use a library like this. Most classes should dehydrate to classes in a very predictable way.
  161. padding-right: 1rem A rule like this...

  162. .pr-1r dehydrates to this class selector.

  163. padding-right: 1rem clearly representing the underlying implementation.

  164. WHATEVER-FIRST We have a 10 year-old-app. We can't just "mobile

    first" it. So we use a range of selector types that allow us to retrofit "desktop first" apps.
  165. .pr-1r@sm ADDITIVE MATCH

  166. .pr-1r@!sm REVERSE-ADDITIVE MATCH

  167. .pr-1r@=sm EXACT MATCH

  168. .pr-1r@!=sm EXCLUDE MATCH

  169. THE PURPOSE OF DESIGN IS TO ALLOW YOU TO DO

    DESIGN LATER, AND IT'S PRIMARY GOAL IS TO REDUCE THE COST OF CHANGE. Sandi Metz, Practical Object Oriented Design in Ruby I can't make it through a talk without a Sandy Metz quote... I want to change this component in one place. I want to rename this component in one place. I want to remove this component in one place.
  170. ⛄ WE STILL NEED ABSTRACTIONS Harry Roberts was Right. We

    still need abstractions for our Rails App. Everything that Harry said is wrong about this approach is still a problem in our Rails apps.
  171. CSS MODULES At this point in the talk, I'm talking

    about things we're not using but I'd like to. I stole these examples from Glen Maddern's talk on CSS Modules. So, they should be pretty close to accurate. CSS Modules is an experimental perspective of the future. There's still a lot to be figured out. But it's headed in an interesting direction.
  172. /* button.css */ .default { color: #111 } Here's a

    very generically named button style.
  173. /* button.css */ .default { color: #111 } /* ICSS

    */ :export { default: default_f34f7fa0; } .default_f34f7fa0 { color: #111 } This is what it gets transformed into through CSS Modules. It creates a globally available class name with your chosen classname, appended with a unique id. It also creates an export statement mapping the name you know as a key to the unique global.
  174. /* ICSS */ :export { default: default_f34f7fa0; } .default_f34f7fa0 {

    color: #111 } /* MyButton.jsx */ import styles from './button.css' You import this via loader into your component...
  175. default: default_f34f7fa0; } .default_f34f7fa0 { color: #111 } /* MyButton.jsx

    */ import styles from './button.css' const MyButton (props) => ( <button className={styles.default}> Default </button> ); And use the key as your classname.
  176. import styles from './button.css' const MyButton (props) => ( <button

    className={styles.default}> Default </button> ); /* rendered output */ <button class="default_f34f7fa0"> Default </button> When the component is rendered, it's rendered with the unique class.
  177. /* button.css */ .base { border-radius: 3px; } .normal {

    composes: base; background-color: blue; } .error { composes: base; background-color: red; } They've also introduced this `composes` API.
  178. /* button.css */ .base { border-radius: 3px; } .normal {

    composes: base; background-color: blue; } .error { composes: base; background-color: red; } If you've spent much time in Sass your immediate inclination might lead you to think of this like @extend, clobbering the the `.base` definition with an onslaught of additional selectors. It doesn't. Let's step through what it actually does...
  179. background-color: red; } /* ICSS */ :export { base: base_81f12d56;

    normal: base_81f12d56 normal_f3457fa0; error: base_81712d56 error_b7d2ad6f; } .base_81f12d56 { border-radius: 3px; } .normal_f34f7fa0 { background-color: blue; } .error_b7d2ad6f { background-color: red; } Again, CSS Modules transforms the file to ICSS which includes a map of the known keys to unique classnames.
  180. background-color: red; } /* ICSS */ :export { base: base_81f12d56;

    normal: base_81f12d56 normal_f3457fa0; error: base_81712d56 error_b7d2ad6f; } .base_81f12d56 { border-radius: 3px; } .normal_f34f7fa0 { background-color: blue; } .error_b7d2ad6f { background-color: red; } Now unlike @extend, it exports all of the rules in isolation. So, the magic must exist in the export...
  181. background-color: red; } /* ICSS */ :export { base: base_81f12d56;

    normal: base_81f12d56 normal_f3457fa0; error: base_81712d56 error_b7d2ad6f; } .base_81f12d56 { border-radius: 3px; } .normal_f34f7fa0 { background-color: blue; } .error_b7d2ad6f { background-color: red; } Here, we see that the `normal` and `error` keys are mapping to not 1 class but two. It's extending by applying both classes.
  182. .base_81f12d56 { border-radius: 3px; } .normal_f34f7fa0 { background-color: blue; }

    .error_b7d2ad6f { background-color: red; } /* rendered output */ <button class="base_81f12d56 error_b7d2ad6f"> Error! </button> This results in rendered output like this, where bath `error` and the class it `composes` are applied. This is brilliant.
  183. .flex-stack { composes: .d-f .f_1 .df-c .fd-r\@md from './minions_.css'; }

    It means that we can abstract without side-effects. I get giddy when I think about using technology like this in our applications. It's available for those of you using JSPM, Browserify, and Webpack. I recommend using it.
  184. WE SUCK AT PREDICTING THE FUTURE I can't predict the

    future. I can only hope that it's something along the lines of CSS Modules. Until then... I want to build abstractions that build into the future.
  185. G INLINE STATE STYLES Your components are fragile. Fix them.

    Test them.
  186. EMBRACE IMMUTABLE SELECTORS The best arguments no longer apply in

    our component driven apps.
  187. ☠ STOP USING JS AND CSS TO BUILD THE SAME

    ABSTRACTION If you've modularized well enough, You're likely not actually getting re-use from your CSS
  188. HOPE FOR ABSTRACTIONS WITHOUT SIDE EFFECTS HOPE Abstract without global

    available side effects
  189. BE PREPARED WHEN THE FUTURE ARRIVES

  190. @CHANTASTIC Thanks you listening. I'm @chantastic, if you want to

    tell me how batshit crazy I am. Thank you.