12

Really simple: how do I most accurately test if a browser has support for a certain CSS selector?

I currently have some CSS code that makes the page a little more interactive by using the :checked selector in CSS, but I want to create a fallback script that does the same thing with JavaScript, but only if the user's browser has no support for the :checked selector.

My question is, how do I most accurately test if the user's browser supports a certain CSS selector?

Here is the code I'd like to use it on:

HTML:

<label class="coolbox">
    <input type="checkbox"/>
    <span>I want to eat some caek.</span>
</label>

CSS:

.coolbox input {display:none;}
.coolbox span::before {
    content: "";
    display:inline-block;
    width:10px;
    height:10px;
    margin-right:5px;
    border:1px solid black;
}
.coolbox:hover span::before {border:1px solid #555;}
.coolbox:active span::before {border:1px solid #999;}

.coolbox span::before {background-color:#F77;}
.coolbox input:checked + span::before {background-color:#4A4;}

Demo

PS: I'd prefer not to just use conditional comments, because I'd like to follow the standard of detecting features instead of browsers.

9
  • 1
    Isn't this what modernizr.com does? Commented Jan 13, 2014 at 15:22
  • 3
    I'd prefer it if it would be possible with just a simple script instead of having to download a library which I'd just be using once. Commented Jan 13, 2014 at 15:24
  • 1
    @Andreas Is :checked test available in modernizr? Commented Jan 13, 2014 at 15:31
  • 1
    @Danko See the OP's "PS". Commented Jan 13, 2014 at 15:32
  • 1
    Checked seems to be available in modernizr: github.com/Modernizr/Modernizr/blob/master/feature-detects/css/… The testStyles method is basically injectElementWithStyles: github.com/Modernizr/Modernizr/blob/master/src/… Commented Jan 13, 2014 at 15:36

5 Answers 5

9

You could use querySelector:

function testSelector(selector, node){
  var scope = document.createElement("div");
  scope.appendChild(node);

  try {
    return scope.querySelector(selector) !== null;
  } catch(e) { return false; }
}

You can test it like this:

var node = document.createElement("input");
node.type = 'checkbox';
node.checked = 'checked';

testSelector("input:checked", node); // === true

See this other question for more info on querySelector.

Sign up to request clarification or add additional context in comments.

7 Comments

If I run this script, there seems to be an error in testSelector's return statement ("invalid argument"). I'm still looking for what is causing this exactly on google, but if that can be fixed, I think this'd be the best solution. Would you have any idea what could be the cause of this?
After testing document.body.querySelector(':checked') in IE11's IE8-emulator's console, it gave the same "invalid argument" error. It looks like IE8 (atleast in IE11 emulation mode) considers the :checked selector invalid, and throws an error.
I tested on Chromium and Firefox but not IE. I'm not sure what could be causing "invalid argument". If you're testing on IE, make sure you're using IE8+ in standards mode. Or the querySelector function will not exist.
I've decided to go with this answer, since this doesn't require any changes to either the CSS or JS, and doesn't modify the contents of the page to test anything. I've put a try {} catch(e){} around the part that was causing problems in IE8. I'm just going to assume that if the browser throws an error if you use :checked in .querySelector, the browser doesn't support it.
@joeytje50 is correct. An unrecognized or invalid selector should throw an error, not just return a null result, so IE is behaving correctly. When querySelector() returns null, that means the selector is valid but doesn't match anything. See w3.org/TR/selectors-api/#processing-selectors
|
5

Workaround for your case:

<input type=checkbox checked=checked>

css:

input{
  font-family:'arial';
}
input:checked{
  font-family:'sans-serif';
}

checking procedure: js

alert($('input').css('font-family')=='sans-serif'?'supported':'not supported');

1 Comment

Changed my "accept" to this answer, because I ended up actually using this. Thanks for your help!
3

From some research I was able to find various websites that can test your browser to see the support.

This website is helpful to find what supports what but does not test your current browser.

This website will test your browser and if you don't want to use modernizr you can learn from their script.

This last website seems to do a great job and is very accurate of the support. I also am pretty sure I found the script that is doing this so like I said you can learn from how other website are doing this.

To figure how they are making this work you will need to understand how their scripts are working. The bottom three seem to be the most critical to the function.

<script src="utopia.js"></script>
<script src="supports.js"></script>
<script src="csstest.js"></script>
<script src="tests.js"></script>

These are just some options that I found and it is up to your needs. Best of luck and hopefully this has been helpful.

2 Comments

Going by the source code on css3.info, I've made a little test script for use in the console: var x = new CSSTestCase(['checked'], { onSuccess: function() {console.log(true,arguments)}, onFailure: function() {console.log(false,arguments)}});, and when I browse through the logged message in the console, this script seems to require loading an iframe for every test. For the other site, I'm still looking, but it looks a bit confusing as to how they actually do the feature testing on their site.
@joeytje50 Hmm very weird, after looking through the page's source code there is no iframe anywhere on the page. I'm guess it must be generated in by the script which shouldn't be to much of a problem, you'll just have to find how the iframe is being added. (nevermind I am talking about css3test, which seems like a better script)
2

A shorter way to do it would be to simply try the query selector, if it produces an error, return false, else true, like this:

function testSelector(selector) {
  document.querySelector('*');  //checks if querySelector is implemented and raises an error if not
  try {document.querySelector(selector)} catch (e) {return false}
  return true;
}

I checked it on IE9 Windows and Chrome Mac (V43.0.2357.130), Win(V39.0.2171.95m), FireFox Win (V38.0.5), and it works fine with testSelector("form:invalid"), which is not implemented by IE9, but by everybody else.

Comments

1

While this is, admittedly, a very late answer to a rather old question it seemed worth adding another answer.

One approach, using JavaScript to test for selector support, is below with explanatory comments in the code:

// some DOM utilities and helpers,
// caching document as I don't enjoy typing that much:
const D = document,
  // aliasing document.querySelector() and element.querySelector()
  // again, because I don't enjoy typing; here the function takes
  // two arguments 'sel' and 'context',
  // 'sel' is a String, and is the selector for the element we're
  // trying to retrieve;
  // 'context' is the element/node we wish to search within; if
  // no context is passed we default to document.querySelector()
  // otherwise we use Element.querySelector():
  get = (sel, context = D) => context.querySelector(sel),
  // as above, except it's an alias for querySelectorAll(), and
  // here we return an Array of nodes instead of a NodeList in
  // order to use Array methods when/if required:
  getAll = (sel, context = D) => [...context.querySelectorAll(sel)],
  // alias for document.createElement(), which also allows properties
  // to be set on the created element:
  create = (tag, prop) => document.createElement(tag),
  // simple function to allow for more elaborate templates, and
  // arguments if required later:
  templatedResult = (text) => `<code>${text}</code>`,
  // named function to assess whether the browser supports/implements
  // a given selector; this takes a reference to the Event Object
  // passed automatically from EventTarget.addEventListener():
  verifySelectorCompatibility = (evt) => {
    // preventing default actions (as one of the events to which
    // bind the function is form.submit):
    evt.preventDefault();

    // gathering variables/data
    // here we retrieve the first/only <input> element within
    // the document:
    const input = get('#testSelector'),
      // we retrieve the value from the <input> and trim that
      // value of it's leading/trailing white-space:
      selector = input.value.trim(),
      // we retrieve the element with id=results:
      output = get('#results'),
      // we use the CSS.supports() function to assess whether
      // the browser supports the supplied selector, which
      // we pass to the function in a template string
      // concatenating the selector within the string
      // 'selector(...)' as required by the function:
      result = CSS.supports(`selector(${selector})`),
      // creating an <li>:
      listElement = create('li');

    // if the event-type is 'submit' and the user-entered selector
    // - once trimmed of leading/trailing white-space - is zero-
    // length we return at this point:
    if (evt.type === 'submit' && selector.trim().length === 0) {
      return false;
    }

    // here we add the class of 'supported' or 'unsupported' based on
    // the 'result' variable being exactly equal to (Boolean) true
    // or not:
    listElement.classList.add(result === true ? 'supported' : 'unsupported');

    // here we set the innerHTML of the <li> element to the template string
    // from the defined function (above):
    listElement.innerHTML = templatedResult(selector);

    // we then use Element.prepend() to insert the new <li>
    // as the first child of the 'output' element:
    output.prepend(listElement);

  },
  // here we return the first/only <button> and <form> elements:
  button = get('button'),
  form = get('form');

// and we then bind the verifySelectorCompatibility() function
// to the 'click' event of the <button> and the 'submit' event
// of the <form>:
button.addEventListener('click', verifySelectorCompatibility);
form.addEventListener('submit', verifySelectorCompatibility);
*,
 ::before,
::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

form {
  margin-block: 1em;
  margin-inline: auto;
  width: clamp(30rem, 60vw, 1000px);
}

fieldset {
  display: grid;
  gap: 1em;
  grid-auto-rows: min-content;
  grid-template-columns: repeat(4, 1fr);
  padding-block: 0.25em;
  padding-inline: 0.5em;
}

label {
  display: flex;
  flex-flow: row nowrap;
  gap: 1em;
  grid-column: 1 / -1;
  margin-block: 1em;
  margin-inline: 0.5em;
  padding: 0.25em;
}

label span {
  align-self: center;
}

label span::after {
  content: ': ';
}

label input {
  flex-grow: 1;
  object-fit: cover;
  padding: 0.25em;
}

button {
  grid-column: -2 / span 2;
}

#results li {
  border-block-end-width: 3px;
  border-block-end-style: solid;
  font-family: monospace;
  font-size: 1.5em;
  padding-block: 0.25em;
  padding-inline: 0.5em;
}

.supported {
  border-block-end-color: lime;
}

.supported::marker {
  content: '\2713';
}

.unsupported {
  border-block-end-color: red;
}

.unsupported::marker {
  content: '\2717';
}
<form action="#" method="post">
  <fieldset>
    <legend>Does your browser support..?</legend>
    <label>
      <span>Enter a selector to test</span>
      <input type="text" id="testSelector">
    </label>
    <button type="button">Check your browser</button>
  </fieldset>
  <ul id="results"></ul>
</form>

JS Fiddle demo.

References:

1 Comment

In my original question, I was asking for a simple way to create a JS fallback for browsers that do not support a certain selector. This answer is way too elaborate, in my opinion, and it would fit much better as an answer if you would simply have answered with a single script that functions like if (!CSS.supports('selector(:checked)')) fallbackChecked();, and then explained what this CSS.supports function does. However, even though your answer contains quite a lot of unnecessary bloat that distracts from the solution, I do very much appreciate pointing me towards CSS.supports. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.