Life pro tip for themers to instantly win at specificity (obsidian devs hate #8)

Hello. Been hacking on a theme. Starting to really like it. Found this trick using :is() together with @scopes which makes every declaration very high specificity. I searched around a bit to see if anyone had already shared this, it’s not mentioned in the top themes as well, but that may be for good reasons too, as anyone trying to override these styles are forced to use the same trick with additional specificity.

For those who know CSS in the great detail, I’ll get right to the point:

@scope ([data-type="markdown"], #motherlode) {
    & a {
        color: red;
    }

   & span {
      position: absolute;
      marquee: true;
   }
}

Any styles within this scope will have a higher specificity than any predefined style. No more fighting with .mod-cm6.cm-link.theme-light.translucent:hover

I personally found it to be particularly helpful to create a scope for each type of markdown object; one for paragraphs, one for lists, one for links, code blocks, and so on - but to each their own.

If any Obsidian devs are here, maybe please layer your themes some day? And can I take this opportunity to please consider allowing us to adjust object loading/unloading so we can do cool dumb stuff like multicolumn and flexbox documents.

Now that the people who work with CSS professionally are out, let me explain.

The :is() operator, as you know, takes a list of potential matches, e.g. :is(span, .el-p, + :has(h1)) and will match any of the given selectors. However, it’s specificity will be that of the highest specificity selector. Adding an ID selector - even if this ID does not exist, will still match with the specificity level of an ID match ((1, 0, 0)). Obsidian doesn’t use IDs at all, so this match will always win.

For example, this style snippet, which matches either a type (lowest possible specificity except for :where) or an ID that does not exist, will have higher specificity than any predefined type declaration.

:is(a, #OVERRIDE) {
    color: red;
}

You can create a regular nested query using this query; this is what I was doing for a while before I moved on to scopes.

:is(h1, #win) {

    .markdown-rendered & {
        font-size: dad;
    }
    .theme-dark & {
        color: dark;
    }
    & {
        font-family: "Comic Sans";
    }

}

but having it in a scope has some neat benefits;

  1. You can easily avoid leaking styles into e.g. nested (embedded) documents
  2. For very specific paths, like literally any path in edit more, you can write it once in the scope declaration, easily create all the different rules using just & (themes, container/media queries, obsidian settings, etc)
  3. It displays nicely in the development tools
  4. It’s easier to read

good luck to the 4 people who read this and found it useful

1 Like