Skip to content

:where(.lexxy-content) doesn't actually keep specificity low - nested rules block consumer overrides #1000

@lylo

Description

@lylo

Summary

lexxy-content.css wraps the outer container in :where(.lexxy-content), which suggests styles are intentionally low-specificity and easy to override. But CSS nesting only zeroes out the outer wrapper – nested selectors keep their full specificity, so any rule that mentions a class still resolves at (0,1,1) or higher. The result is that simple consumer selectors like article img { ... } lose to lexxy's defaults.

Repro

Consumer custom CSS:

article img {
  border-radius: 0;
}

This has no effect on images inside posts, because of the Lexxy rule:

:where(.lexxy-content) {
  .attachment--preview {
    img, video {
      border-radius: var(--lexxy-radius);
    }
  }
}

This Lexxy rule compiles to :where(.lexxy-content) .attachment--preview img with specificity (0,1,1), which beats article img (0,0,2).

The same problem affects every nested .attachment*, code, pre, etc. – anything where consumers might reasonably want to write a low-specificity override.

The outer :where(.lexxy-content) wrapping reads as a deliberate signal: "we don't want our specificity to fight you." In practice, the moment any nested selector mentions a class, that signal evaporates. Consumers writing simple element selectors against article (the natural way to scope blog post styles) will silently lose every battle without realising why.

Suggested fixes (either would work)

Option 1. Wrap nested selectors in :where() too

Inside :where(.lexxy-content), change patterns like:

.attachment--preview {
  img, video { ... }
}

to:

:where(.attachment--preview) {
  :where(img, video) { ... }
}

This pushes practical specificity down to the bare element selectors, so article img wins.

Option 2. Put content styles in @layer (preferred)

Wrap the content stylesheet in a layer:

@layer lexxy {
  /* contents of lexxy-content.css */
}

Unlayered styles always win over layered ones, regardless of specificity, so consumer CSS overrides "just work" without any specificity gymnastics.

Pro: modern, idiomatic for CSS libraries (Tailwind, Open Props, Pico, etc. all do this), self-documenting, future-proof.
Con: shifts the library's cascade model - anyone currently relying on lexxy's specificity to override their own unlayered styles would be affected.

Option 2 is what :where() seems to imply but doesn't actually deliver for nested rules. Worth considering an opt-in flag (e.g. @layer lexxy.content { ... }) if you want to introduce it without breaking existing consumers.


Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions