Hacker News new | ask | show | jobs
by aleem 3958 days ago
It's wonderful that CSS problems have been distilled so clearly, it's been a long time coming. Radium[1] is really worth checking out, it's simple and clear.

From the article:

  /* BEM */
  .normal { /* all styles for Normal */ }
  .button--disabled { /* overrides for Disabled */ }
  .button--error { /* overrides for Error */ }
  .button--in-progress { /* overrides for In Progress */

  /* CSS MODULES */
  .normal { /* all styles for Normal */ }
  .disabled { /* all styles for Disabled */ }
  .error { /* all styles for Error */ }
  .inProgress { /* all styles for In Progress */

  > In CSS Modules each class should have all the styles needed for that variant
This seems like trading one set of problems for another. It violates DRY and consequently impacts code maintainability. The corollary is that .disabled should @include .normal and then define the overrides so you only have to define the base styles once in .normal... but that also requires discipline and code wrangling.

  > [BEM] requires an awful lot of cognitive effort around naming discipline.
But the proposed alternative is not too different either:

  /* components/submit-button.css */
  .error { ... }
  .inProgress { ... }
This example just replaces BEM naming and namespacing with file naming and directory structure.

[1]: https://github.com/FormidableLabs/radium

EDIT: On second reading, the stuff in there is definitely not "Welcome to the future" exploring the "adjacent possible" realm--those are quite grandoise pretexts to a solution that is just as unwieldy.

4 comments

Regarding your first point, he addresses that in the next section:

"The composes keyword says that .normal includes all the styles from .common, much like the @extends keyword in Sass. But while Sass rewrites your CSS selectors to make that happen, CSS Modules changes which classes are exported to JavaScript."

And isn't file (module) naming and directory structure what we use to organize regular code? I like the idea of going to the components folder and looking for the 'submit button' file when I need to alter something much more than searching a 20,000 line css file or randomly split scss files. Even nicer is the concept of including your styles in the same directory as the components they define, further mirroring normal code organization. It seems like this would allow for much easier onboarding into a project.

I am not saying it's a bad idea. However, it trades one set of problems for another. What point is there in highlighting a problem and then proposing a remedy which introduces the same problems in a different form?

> I like the idea of going to the components folder and looking for the 'submit button'

All existing frameworks use the same approach (Bootstrap, Foundation, et al). GetMDL.io for example uses BEM and clean files to organize code https://github.com/google/material-design-lite/tree/master/s...

Dealing with that on any reasonably sized project still requires "naming discipline" and "cognitive effort", both of which the author highlights as problems ailing BEM with a remedy that still suffer from the same. It didn't read very convincingly.

> The corollary is that .disabled should @include .normal and then define the overrides so you only have to define the base styles once in .normal... but that also requires discipline and code wrangling.

If I'm using .btn-disabled everywhere, then its hard to be to write a CSS rule that says 'whenever a button follows another button, apply this style to it'. That's why we have this competition with BEM and `.btn .disabled`.

> This example just replaces BEM naming and namespacing with file naming and directory structure.

...Yes. That's the point.

The problem is that CSS is a global 'language', and everything that you write has the chance to impact all your other CSS. When I'm writing a particular component, there's no way to write CSS that's local to just that component.

We have things like BEM that is about establishing a _convention_ to write Pseudo-Local-CSS, but nothing is enforced. Everything is still global. 'CSS Modules' enforces your CSS to be 'local only' as a file-level, just like regular (Node) JS.

It's like how there's a convention of not adding a string to an integer, but adding a type system to your programming language enforces that on a technical, contractual level.

Except as far as I can tell, with Radium you get zero support for pseudo classes and media queries until after the client-side JavaScript has downloaded and been initialised. This is acceptable if it's purely a client-side app, but if you're using an kind of server-side rendering it's clearly not good enough.

I really don't want a lack of hover or focus effects on initial page load, and I really don't want my entire page layout to change once the JavaScript-powered media queries kick in.

Radium is simple, for that I like it. Complexity comes with a price and there may be alternatives but Radium leverages JS expressions which I like. Quoting from the article again:

  /* components/submit-button.jsx */
  import { Component } from 'react';
  import styles from './submit-button.css';
  
  export default class SubmitButton extends Component {
    render() {
      let className, text = "Submit"
      if (this.props.store.submissionInProgress) {
        className = styles.inProgress
        text = "Processing..."
      } else if (this.props.store.errorOccurred) {
        className = styles.error
      } else if (!this.props.form.valid) {
        className = styles.disabled
      } else {
        className = styles.normal
      }
      return <button className={className}>{text}</button>
    }
  }

That looks unwieldy -- 4 if/else clauses just to deal with CSS. Contrast that with the example in "Usage" over at the Radium page https://github.com/FormidableLabs/radium -- much simpler.
If we were to mirror the approach of the Radium example, we'd actually end up with something more like this:

  import React from 'React';
  import styles from './SubmitButton.css';

  class SubmitButton extends Component {
    defaultProps = {
      kind: 'default'
    }
    
    render() {
      const { kind } = this.props;
      const text = kind === 'in-progress' ? 'Processing...' : 'Submit';
      
      return <button className={styles[kind]}>{text}</button>
    }
  }
A lot of the latest thinking in (non-JS) CSS suggests that violating DRY is an excellent idea. Copy and pasting CSS rules is cheap, but maintaining and adapting a large complex CSS project that's blowing up in a team's face can be very expensive.