| 1 | # PostCSS Is Pseudo [<img src="https://postcss.github.io/postcss/logo.svg" alt="PostCSS" width="90" height="90" align="right">][postcss]
|
|---|
| 2 |
|
|---|
| 3 | [![NPM Version][npm-img]][npm-url]
|
|---|
| 4 | [![CSS Standard Status][css-img]][css-url]
|
|---|
| 5 | [<img alt="Build Status" src="https://github.com/csstools/postcss-plugins/workflows/test/badge.svg" height="20">][cli-url]
|
|---|
| 6 | [<img alt="Discord" src="https://shields.io/badge/Discord-5865F2?logo=discord&logoColor=white">][discord]
|
|---|
| 7 |
|
|---|
| 8 | [PostCSS Is Pseudo Class] lets you use the `:is` pseudo class function, following the
|
|---|
| 9 | [CSS Selector] specification.
|
|---|
| 10 |
|
|---|
| 11 | ```pcss
|
|---|
| 12 | :is(input, button):is(:hover, :focus) {
|
|---|
| 13 | order: 1;
|
|---|
| 14 | }
|
|---|
| 15 | ```
|
|---|
| 16 |
|
|---|
| 17 | Becomes :
|
|---|
| 18 |
|
|---|
| 19 | ```pcss
|
|---|
| 20 | input:hover {
|
|---|
| 21 | order: 1;
|
|---|
| 22 | }
|
|---|
| 23 | input:focus {
|
|---|
| 24 | order: 1;
|
|---|
| 25 | }
|
|---|
| 26 | button:hover {
|
|---|
| 27 | order: 1;
|
|---|
| 28 | }
|
|---|
| 29 | button:focus {
|
|---|
| 30 | order: 1;
|
|---|
| 31 | }
|
|---|
| 32 | ```
|
|---|
| 33 |
|
|---|
| 34 | ## Usage
|
|---|
| 35 |
|
|---|
| 36 | Add [PostCSS Is Pseudo Class] to your project:
|
|---|
| 37 |
|
|---|
| 38 | ```bash
|
|---|
| 39 | npm install @csstools/postcss-is-pseudo-class --save-dev
|
|---|
| 40 | ```
|
|---|
| 41 |
|
|---|
| 42 | Use [PostCSS Is Pseudo Class] as a [PostCSS] plugin:
|
|---|
| 43 |
|
|---|
| 44 | ```js
|
|---|
| 45 | import postcss from 'postcss';
|
|---|
| 46 | import postcssIsPseudoClass from '@csstools/postcss-is-pseudo-class';
|
|---|
| 47 |
|
|---|
| 48 | postcss([
|
|---|
| 49 | postcssIsPseudoClass(/* pluginOptions */)
|
|---|
| 50 | ]).process(YOUR_CSS /*, processOptions */);
|
|---|
| 51 | ```
|
|---|
| 52 |
|
|---|
| 53 | [PostCSS Is Pseudo Class] runs in all Node environments, with special instructions for:
|
|---|
| 54 |
|
|---|
| 55 | | [Node](INSTALL.md#node) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) |
|
|---|
| 56 | | --- | --- | --- | --- | --- |
|
|---|
| 57 |
|
|---|
| 58 | ## Options
|
|---|
| 59 |
|
|---|
| 60 | ### preserve
|
|---|
| 61 |
|
|---|
| 62 | The `preserve` option determines whether the original notation
|
|---|
| 63 | is preserved. By default, it is not preserved.
|
|---|
| 64 |
|
|---|
| 65 | ```js
|
|---|
| 66 | postcss([
|
|---|
| 67 | postcssIsPseudoClass({ preserve: true })
|
|---|
| 68 | ]).process(YOUR_CSS /*, processOptions */);
|
|---|
| 69 | ```
|
|---|
| 70 |
|
|---|
| 71 | ```pcss
|
|---|
| 72 | :is(input, button):is(:hover, :focus) {
|
|---|
| 73 | order: 1;
|
|---|
| 74 | }
|
|---|
| 75 | ```
|
|---|
| 76 |
|
|---|
| 77 | Becomes :
|
|---|
| 78 |
|
|---|
| 79 | ```pcss
|
|---|
| 80 | input:hover {
|
|---|
| 81 | order: 1;
|
|---|
| 82 | }
|
|---|
| 83 | input:focus {
|
|---|
| 84 | order: 1;
|
|---|
| 85 | }
|
|---|
| 86 | button:hover {
|
|---|
| 87 | order: 1;
|
|---|
| 88 | }
|
|---|
| 89 | button:focus {
|
|---|
| 90 | order: 1;
|
|---|
| 91 | }
|
|---|
| 92 | :is(input, button):is(:hover, :focus) {
|
|---|
| 93 | order: 1;
|
|---|
| 94 | }
|
|---|
| 95 | ```
|
|---|
| 96 |
|
|---|
| 97 | ### specificityMatchingName
|
|---|
| 98 |
|
|---|
| 99 | The `specificityMatchingName` option allows you to change to selector used to adjust specificity.
|
|---|
| 100 | The default value is `does-not-exist`.
|
|---|
| 101 | If this is an actual class, id or tag name in your code, you will need to set a different option here.
|
|---|
| 102 |
|
|---|
| 103 | See how `:not` is used to modify [specificity](#specificity).
|
|---|
| 104 |
|
|---|
| 105 | ```js
|
|---|
| 106 | postcss([
|
|---|
| 107 | postcssIsPseudoClass({ specificityMatchingName: 'something-random' })
|
|---|
| 108 | ]).process(YOUR_CSS /*, processOptions */);
|
|---|
| 109 | ```
|
|---|
| 110 |
|
|---|
| 111 | ```pcss
|
|---|
| 112 | :is(.button, button):hover {
|
|---|
| 113 | order: 7;
|
|---|
| 114 | }
|
|---|
| 115 | ```
|
|---|
| 116 |
|
|---|
| 117 | Becomes :
|
|---|
| 118 |
|
|---|
| 119 | ```pcss
|
|---|
| 120 | .button:hover {
|
|---|
| 121 | order: 7;
|
|---|
| 122 | }
|
|---|
| 123 |
|
|---|
| 124 | button:not(.something-random):hover {
|
|---|
| 125 | order: 7;
|
|---|
| 126 | }
|
|---|
| 127 | ```
|
|---|
| 128 |
|
|---|
| 129 | ### onComplexSelector
|
|---|
| 130 |
|
|---|
| 131 | Warn on complex selectors in `:is` pseudo class functions.
|
|---|
| 132 |
|
|---|
| 133 | ```js
|
|---|
| 134 | postcss([
|
|---|
| 135 | postcssIsPseudoClass({ onComplexSelector: 'warning' })
|
|---|
| 136 | ]).process(YOUR_CSS /*, processOptions */);
|
|---|
| 137 | ```
|
|---|
| 138 |
|
|---|
| 139 | ### onPseudoElement
|
|---|
| 140 |
|
|---|
| 141 | Warn when pseudo elements are used in `:is` pseudo class functions.
|
|---|
| 142 |
|
|---|
| 143 | ⚠️ Pseudo elements are always invalid and will be transformed to `::-csstools-invalid-<pseudo-name>`.
|
|---|
| 144 |
|
|---|
| 145 | ```js
|
|---|
| 146 | postcss([
|
|---|
| 147 | postcssIsPseudoClass({ onPseudoElement: 'warning' })
|
|---|
| 148 | ]).process(YOUR_CSS /*, processOptions */);
|
|---|
| 149 | ```
|
|---|
| 150 |
|
|---|
| 151 | ```css
|
|---|
| 152 | :is(::after):hover {
|
|---|
| 153 | order: 1.0;
|
|---|
| 154 | }
|
|---|
| 155 |
|
|---|
| 156 | /* becomes */
|
|---|
| 157 |
|
|---|
| 158 | ::-csstools-invalid-after:hover {
|
|---|
| 159 | order: 1.0;
|
|---|
| 160 | }
|
|---|
| 161 | ```
|
|---|
| 162 |
|
|---|
| 163 | ## ⚠️ Known shortcomings
|
|---|
| 164 |
|
|---|
| 165 | ### Specificity
|
|---|
| 166 |
|
|---|
| 167 | `:is` takes the specificity of the most specific list item.
|
|---|
| 168 | We can increase specificity with `:not` selectors, but we can't decrease it.
|
|---|
| 169 |
|
|---|
| 170 | Converted selectors are ensured to have the same specificity as `:is` for the most important bit.
|
|---|
| 171 | Less important bits can have higher specificity that `:is`.
|
|---|
| 172 |
|
|---|
| 173 | Before :
|
|---|
| 174 |
|
|---|
| 175 | [specificity: 0, 2, 0](https://polypane.app/css-specificity-calculator/#selector=%3Ais(%3Ahover%2C%20%3Afocus)%3Ais(.button%2C%20button))
|
|---|
| 176 |
|
|---|
| 177 | ```pcss
|
|---|
| 178 | :is(:hover, :focus):is(.button, button) {
|
|---|
| 179 | order: 7;
|
|---|
| 180 | }
|
|---|
| 181 | ```
|
|---|
| 182 |
|
|---|
| 183 | After :
|
|---|
| 184 |
|
|---|
| 185 | ```pcss
|
|---|
| 186 | /* specificity: [0, 2, 0] */
|
|---|
| 187 | .button:hover {
|
|---|
| 188 | order: 7;
|
|---|
| 189 | }
|
|---|
| 190 |
|
|---|
| 191 | /* specificity: [0, 2, 1] */
|
|---|
| 192 | /* last bit is higher than it should be, but middle bit matches */
|
|---|
| 193 | button:not(.does-not-exist):hover {
|
|---|
| 194 | order: 7;
|
|---|
| 195 | }
|
|---|
| 196 |
|
|---|
| 197 | /* specificity: [0, 2, 0] */
|
|---|
| 198 | .button:focus {
|
|---|
| 199 | order: 7;
|
|---|
| 200 | }
|
|---|
| 201 |
|
|---|
| 202 | /* specificity: [0, 2, 1] */
|
|---|
| 203 | /* last bit is higher than it should be, but middle bit matches */
|
|---|
| 204 | button:not(.does-not-exist):focus {
|
|---|
| 205 | order: 7;
|
|---|
| 206 | }
|
|---|
| 207 | ```
|
|---|
| 208 |
|
|---|
| 209 | ### Complex selectors
|
|---|
| 210 |
|
|---|
| 211 | Before :
|
|---|
| 212 |
|
|---|
| 213 |
|
|---|
| 214 | ```pcss
|
|---|
| 215 | :is(.alpha > .beta) ~ :is(:focus > .beta) {
|
|---|
| 216 | order: 2;
|
|---|
| 217 | }
|
|---|
| 218 | ```
|
|---|
| 219 |
|
|---|
| 220 | After :
|
|---|
| 221 |
|
|---|
| 222 | ```pcss
|
|---|
| 223 | .alpha > .beta ~ :focus > .beta {
|
|---|
| 224 | order: 2;
|
|---|
| 225 | }
|
|---|
| 226 | ```
|
|---|
| 227 |
|
|---|
| 228 | _this is a different selector than expected as `.beta ~ :focus` matches `.beta` followed by `:focus`._<br>
|
|---|
| 229 | _avoid these cases._<br>
|
|---|
| 230 | _writing the selector without `:is()` is advised here_
|
|---|
| 231 |
|
|---|
| 232 | ```pcss
|
|---|
| 233 | /* without is */
|
|---|
| 234 | .alpha:focus > .beta ~ .beta {
|
|---|
| 235 | order: 2;
|
|---|
| 236 | }
|
|---|
| 237 | ```
|
|---|
| 238 |
|
|---|
| 239 | If you have a specific pattern you can open an issue to discuss it.
|
|---|
| 240 | We can detect and transform some cases but can't generalize them into a single solution that tackles all of them.
|
|---|
| 241 |
|
|---|
| 242 | [cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
|
|---|
| 243 | [css-img]: https://cssdb.org/images/badges/is-pseudo-class.svg
|
|---|
| 244 | [css-url]: https://cssdb.org/#is-pseudo-class
|
|---|
| 245 | [discord]: https://discord.gg/bUadyRwkJS
|
|---|
| 246 | [npm-img]: https://img.shields.io/npm/v/@csstools/postcss-is-pseudo-class.svg
|
|---|
| 247 | [npm-url]: https://www.npmjs.com/package/@csstools/postcss-is-pseudo-class
|
|---|
| 248 |
|
|---|
| 249 | [CSS Selector]: https://www.w3.org/TR/selectors-4/#matches
|
|---|
| 250 | [PostCSS]: https://github.com/postcss/postcss
|
|---|
| 251 | [PostCSS Is Pseudo Class]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-is-pseudo-class
|
|---|