autoprefixer icon indicating copy to clipboard operation
autoprefixer copied to clipboard

:placeholder-shown is replaced with -moz-placeholder, although this is outdated

Open kliehm opened this issue 8 months ago • 27 comments

Due to a change in autoprefixer in the last two weeks I assume, suddenly :placeholder-shown is replaced with :-moz-placeholder. According to caniuse, this should only be necessary in Firefox < 50, not in current versions.

Although as far as I understand, :-moz-placeholder should be a synonym to ::placeholder, i.e. to style the placeholder, whereas :placeholder-shown is an indicator if the placeholder is visible or not. So if the focus is within the input and the placeholder text disappears, :placeholder-shown is false. That's something different than styling the placeholder.

Our .browserslistrc looks like this, so Firefox < 50 should not be relevant:

> 1% in DE
last 2 versions
not dead

For the time being, I disabled replacement of :placeholder-shown with /* autoprefixer: ignore next */.

kliehm avatar Mar 20 '25 10:03 kliehm

Can you show simple example of input CSS, output and expected output?

ai avatar Mar 20 '25 10:03 ai

cc @Marukome0743

ai avatar Mar 20 '25 10:03 ai

Although as far as I understand, :-moz-placeholder should be a synonym to ::placeholder, i.e. to style the placeholder, whereas :placeholder-shown is an indicator if the placeholder is visible or not

https://caniuse.com/?search=placeholder-shown says that :-moz-placeholder equals to :placeholder-shown.

Also, another argument is that :placeholder-shown and :-moz-placeholder both uses single : (which means it is a state, not [virtual] element).

But we may be wrong. Please show your proofs or arguments.

ai avatar Mar 20 '25 10:03 ai

https://caniuse.com/?search=placeholder-shown says that :-moz-placeholder equals to :placeholder-shown.

Yes, but that's only true for Firefox prior v50. Modern Firefoxes support :placeholder-shown.

Here is a minimal example: show the label only if the placeholder is visible:

HTML: <input placeholder="&nbsp;" /><label>Text</label>

CSS: label { display: none }; input:not(:placeholder-shown) + label { display: block }

In retrospective, I could have achieved the same effect with :empty, but this is how we've done it. It worked until recently. With the replacement it doesn't work as expected anymore in current versions of Firefox:

input:not(:-moz-placeholder) + label { display: block }

I appreciate your great work, that's only a little annoyance. :)

kliehm avatar Mar 20 '25 10:03 kliehm

Oh, so you have a problem only with not()? It is important note.

Can you show current output and expected output (with prefixes)?

ai avatar Mar 20 '25 10:03 ai

Can you show simple example of input CSS, output and expected output?

Here it still works: the search field at the top has a label "what are you looking for" that disappears, when the input receives focus. Also the reset button (the x) only appears when there is text in the input.

https://verwaltung.bund.de/portal/EN

The negative case only appears on dev stages so far.

kliehm avatar Mar 20 '25 11:03 kliehm

  1. I need to show a small isolated example, not the whole website. Sorry, debugging the huge part of code is hard (and it could be an issue in another part than Autoprefixer, maybe you rely on broken behavior).
  2. I need current output and output that it is expected (so the 1 link doesn’t work).

ai avatar Mar 20 '25 11:03 ai

Okay, I made a fiddle: https://codepen.io/kliehm/pen/ZYExNdd

With a current Firefox you can see that :placeholder-shown is not equivalent to :-moz-placeholder.

So in current versions of Firefox autoprefixer shouldn't replace it. I didn't succeed to include different versions of autoprefixer in the fiddle, so I made it static. My apologies that I'm not able to pin it down further, if it's a problem with autoprefixer or browserslist. I don't think it's a change in Firefox, since in version 10.4.21 of autoprefixer it gets replaced with :-moz-placeholder, whereas in version 10.4.20 it didn't.

kliehm avatar Mar 20 '25 13:03 kliehm

  1. This fiddle is also useful, thanks for it.
  2. But fiddle is not actual/expected CSS output. Please, it is really important for me to follow the instruction because it saves my time for maintaince open source project.
  3. Talking about this fiddle. What is expected and real behaviour. Please show the screenshot.

I see that Firefox and Chroma output is exactly the same.

Image

ai avatar Mar 20 '25 14:03 ai

Okay, sorry for the inconvenience. This is my output on Firefox v136.0.2 (Windows). It's interesting that your browser is interpreting the second part exactly opposite from the expected behavior of :-moz-placeholder. I can reproduce your screenshot with Chrome 134.0.6998.89.

The expected behavior is the top two examples with :placeholder-shown, same as your result.

The bottom two examples with :-moz-placeholder show that it's not the same result as :placeholder-shown. Although you get different results, you can see that it's not equivalent.

Image

kliehm avatar Mar 20 '25 14:03 kliehm

@Marukome0743 I could pin it down: it worked with autoprefixer 10.4.20, was broken with autoprefixer 10.4.21. It is this commit.

The problem is that caniuse lists the replacement with :-moz-placeholder only for Mozilla < 50. For Mozilla > 50 it shouldn't be replaced, pretty please.

kliehm avatar Mar 21 '25 09:03 kliehm

The problem is that caniuse lists the replacement with :-moz-placeholder only for Mozilla < 50. For Mozilla > 50 it shouldn't be replaced, pretty please.

Explanation of bug

TL;DR: The support of Kaios 2.5

I finally found out the cause of this issue. I will describe this step by step.

First, Autoprefixer don't add the :-moz-placeholder under last 1 version browsersl.ist.

Autoprefixer last 1 preview

Image

Second, Autoprefixer appends the :-moz-placeholder from last 2 version browsersl.ist.

Autoprefixer last 2 version preview

Image

Third, I checked the target browsers of the :placeholder-shown property from below code..

https://github.com/postcss/autoprefixer/blob/541295c0e6dd348db2d3f52772b59cd403c59d29/data/prefixes.js#L484-L493

:placeholder-shown target browsers

[
  'firefox 4',  'firefox 5',  'firefox 6',
  'firefox 7',  'firefox 8',  'firefox 9',
  'firefox 10', 'firefox 11', 'firefox 12',
  'firefox 13', 'firefox 14', 'firefox 15',
  'firefox 16', 'firefox 17', 'firefox 18',
  'firefox 19', 'firefox 20', 'firefox 21',
  'firefox 22', 'firefox 23', 'firefox 24',
  'firefox 25', 'firefox 26', 'firefox 27',
  'firefox 28', 'firefox 29', 'firefox 30',
  'firefox 31', 'firefox 32', 'firefox 33',
  'firefox 34', 'firefox 35', 'firefox 36',
  'firefox 37', 'firefox 38', 'firefox 39',
  'firefox 40', 'firefox 41', 'firefox 42',
  'firefox 43', 'firefox 44', 'firefox 45',
  'firefox 46', 'firefox 47', 'firefox 48',
  'firefox 49', 'firefox 50', 'ie 10',
  'ie 11',      'kaios 2.5'
]

The last 2 version browsersl.ist doesn't include the old firefox browsers. So why does Autoprefixer insert the :-moz-placeholder when you cover only last 2 version?

Because of kaios 2.5.

The last 2 version browsersl.ist covers kaios 2.5.

The last 2 version browsersl.ist

Image

In conclusion, Autoprefixer adds :-moz-placeholder with last 2 version browsersl.ist due to kaios 2.5.

Solutions

1. Do nothing

This is a desired result for the people who want to cover kaios 2.5. Then continue to adding :-moz-placeholder.

2. Remove kaios from your own Browserslist.

You can change the target of the browsers using .browserslistrc, package.json and so on. See Browserslist docs for available queries and default value.

3. Don't parse :placeholder-shown to :-moz-placeholder

At first my PR was the removal of :-moz-placeholder-shown property. I didn't know the old firefox supports the non-standard :-moz-placeholder name rather than :placeholder-shown partially. To avoid confusing, stop to parse :placeholder-shown to :-moz-placeholder.

Which solution is better? Thank you for reading!

Marukome0743 avatar Mar 25 '25 08:03 Marukome0743

3. Don't parse :placeholder-shown to :-moz-placeholder

At first my PR was the removal of :-moz-placeholder-shown property. I didn't know the old firefox supports the non-standard :-moz-placeholder name rather than :placeholder-shown partially. To avoid confusing, stop to parse :placeholder-shown to :-moz-placeholder.

Which solution is better? Thank you for reading!

Thank you for your research. I'm afraid solution #2 doesn't work. According to our Browserslist configuration, KaiOS should be excluded:

> 1% in DE
last 2 versions
not dead

Other browsers and OS have a current market share in Germany of < 0.2% (KaiOS doesn't even appear in stats after 2018), so it shouldn't be included. If that configuration doesn't work, I'd prefer solution #3.

kliehm avatar Mar 25 '25 08:03 kliehm

I wonder if you use or combiner instead of and one.


Image


Image


Then you should write this code.

> 1% in DE and last 2 versions
not dead

Please forgive me if it's wrong because I'm newbie on browserslist.

Marukome0743 avatar Mar 25 '25 09:03 Marukome0743

Then you should write this code.

> 1% in DE and last 2 versions
not dead

Good point, I'm not an expert either. I will try that.

kliehm avatar Mar 25 '25 09:03 kliehm

Eventually, I got the corrent .browserslistrc.

> 1% in DE
last 2 versions
not dead
not kaios <= 2.5
Exclude kaios from Browserslist

Image

I also paste the result of your previous one to compare easily.

> 1% in DE
last 2 versions
not dead
Previous Browserslist

Image

This .browserslistrc doesn't parse :placeholder-shown to :-moz-placeholder anymore, right? Could you try it?

Marukome0743 avatar Mar 26 '25 00:03 Marukome0743

Could you try it?

I tried your suggestion

> 1% in DE and last 2 versions
not dead

and it works. So for me that's fine. Thank you for the kind conversation and help.

kliehm avatar Mar 26 '25 09:03 kliehm

I just came across this issue. My use case is the same—combining :not() and :placeholder-shown, which results in the selector always being applied. I fixed this by adding not kaios <= 2.5, but I’m wondering if there will be an official fix.

Option 3 seems reasonable—could someone verify whether :-moz-placeholder in KaiOS 2.5 behaves the same way as :placeholder-shown? Or could this be an error in the Can I Use data?

zipper avatar Apr 01 '25 14:04 zipper

@zipper It looks like the Can I Use data is correct. Based on the KaiOS release history, it seems that KaiOS before 3.0 used Gecko/Firefox 48 (which uses :-moz-placeholder).

So 10.4.20 and earlier had been generating the incorrect :-moz-placeholder-shown for a long time and when it was fixed to :-moz-placeholder, it was technically fixing support for Firefox <= 50 and KaiOS 2.5. The problem is that this backward compatibility code for old versions of Firefox-based browsers is having negative effects on current versions of Firefox-based browsers.

The options seem to be:

  • Rework CSS to avoid combining :not() with an autoprefixed property.
  • Drop support for old Firefox-based browsers (including KaiOS 2.5)

Example, if the text is black and we use :not(:placeholder-shown) to make the text green. Then it will be expanded to two rules, :not(:placeholder-shown) and :not(:-moz-placeholder) that both make the text green when true.

Then, if the element is not showing the placeholder and the browser supports:

  • neither property, then the text is black (because both are treated as invalid).
  • only :placeholder-shown, then the text is black (because one of them is false and the other rule is treated as invalid).
  • only :-moz-placeholder, then the text is black (because one of them is false and the other rule is treated as invalid).
  • :-moz-placeholder and :placeholder-shown, then the text should be black (because both should be false).
    • if the text is green, then it probably means that the newer property, :placeholder-shown, is correctly set to false and the older property :-moz-placeholder is "known"/"not invalid" but not set to false

One approach to reworking the CSS would be to focus on the positive: write the default rule and then positively override that default with when :placeholder-shown is true.

jrchamp avatar Apr 04 '25 22:04 jrchamp

As a sidenote, :is() exists and may simplify pseudo class selector expansion on newer browsers (but probably won't help with this issue).

From https://developer.mozilla.org/en-US/docs/Web/CSS/:not#description

If any selector passed to the :not() pseudo-class is invalid or not supported by the browser, the whole rule will be invalidated. The effective way to overcome this behavior is to use :is() pseudo-class, which accepts a forgiving selector list. For example :not(.foo, :invalid-pseudo-class) will invalidate a whole rule, but :not(:is(.foo, :invalid-pseudo-class)) will match any (including <html> and <body>) element that isn't .foo.

jrchamp avatar Apr 04 '25 22:04 jrchamp

I was wondering why floating form labels don't work in bootstrap and as I started investigating, I've bumped into this bug.

Practically, because of this bug, a

.form-floating > .form-control-plaintext ~ label, .form-floating > .form-control:focus ~ label, .form-floating > .form-control:not(:placeholder-shown) ~ label, .form-floating > .form-select ~ label {
  transform: scale(.85) translateY(-.5rem) translateX(.15rem);
}

will get turned into

.form-floating > .form-control:not(:-moz-placeholder) ~ label {
  transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
}

The former is from the bootstrap site's CSS itself, the latter is my webpack-generated CSS output with using postcss-preset-env. No extra formattings or adjustments on my behalf, it's what gets generated with webpack from the latest bootstrap source. I've tracked the bug down to postcss-preset-env, it works when I comment it out from the webpack configuration.

Looking forward to have this bug fixed because I'm quite sure it corrupts floating labels for everyone else too.

karolyi avatar May 02 '25 13:05 karolyi

@karolyi The floating form labels issues was fixed by removing KaiOS 2.5 support from Bootstrap 5.3.5 via https://github.com/twbs/bootstrap/commit/b1e16bd1617ca29509aaad7c2f6fa6e377aaf173

jrchamp avatar May 02 '25 15:05 jrchamp

@jrchamp thanks, but that only applies for the bootstrap website, I presume. I'm using bootstrap as a module and have my own .browserslistrc setting.

I've added the referenced line to it for now, but you can expect others to show up with this same issue.

karolyi avatar May 02 '25 15:05 karolyi

@karolyi The floating form labels issues was fixed by removing KaiOS 2.5 support from Bootstrap 5.3.5 via twbs/bootstrap@b1e16bd

Issue still exists when using the latest Bootstrap 5.3.6, even after removing KaiOS from the browserslist.

Image

selimanac avatar Jun 16 '25 08:06 selimanac

Issue still exists when using the latest Bootstrap 5.3.6, even after removing KaiOS from the browserslist.

Really?

I made the demo repository and tried the floating labels on bootstrap with Firefox 139.0.4 version. I will show you the tests through the videos.

Part1: unspecified the browserslist

Movie

Image

package.json
{
  "name": "sass-js-esm",
  "description": "Include Bootstrap's source Sass and compiled JavaScript bundle via npm.",
  "version": "0.0.0",
  "private": true,
  "license": "MIT",
  "stackblitz": {
    "startCommand": "npm start"
  },
  "scripts": {
    "build": "npm run css",
    "css-compile": "sass --style compressed --source-map --embed-sources --no-error-css --load-path=node_modules scss/:css/",
    "css-lint": "stylelint scss/",
    "css-prefix": "postcss --replace css/styles.css --use autoprefixer --map",
    "css": "npm-run-all css-compile css-prefix",
    "server": "sirv --dev --no-clear --port 3000",
    "start": "npm-run-all --parallel watch server",
    "watch": "nodemon -e html,scss -x \"npm run css\"",
    "test": "npm-run-all css-lint css"
  },
  "dependencies": {
    "@popperjs/core": "^2.11.8",
    "bootstrap": "^5.3.6"
  },
  "devDependencies": {
    "autoprefixer": "^10.4.21",
    "nodemon": "^3.1.10",
    "npm-run-all": "^4.1.5",
    "postcss": "^8.5.4",
    "postcss-cli": "^11.0.1",
    "sass": "^1.89.1",
    "sirv-cli": "^3.0.1",
    "stylelint": "^16.20.0",
    "stylelint-config-twbs-bootstrap": "^16.0.0"
  }
}

As you say, the floating labels don't work because Autoprefixer adds :-moz-placeholder prefix.

Part2: Browserslist has not kaios <= 2.5 settings

Movie

Image

package.json
{
  "name": "sass-js-esm",
  "description": "Include Bootstrap's source Sass and compiled JavaScript bundle via npm.",
  "version": "0.0.0",
  "private": true,
  "license": "MIT",
  "stackblitz": {
    "startCommand": "npm start"
  },
  "scripts": {
    "build": "npm run css",
    "css-compile": "sass --style compressed --source-map --embed-sources --no-error-css --load-path=node_modules scss/:css/",
    "css-lint": "stylelint scss/",
    "css-prefix": "postcss --replace css/styles.css --use autoprefixer --map",
    "css": "npm-run-all css-compile css-prefix",
    "server": "sirv --dev --no-clear --port 3000",
    "start": "npm-run-all --parallel watch server",
    "watch": "nodemon -e html,scss -x \"npm run css\"",
    "test": "npm-run-all css-lint css"
  },
  "dependencies": {
    "@popperjs/core": "^2.11.8",
    "bootstrap": "^5.3.6"
  },
  "devDependencies": {
    "autoprefixer": "^10.4.21",
    "nodemon": "^3.1.10",
    "npm-run-all": "^4.1.5",
    "postcss": "^8.5.4",
    "postcss-cli": "^11.0.1",
    "sass": "^1.89.1",
    "sirv-cli": "^3.0.1",
    "stylelint": "^16.20.0",
    "stylelint-config-twbs-bootstrap": "^16.0.0"
  },
  "browserslist": [
    "last 2 version",
    "not dead",
    "not kaios <= 2.5"
  ]
}

The floating label works correctly since there is no :-moz-placeholder prefix in input element anymore.

Summary

I can't reproduce your issue when using Bootstrap 5.3.6.

Would you check it with your Firefox browser in StackBlitz?

Open in StackBlitz

Marukome0743 avatar Jun 16 '25 13:06 Marukome0743

Really?

Nope, totally my mistake, I just figured out that postcss couldn’t find the .browserslistrc file. Sorry about that! 🙏

selimanac avatar Jun 17 '25 09:06 selimanac

Really?

Nope, totally my mistake, I just figured out that postcss couldn’t find the .browserslistrc file. Sorry about that! 🙏

Never mind, I made many mistakes too. Anyway I'm happy to help your issue😄

Marukome0743 avatar Jun 17 '25 09:06 Marukome0743

Am I doing something wrong here? I changed the list to be kinda simple to make sure something else doesn't impact the output but I'm still getting prefixed :not(:placeholder-shown) into :not(:-moz-placeholder). I'm testing this using:

postcss css/style.css -u autoprefixer --autoprefixer.browsers "last 1 version and not dead and not kaios <= 2.5" --no-map -o css/style.cs

The browsersl.ist doesn't show the kaiOS browser 2.5, so it shouldn't do the thing, right?

Draghmar avatar Jun 29 '25 12:06 Draghmar

I'm still getting prefixed :not(:placeholder-shown) into :not(:-moz-placeholder).

The latest postcss-cli doesn't have --autoprefixer.browsers and -b option anymore.

Current postcss-cli support options
Usage:
  postcss [input.css] [OPTIONS] [-o|--output output.css] [--watch|-w]
  postcss <input.css>... [OPTIONS] --dir <output-directory> [--watch|-w]
  postcss <input-directory> [OPTIONS] --dir <output-directory> [--watch|-w]
  postcss <input-glob-pattern> [OPTIONS] --dir <output-directory> [--watch|-w]
  postcss <input.css>... [OPTIONS] --replace

Basic options:
  -o, --output   Output file                                            [string]
  -d, --dir      Output directory                                       [string]
  -r, --replace  Replace (overwrite) the input file                    [boolean]
  -m, --map      Create an external sourcemap
  --no-map       Disable the default inline sourcemaps
  -w, --watch    Watch files for changes and recompile as needed       [boolean]
  --verbose      Be verbose                                            [boolean]
  --env          A shortcut for setting NODE_ENV                        [string]

Options for use without a config file:
  -u, --use      List of postcss plugins to use                          [array]
  --parser       Custom postcss parser                                  [string]
  --stringifier  Custom postcss stringifier                             [string]
  --syntax       Custom postcss syntax                                  [string]

Options for use with --dir:
  --ext   Override the output file extension; for use with --dir        [string]
  --base  Mirror the directory structure relative to this path in the output
          directory, for use with --dir                                 [string]

Advanced options:
  --include-dotfiles  Enable glob to match files/dirs that begin with "."
                                                                       [boolean]
  --poll              Use polling for file watching. Can optionally pass polling
                      interval; default 100 ms
  --config            Set a custom directory to look for a config file  [string]

Options:
  --version   Show version number                                      [boolean]
  -h, --help  Show help                                                [boolean]

Examples:
  postcss input.css -o output.css                       Basic usage
  postcss src/**/*.css --base src --dir build           Glob Pattern & output
  cat input.css | postcss -u autoprefixer > output.css  Piping input & output

If no input files are passed, it reads from stdin. If neither -o, --dir, or
--replace is passed, it writes to stdout.

If there are multiple input files, the --dir or --replace option must be passed.

Input files may contain globs (e.g. src/**/*.css). If you pass an input
directory, it will process all files in the directory and any subdirectories,
respecting the glob pattern.

Therefore, the correct command is ...

BROWSERSLIST="last 1 versions, not dead, not kaios <= 2.5" postcss css/style.css -u autoprefixer --no-map -o css/style.css

in Bash.

In addition, you don't need to specify not kaios <= 2.5 if you use last 1 version rather than last 2 version.

By the way, I updated the demo repository to confirm the above postcss-cli command. You can check the output result with npm run css-with-kaios and npm run css-without-kaios command in StackBlitz.

Marukome0743 avatar Jun 29 '25 15:06 Marukome0743

Well that explains a lot. Thanks! :) Is there a way to save that somewhere so I won't have to prepend the command each time I wanted run it?

The last 1 version was there because of the testing I was doing. My list is a little bit different than that. ;)

Draghmar avatar Jun 29 '25 16:06 Draghmar