Skip to content

@morev/bem/no-detached-entity-extensions ​

Requires extensions of a BEM entity selector to be declared within that entity.

An entity extension is selector material added to a complete BEM entity selector without introducing a relational target.

scss
.block {
  &__title {}

  &__title:hover {} 
  &__title::placeholder {} 
  &__title[aria-current='true'] {} 
  &__title.is-active {} 

  &__title--active {} 
  &__title--theme--dark {} 
}
scss
.block {
  &__title {
    &:hover {}
    &::placeholder {}
    &[aria-current='true'] {}
    &.is-active {}

    &--active {}
    &--theme--dark {}
  }
}

Motivation ​

Finding a BEM entity should be enough to understand how it looks and behaves. When its extensions are declared separately, its styles are scattered across sibling declarations. Changing or removing the entity then requires searching for every selector that extends it.

Keeping extensions inside their entity gives its local styles one predictable location. You can read its states together, update them alongside the base styles, and remove them with the entity without leaving detached selectors behind.

SCSS modifiers ​

SCSS can concatenate a modifier suffix with &. The rule therefore requires modifiers to be nested within their base block or element:

scss
.block {
  // Block modifiers belong inside the block.
  &--compact {}

  &__button {
    // Element modifiers belong inside the element.
    &--active {}

    // Modifier values can be declared directly inside the base entity.
    &--theme--dark {}

    // Nesting values inside the modifier is also allowed.
    &--theme {
      &--light {}
    }
  }
}

Native CSS ​

Native CSS nesting cannot concatenate a modifier suffix with &. Standalone modifier selectors are therefore allowed:

css
.block__button--active {
  &:hover {}
}

.block__button--theme--dark {}

Extensions that native CSS can express with nesting still have to be nested:

css
.block__button--active:hover {} 
css
.block__button--active {
  &:hover {}
}

Rule options ​

All options are optional and come with recommended default values.

js
export default {
  plugins: ['@morev/stylelint-plugin'],
  rules: {
    '@morev/bem/no-detached-entity-extensions': true,
  },
};
js
export default {
  plugins: ['@morev/stylelint-plugin'],
  rules: {
    '@morev/bem/no-detached-entity-extensions': [true, {
      separators: {
        element: '__',
        modifier: '--',
        modifierValue: '--',
      },
      messages: {
        detached: (extension, owner) =>
          `Move ${extension} into ${owner}.`,
      },
    }],
  },
};
Show full type of the options
ts
type PrimaryOption = true;

type SecondaryOption = {
  /**
   * Custom message functions for rule violations.
   */
  messages?: {
    /**
     * Custom message for a BEM entity extension declared outside its required owner.
     *
     * @param   extension   Complete resolved extension selector.
     * @param   owner       BEM entity that must own the extension.
     *
     * @returns             The error message to report.
     */
    detached?: (extension: string, owner: string) => string;
  };

  /**
   * Object that defines BEM separators used to distinguish blocks, elements, modifiers, and modifier values.
   *
   * @default { element: '__', modifier: '--', modifierValue: '--' }
   */
  separators?: Partial<Separators>;
};
Show info about Stylelint-wide options

Every rule in this plugin also supports the standard Stylelint per-rule options (disableFix, severity, url, reportDisables, and message), even though they are not explicitly reflected in the type definitions to avoid unnecessary noise.


Note: the message option is technically available, but its use is discouraged: each rule already provides a typed messages object, <!-- eslint-disable-line -- Global ID --> which not only offers IDE autocompletion but also supports multiline strings and automatically handles indentation.


For more information, see the official Stylelint configuration docs.


separators ​

The rule supports different naming conventions for BEM entities by allowing you to configure the separators between block elements, modifiers, and modifier values.

This flexibility ensures compatibility with all popular BEM styles described in the official [BEM methodology naming convention][bem-guide] or even custom ones.

Available separators ​

OptionDefaultDescription
element__Separator between block and element.
modifier--Separator between block/element and modifier name.
modifierValue--Separator between modifier name and modifier value.

messages ​

The rule provides built-in error messages for all violations it detects.
You can customize them using the messages option. This can be useful to:

  • Adjust the tone of voice to match your team's style;
  • Translate messages into another language;
  • Provide additional project-specific context or documentation links.

INFO

You don't need to override all message functions — or any of them at all.

Overrides the default detached(extension, owner) message.

  • extension is the complete resolved selector compound;
  • owner is the BEM entity within which that extension must be declared.

Example ​

js
export default {
  plugins: ['@morev/stylelint-plugin'],
  rules: {
    '@morev/bem/no-detached-entity-extensions': [true, {
      messages: {
        detached: (extension, owner) =>
          `⛔ Move "${extension}" into "${owner}".`,
      },
    }],
  },
};

Released under the MIT License.