|
| 1 | +# CSS Layers |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +| Stage | Status | |
| 6 | +| -------- | ------ | |
| 7 | +| Approved | 🚧 | |
| 8 | +| Adopted | 🚧 | |
| 9 | + |
| 10 | +## Context |
| 11 | + |
| 12 | +During our transition to CSS Modules, we made use of the `:where()` pseudo-class |
| 13 | +to help create stable selector specificity. Now that teams could provide a |
| 14 | +`className` to a component we needed to ensure that selectors that we used in a |
| 15 | +component had matching specificity to the given class name, otherwise we would |
| 16 | +run into the risk of accidentally shipping a breaking change. |
| 17 | + |
| 18 | +For example, consider setting a `color` on a component: |
| 19 | + |
| 20 | +```css |
| 21 | +/* v1.0.0 of a component */ |
| 22 | +.Component { |
| 23 | + color: var(--fgColor-default); |
| 24 | +} |
| 25 | + |
| 26 | +/* The override class placed on the same element as the component class */ |
| 27 | +.override { |
| 28 | + color: var(--fgColor-muted); |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +Now, in a future version of the component, we increase the specificity of the |
| 33 | +selector: |
| 34 | + |
| 35 | +```css |
| 36 | +/* v1.1.0 of a component */ |
| 37 | +.Component[data-variant='default'] { |
| 38 | + color: var(--fgColor-default); |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +With this change, the `override` class will no longer override the color as it |
| 43 | +did in the previous version. |
| 44 | + |
| 45 | +To mitigate this, we can use the `:where()` pseudo-class to limit the |
| 46 | +specificity of our selectors to `0,1,0`, for example: |
| 47 | + |
| 48 | +```css |
| 49 | +.Component:where([data-variant='default']) { |
| 50 | + color: var(--fgColor-default); |
| 51 | +} |
| 52 | + |
| 53 | +/* Or with nesting: */ |
| 54 | +.Component { |
| 55 | + &:where([data-variant='default']) { |
| 56 | + color: var(--fgColor-default); |
| 57 | + } |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +However, this approach requires us to use the `:where()` pseudo-class in every |
| 62 | +selector where we would like to have stable specificity. This can lead to issues |
| 63 | +where you may forget to use the `:where()` pseudo-class in a selector, or where |
| 64 | +you are not sure if you need to use it or not so you add it everywhere. |
| 65 | + |
| 66 | +## Decision |
| 67 | + |
| 68 | +Instead of making use of the `:where()` pseudo-class in every selector, we will |
| 69 | +use a CSS layer to create a stable selector specificity. This will allow us to |
| 70 | +author our CSS without having to worry about the specificity of each selector. |
| 71 | + |
| 72 | +Our example from earlier would now look like: |
| 73 | + |
| 74 | +```css |
| 75 | +@layer primer.components.component { |
| 76 | + .Component[data-variant='default'] { |
| 77 | + color: var(--fgColor-default); |
| 78 | + } |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +Using a CSS layer in this way allows us to author our CSS without having to |
| 83 | +consider whether or not to use `:where()`. This is due to the nature of CSS |
| 84 | +layers where if two layers are targetting the same element, the last layer |
| 85 | +targetting that element will take precedence. |
| 86 | + |
| 87 | +Since we are moving our CSS into a CSS layer, this means that later layers (in |
| 88 | +particularly the anonymous layer) will take precedence when two rules are |
| 89 | +matching the same element. For example: |
| 90 | + |
| 91 | +```css |
| 92 | +@layer primer.components.component { |
| 93 | + .Component[data-variant='default'] { |
| 94 | + color: var(--fgColor-default); |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +.override { |
| 99 | + color: var(--fgColor-muted); |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +It's important to note that even though the `override` class has lower |
| 104 | +specificity, it will still take precedence over the |
| 105 | +`.Component[data-variant='default']` due to how CSS layers work. |
| 106 | + |
| 107 | +### Impact |
| 108 | + |
| 109 | +This decision will impact our component styles that are authored with CSS |
| 110 | +Modules. For each file, we will need to update the module to be wrapped in a CSS |
| 111 | +layer. This layer will be named following the convention: |
| 112 | +`primer.components.<component-name>` |
| 113 | + |
| 114 | +In addition, selectors that use `:where()` may now be refactored to no longer |
| 115 | +use the `:where()` pseudo-class. |
| 116 | + |
| 117 | +## Unresolved questions |
| 118 | + |
| 119 | +- Is it possible for stylelint to have a rule to guarantee the presence of a CSS |
| 120 | + layer in a CSS Module file for a component? |
0 commit comments