# Specificity

Source: https://plumeria.dev/docs/reference/specificity





<Steps>
  <Step>
    ## Property priority [#property-priority]

    Plumeria assigns specificity from the shorthand-to-longhand property graph.
    Each step toward a more specific property adds one `:not(#\#)` to its atomic
    class. To simplify the examples, `hash(xxxxxxxx)` is written as `xatom`.

    ### Override only that value [#override-only-that-value]

    This ensures that longhand properties **add to** shorthand properties,
    regardless of the merge order:

    ```js
    const styles = css.create({
      base: { margin: 10 },
      custom: { marginTop: 20 },
    });

    // Both produce the same result
    classStyle={styles.base, styles.custom}
    classStyle={styles.custom, styles.base}

    // Result: margin 10px on all sides, except top is 20px
    ```

    Normally, a later shorthand can reset an earlier longhand. The specificity
    hierarchy prevents that reset, so both of the following produce the same result:

    ```css
    xatom1 {
      padding: 20px;
    }

    xatom2:not(#\#) {
      padding-top: 10px;
    }
    ```

    And this too

    ```css
    xatom2:not(#\#) {
      padding-top: 10px;
    }

    xatom1 {
      padding: 20px;
    }
    ```

    Logical intermediate properties use the intermediate depth. For example:

    ```sh
    padding:             0 - :not(#\#) x 0
    padding-block:       1 - :not(#\#) x 1
    padding-block-start: 2 - :not(#\#) x 2
    ```

    Only the property's graph depth is added. A shorthand does not receive extra
    specificity to simulate the CSS cascade.
  </Step>

  <Step>
    ### Declarations in the same style [#declarations-in-the-same-style]

    Shorthand and longhand declarations remain independent atoms even when they
    are written in the same style. A later shorthand does not filter out an earlier
    longhand:

    ```js
    const styles = css.create({
      base: {
        paddingTop: '20px',
        padding: '10px',
      },
    });
    ```

    The longhand's higher specificity keeps the top value while the shorthand sets
    the remaining sides:

    ```css
    xatom1:not(#\#) {
      padding-top: 20px;
    }

    xatom2 {
      padding: 10px;
    }
    ```

    <Callout title="Enforcing with ESLint">
      These relationships are complex even in regular CSS. The ESLint rule enforces
      a consistent authoring order by placing longhand properties after their
      shorthands.
    </Callout>
  </Step>

  <Step>
    ## `classStyle` prop merge styles [#classstyle-prop-merge-styles]

    Also, when `classStyle` prop has duplicate properties on its array, the style properties on the right always take precedence and duplicate properties on the left are filtered. In that case no filtered hash is generated.

    ```js
    const styles = css.create({
      base: { 
        fontSize: 10,
      },
      custom: { 
        fontSize: 16,
      },
    });

    // The right always wins
    classStyle={styles.base, styles.custom} // 16px
    classStyle={styles.custom, styles.base} // 10px
    ```

    Generated as follows:

    ```css
    xatom {
      font-size: 16px;
    }
    ```

    Or:

    ```css
    xatom {
      font-size: 10px;
    }
    ```
  </Step>

  <Step>
    ## Conditional and nested rules [#conditional-and-nested-rules]

    Conditional rules such as `@media`, `@container`, and `@supports`, as well as
    nested selectors, receive one additional `:not(#\#)`. This lets a conditional
    or nested declaration override its base declaration without relying on where
    the generated rule appears in the stylesheet. A nested selector inside a
    condition receives both.

    ```sh
    base shorthand:                property depth 0
    base longhand:                 property depth
    shorthand in condition:        property depth 0 + condition depth 1
    longhand in condition:         property depth + condition depth 1
    nested selector:               property depth + nested depth 1
    nested selector in condition:  property depth + nested depth 1 + condition depth 1
    ```

    Custom properties are exempt. They never receive a nested or condition depth.

    The optimizer merges identical selectors and identical at-rules recursively to
    reduce bundle size. Supported conditional at-rules are moved after base rules
    so a conditional shorthand can override a base longhand when their specificity
    is equal. A condition that matches a subset of another is then placed after it,
    so the narrower one wins everywhere both of them reach. Whether one condition
    implies another is not decidable in general, so only the comparable shapes are
    read — a `min-`, `max-` or range comparison on `width`, `height`, `inline-size`
    or `block-size`, joined with `and`, against the same media type or the same
    container. Anything else keeps the order it was written in, unless a comparable
    pair leaves no arrangement that also holds it still.
  </Step>

  <Step>
    ## What source order still decides [#what-source-order-still-decides]

    Every pair where one declaration contains the other is settled by specificity, so
    composing styles from different modules gives the same result no matter which
    module the bundler reached first. Four pairs have no containment to rank them by.
    Three of them are left to source order; the fourth is settled by the order the
    styles are composed in.

    ### The same property under two names [#the-same-property-under-two-names]

    `paddingTop` and `paddingBlockStart` are the same computed property in a
    horizontal writing mode, so they receive the same depth and neither can outrank
    the other. Plain CSS resolves this the same way.

    In a vertical writing mode they are two different edges and no longer compete.
    Which one an element renders under is decided at runtime and inherited, so no
    build-time setting can answer it; `no-order-dependent-overlap` reads the pair as
    one property, which is right for a horizontal or a mixed document and wrong only
    where an element is known to be vertical. Writing one spelling and not the other
    removes the question, and `no-logical-properties` or `no-physical-properties`
    enforces that.

    ```js
    const styles = css.create({
      physical: { paddingTop: 4 },
      logical: { paddingBlockStart: 10 },
    });
    ```

    A rule reports and offers the rename; it cannot hold the count at zero, because
    it can be switched off for one developer and does nothing where no linter runs.
    `withoutLogicalProperties` and `withoutPhysicalProperties` make the same choice
    in the bundler, and every file the compiler processes is checked against it —
    [Next.js](/docs/api-reference/plugins/next-plugin) and
    [every other bundler](/docs/api-reference/plugins/unplugin). Run the rule first,
    since it reports every occurrence at once and that is what moving an existing
    codebase needs; turn the build option on once the count has reached zero.

    Both take `{ sizes: true }`, which extends the check from the edges to the
    twelve axis pairs, and both directions at once is a configuration error. Neither
    rewrites anything, for the reason above: where an element is vertical the rename
    is a change of meaning.

    ### Two shorthands that cross [#two-shorthands-that-cross]

    `borderTop` sets three values on one edge. `borderBlockWidth` sets one value on
    two edges. They overlap on `border-top-width`, but neither contains the other, so
    there is nothing to rank them by. Writing the three declarations `borderTop`
    stands for turns the pair into a shorthand and a longhand, which specificity
    does rank; `expand-border-shorthands` does that rewrite.

    ```js
    const styles = css.create({
      edge: { borderTop: '1px solid red' },
      axis: { borderBlockWidth: 5 },
    });
    ```

    ### Two conditions that overlap [#two-conditions-that-overlap]

    Two conditions that can match at the same time produce the same specificity.
    Where one of them matches a subset of the other, the optimizer settles the pair
    by placing the narrower one last, so the result no longer depends on where the
    two declarations were written or which module the compiler reached first.

    ```js
    const styles = css.create({
      medium: { '@media (min-width: 600px)': { color: 'red' } },
      wide: { '@media (min-width: 900px)': { color: 'blue' } },
    });
    ```

    At a width of 1000px both queries apply and `blue` wins, written in either
    order. `no-order-dependent-overlap` still reports the reversed spelling, because
    the source then reads as though `red` would win.

    Two conditions that overlap without either containing the other — `600px` to
    `900px` against `700px` to `1000px` — are left to source order, and so is any
    pair the optimizer cannot compare.

    ### Two states that match at once [#two-states-that-match-at-once]

    Every nested selector receives the same depth, so `:hover` and `:focus` are
    ranked equally and an element that is both has nothing to settle the
    intersection. Two states on one pseudo-element are the same shape, because
    `:hover::before` and `:focus::before` address the same generated box. A state and
    a bare `::before` never compete, and neither do two different pseudo-elements,
    including two arguments of the same one such as `::part(icon)` and
    `::part(label)`.

    ```js
    const styles = css.create({
      hovered: { ':hover': { color: 'red' } },
      focused: { ':focus': { color: 'blue' } },
    });
    ```

    Nothing in the pair ranks it, and nothing can. The property graph ranks by
    containment, and no such relation holds between two states — `:hover` is not a
    more specific description of an element than `:focus`. Composing the two styles
    is what says which one wins:

    ```js
    classStyle={[styles.hovered, styles.focused]} // blue where both hold
    classStyle={[styles.focused, styles.hovered]} // red where both hold
    ```

    The atom from the source written further right receives one more `:not(#\#)`, so
    it wins where both states hold and changes nothing where only one does. That
    weighted atom is a class of its own, which is what lets both arrangements exist
    in one application without reaching each other. Reversing the array reverses the
    winner, and the module the bundler reached first no longer decides anything.

    Only a pair that is otherwise indistinguishable is weighted: the same condition,
    the same pseudo-element, the same property and the same selector specificity.
    Every axis that already decides is left alone — a differing `@media`, `@layer` or
    `@scope`, a longhand against its shorthand, `:host(.active)` against `:hover` —
    and so is an explicit `:hover:focus`, which stays above the weighting.

    Written in one style there is no composition to read, and the intersection falls
    back to source order:

    ```js
    const styles = css.create({
      button: {
        ':hover': { color: 'red' },
        ':focus': { color: 'blue' },
      },
    });
    ```

    `no-order-dependent-overlap` reports that pair. Declare `:hover:focus` if the
    intersection has a value of its own, or split the two into separate styles and
    compose them in the order that should win.

    <Callout title="Keep these in one style">
      Source order is stable inside a single style object, and the ESLint rule keeps
      that order consistent. Splitting one of the first three across two `css.create`
      calls is what makes the result depend on the bundler instead. Two states are the
      exception: splitting them is how the intent is stated.
      `no-order-dependent-overlap` reports all four when they meet in one style,
      the conditions only where one provably matches a subset of the other.
    </Callout>

    ### Removing the first two entirely [#removing-the-first-two-entirely]

    Two optional rules take the room away instead of reporting it. Expanding a
    border shorthand into the three declarations it stands for turns a crossing pair
    into a shorthand and a longhand, and writing one spelling of a property and not
    the other means the same property can never appear twice:

    ```js
    import plumeria from '@plumeria/eslint-plugin';

    export default [
      plumeria.configs.recommended,
      {
        rules: {
          '@plumeria/expand-border-shorthands': 'warn',
          '@plumeria/no-logical-properties': 'warn',
        },
      },
    ];
    ```

    With both on, every pair on this page except the overlapping conditions and the
    two states becomes impossible to write. See
    [Optional rules](/docs/api-reference/plugins/eslint-plugin/optional-rules).
  </Step>
</Steps>
