'What's the 'correct' way to override styles from parent when using CSS Modules?

I am using NextJS with CSS Modules to manage styles.

I would like to modify the appearance of a component when it is:

  1. Rendered normally on a page, using props to specify modifiers for className.
  2. Rendered within a specific parent component
  3. Rendered within a specific parent component as props.children.

I have a working solution for all of the above, but I'd like to know if any of these techniques are considered bad practice, or if there is a better alternative I should be considering (Particularly for scenario 3).

Using a simple Button component as an example:

Scenario 1:

button.module.scss

.button {
   color: 'black';
}

.red {
   color: 'red';
}

.blue {
   color: 'blue';
}

Button.jsx

import styles from './button.module.scss';

const Button = (props) => {

   const classNames = [styles.button];

   props.modifiers.map((modifier) => {
      classNames.push(styles[modifier]);
   });

   return (
      <button className={classNames.join(' ')}>{props.text}</button>
   );
}

export default Button;

Page.jsx

<Button text={`Test`}/>
<Button text={`Test`} modifiers={[`red`]}/>
<Button text={`Test`} blue={[`red`]}//>

Scenario 2:

This time, I need to change the styling of the Button component when it appears within ParentComponent (sometimes combined with the previous technique).

parent-component.module.scss

.button {
   background-color: 'green';
}

ParentComponent.jsx

import styles from './parent-component.module.scss';
import Button from 'components/button/Button.jsx';

const ParentComponent = (props) => {
   return (
      <>
         <Button text={`Test`} modifiers={[styles.button}>
         <Button text={`Test`} modifiers={[`red`, styles.button]}>
      </>
   );
}

Button.jsx has to be updated to deal with this

props.modifiers.map((modifier) => {
   // The modifier doesn't exist in the local styles, so we apply it directly
   classNames.push(styles[modifier] || modifier);
});

Scenario 3:

ParentComponent.jsx

import styles from './parent-component.module.scss';

const ParentComponent = (props) => {
   return (
      <div>{props.children}</div>
   );
}

// Expose styles on the exported component, so we can refer to it in page.jsx
ParentComponent.styles = styles;

export default ParentComponent;

Page.jsx

<ParentComponent>
   <Button text={`Test`} modifiers={[ParentComponent.styles.button]}>
</ParentComponent>

I'm coming from a BEM background, where I'd normally use something like this to solve this issue: (Source https://css-tricks.com/using-sass-control-scope-bem-naming/)

.component {
  $self: &;
  display: block;
  max-width: 30rem;
  min-height: 30rem;
  
  &--reversed {
    background: white;
    border-color: lightgray;
    
    // Here, we use $self to get the correct scope
    #{ $self }__child-element {
      background: rebeccapurple;
    }
  }
}

Any advice appreciated.



Solution 1:[1]

Use the npm package classnames.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Alex Mckay