csswg-drafts icon indicating copy to clipboard operation
csswg-drafts copied to clipboard

[css-conditional] testing support of properties and values with partial implementations

Open rachelandrew opened this issue 6 years ago • 25 comments

There are a few places where @supports is unable to help authors as the property and value they want to test for is supported by the user agent, but not in the context they want to use it.

The obvious one (and where it keeps coming up at the moment) is testing support of any of the gap properties column-gap, row-gap or gap. I can test for support of column-gap and every browser will tell me it has support, however if I then try to use it in flexbox, I will only get a gap in Firefox, as that is the only user agent to have an implementation right now.

This means authors can't create a nice fallback with margins, then removing the margin on testing for support. It is likely that other box alignment properties - perhaps as browsers start to implement them for block layout, will have the same issue.

Other places this might be an issue are for fragmentation, I might want to test if fragmentation properties are available in a certain context.

It feels as if we need a way to ask for support of, for example, column: gap in display: flex. I don't know if that is even feasible in implementations, but it seems that this is likely to continue to be an issue as specs become more modular.

I found an older issue which also refers to partial implementations not being testable: https://github.com/w3c/csswg-drafts/issues/1167

rachelandrew avatar Jan 26 '19 08:01 rachelandrew

This also comes up all the time when dealing with properties from SVG that are crossing over into CSS box layout & vice versa. For things like filter we have had browsers that support one syntax for SVG elements only, and a different syntax for everything except SVG elements, and both syntaxes would pass an @supports test.

(Aside: I really wish the spec for gap had strongly encouraged browsers not to support the generic property name until they implemented support for the generic feature. I was rather disappointed when browsers rushed to ship the easy parser implementation of the synonym before addressing their layout code, for this reason. But the broader issue still remains.)

AmeliaBR avatar Jan 26 '19 15:01 AmeliaBR

Perhaps the request should be that, in the absence of finer granularity queries, @supports should return false unless the implementation support exists in all usage contexts.

nigelmegitt avatar Jan 28 '19 12:01 nigelmegitt

"Perhaps the request should be that, in the absence of finer granularity queries, @supports should return false unless the implementation support exists in all usage contexts."

That might be a bit more useful, however then there is a property and value that the author knows is supported, but seems to be returning false. At that point we are asking them to understand all possible contexts in which that property vould be used.

The fragmentation properties are probably better to look at here than gaps, which are likely to be implemented - say if we did a specific masonry layout or something.

At the moment a property like break-before: avoid is specified for multicol, paged media, and regions. Assuming a wonderful world in which the property was supported in multicol and paged media, regions is a long way off, and probably won't happen in the current form. Which would mean that testing for break-* properties would be useless for the foreseeable future, as we don't have Regions so it can't be implemented in them. Most authors aren't even going to know about Regions and therefore why the property is returning false.

rachelandrew avatar Jan 28 '19 12:01 rachelandrew

There's no good solution to this that doesn't break the fundamental value proposition of @supports, which is that, since it's based purely on "does it parse", it requires no special table of return results in the browser, which history has shown us is destined to become some combination of stale and lies. I'm strongly against anything that tries to get "special-case" answers like this that aren't backed by a simple "try to parse it" mechanism.

(I definitely feel the pain, but it's not solveable here without making things overall worse.)

tabatkins avatar Jan 29 '19 22:01 tabatkins

Is it feasible to have @supports defined in the context of another selector? Something like:

body {
display: flex;
margin: 2rem;

  @supports(column:gap) {
  margin:0;
  column: gap;
  }
}

This way, the parsing stays simple and uses basic 'does it parse' logic.

ghost avatar Jan 30 '19 22:01 ghost

Related...

Though @supports(position:sticky) currently parses for all major vendors, Blink, which has the largest market share, still doesn't *fully support the value.

@SelenIT https://github.com/w3c/csswg-drafts/issues/2155#issuecomment-410978077

It seems that for some reason Chrome reverted their changes for fixing position:sticky behavior on thead element.

@tabatkins I understand your sentiment about making things worse, but currently the often most efficient ways to deal with false @support scenarios are selector hacks, even if...

@dbaron https://github.com/w3c/csswg-drafts/issues/3051#issuecomment-416084354

Using hacks that assume support for X is equivalent to support for Y is a bad idea if those hacks are targeting current browsers. Such hacks are safe only if the features are implemented in all current browsers and the equivalence holds in the old versions where they're not implemented.

And the resolution in https://github.com/w3c/csswg-drafts/issues/3051 seems to express an opposite stance, getting rid of the ability to use some of these selector hacks.

jonjohnjohnson avatar Jan 30 '19 23:01 jonjohnjohnson

Yup, selector hacks (or other browser-selection mechanisms) aren't great, but again, we already know that manually-maintained lists of support eventually rot, every single time it's ever been tried.

So it's a choice of somewhat encouraging bad individual-author behavior, or speccing bad UA behavior. I think the former does less damage overall.

tabatkins avatar Feb 02 '19 01:02 tabatkins

Given, gap properties already exist for grid i.e. grid-gap, grid-column-gap, grid-row-gap, aliases for gap properties could also be added for Flexbox. This would enable detection for gap support specifically in Flexbox using the existing Feature Query syntax. For example:

@supports (flex-gap: 10px) {
  div {
    display: flex;
    gap: 10px;
  }
}

@supports not (flex-gap: 10px) {
  div {
    display: flex;
    margin: 10px;
  }
}

I'm not sure however, how appropriate this approach would be regarding other properties.

It certainly seams that as there is greater cross-pollination between specifications, this kind of situation is likely to become more prevalent and a sustainable solution would be nice.

simlawr avatar Feb 15 '19 17:02 simlawr

@simlawr That solution would somewhat cleanly solve the current issue regarding the -gap properties, as authors can easily understand it. Though I believe it doesn't scale because it means that for every new property that is shared between different contexts, there needs to be an alias introduced only for the purpose of feature detection. That burdens authors, as they have to remember the aliases when doing feature detection, implementors, as they have to implement the aliases only applying in certain contexts, spec. authors because they will have to remember to add those aliases when the property is used in different contexts, and documentation writers because they will also have to cover those aliases.

Unfortunately, I don't have a better solution for this. There is probably no automated way of doing this kind of feature detection.

Sebastian

SebastianZ avatar Nov 22 '19 22:11 SebastianZ

I really like Rachel's original suggestion although I would change it to "with" rather than "in" because you are more looking for a generic relationship between the 2 rather than a parent/child relationship.

@supports (gap: 1px) with (display: flex) {
   /* css code */
}

It doesn't sound that impossible for the computer to recognise.

The browser would need to check if both of the following conditions are true:

  • It understands both properties.
  • At least one of the properties has any sort of effect on the other.

If not then it returns false.

I am probably grossly simplifying the problem though. 😅

Dan503 avatar Apr 29 '20 07:04 Dan503

In the case of flex gap specifically, is there any workaround for web developers to detect support for it? It's only recently shipped in Chrome and even more recently landed in Safari Technology Preview, and without feature detection people will need to wait for another 3 years or so to be able to simply assume it will work.

foolip avatar Oct 27 '20 11:10 foolip

In the case of flex gap specifically, is there any workaround for web developers to detect support for it?

Yes, there is but you have to use JS to detect it.

The following is based on this blog post: https://ishadeed.com/article/flexbox-gap/

// Set up a cache to prevent a DOM element from getting created for every use of the function
let isSupported;

function checkFlexGapSupport() {
        // Use the cached value if it has been defined
	if (isSupported !== undefined) {
		return isSupported
	}

	// Create a flex container with row-gap set
	const flex = document.createElement('div')
	flex.style.display = 'flex'
	flex.style.flexDirection = 'column'
	flex.style.rowGap = '1px'
	flex.style.position = 'absolute'

	// Create two, elements inside it
	flex.appendChild(document.createElement('div'))
	flex.appendChild(document.createElement('div'))

	// Append to the DOM (needed to obtain scrollHeight)
	document.body.appendChild(flex)

        // Flex container should be 1px high due to the row-gap
	isSupported = flex.scrollHeight === 1

        // Remove element from the DOM after you are done with it
	flex.parentNode.removeChild(flex)

	return isSupported
}

Dan503 avatar Oct 27 '20 20:10 Dan503

Thanks @Dan503! I guess what people then typically to do is set a class on the body element and use that instead of @supports?

foolip avatar Oct 27 '20 22:10 foolip

That used to be very common, don't know if it still is these days. Often people would use a tool like Modernizr to do that.

tremby avatar Oct 28 '20 01:10 tremby

Is there any development on doing this either through an alias or an improvement to @supports? Using gap in the context of Flexbox is virtually non-existent to me since it's not checkable.

I've stumble upon the above modified Modernizr check. For simple cases it might actually be easier to use a mixin through some CSS framework to "mask" gap so for instance calling a mixing gap(value) to something like the over-simplified snippet below for demonstration purposes. After all, a fix for browsers still not supporting gap would be needed.

& > :not(:last-child) {
  margin-right: value;
}

BUT you do have to account for Flexbox direction and wrapping, so something more like flex-layout(value, direction, wrap) to make it more generic which can quickly make it a real pain vs using JS & DOM manipulation.

Resorting to either DOM manipulation or ignoring the feature I do hope there's some mediation without having to wait maybe another few years until gap is pretty safe. It seems following how @supports works generics, such as gap will simply need case-specific aliased names or they won't be able to be implemented correctly. It'd be quite unfortunate to see Modernizer forced to evolve into a bandaid of sorts.

marcebdev avatar Dec 30 '20 20:12 marcebdev

@supports not (image-orientation: from-image) {
    .flexbox {
        margin: 0.5em auto;
    }
}

koller18 avatar Dec 05 '21 19:12 koller18

@koller18 how's your code sample related to the subject? 🙄 It's not even a hack to "assume" parallel flexbox+gap support for any browser past + present.

WebMechanic avatar Apr 04 '22 11:04 WebMechanic

I just had an idea for a solution to this: Custom @supports rules.

Similar to script-based custom media queries, we could introduce custom @supports rules. Those would allow to register some JavaScript worklet executing code like the one suggested by @Dan503.

The big advantage would be that authors could use @supports in their CSS context. The only part in JS would be the check for the support. Another advantage would be the great flexibility allowing authors to create any kind of check, possibly even beyond CSS related things. The downsides of this proposal are that executing that code can slow down CSS evaluation and worklets generally don't have access to the DOM. Though I'd say the slow-down in evaluation is neglectable because the script only has to be executed once when parsing the CSS. The bigger issue which I didn't give much thought yet is probably the DOM access. I assume the access probably needs to be restricted somehow to avoid performance and/or privacy problems.

Sebastian

SebastianZ avatar May 02 '22 06:05 SebastianZ

To make my previous post a bit clearer, here an example for how I imagine this to work:

JS:

// gapInFlexboxSupport.js
class GapInFlexboxSupport {
  name: '--gap-in-flexbox',
  check: () => {
    // Create a flex container with row-gap set
    const flex = document.createElement('div');
    flex.style.display = 'flex';
    flex.style.flexDirection = 'column';
    flex.style.rowGap = '1px';

    // Create two, elements inside it
    flex.append(document.createElement('div'), document.createElement('div'));

    // Append to the DOM (needed to obtain scrollHeight)
    document.body.append(flex);

    // Flex container should be 1px high due to the row-gap
    return flex.scrollHeight === 1;
  }
}

registerSupportCheck(GapInFlexboxSupport);


// In document
CSS.supportsWorklet.addModule('gapInFlexboxSupport.js');

CSS:

@supports (--gap-in-flexbox) {
  .flex {
    display: flex;
    gap: 8px;
  }
}

Regarding performance and especially privacy concerns, we could create a worklet-specific document. (This is also what I expect in my example code.) So there is no relation to the actual document. Though this would, of course, also limit the possibilities for this feature.

Of course this also requires a way to check whether custom @supports rules are supported, i.e. #6966.

Sebastian

SebastianZ avatar Jun 14 '22 11:06 SebastianZ

In the case of flex gap specifically, is there any workaround for web developers to detect support for it? It's only recently shipped in Chrome and even more recently landed in Safari Technology Preview, and without feature detection people will need to wait for another 3 years or so to be able to simply assume it will work.

Much longer than this :/ 2 years have past since this statement and at work we still do not allow gap (in flex elements) because we can not write fallbacks. We typically support 5-8 years of browser versions.

If @supports (flex-gap: 1px) {} was available in the next version of each browser we could start using it.

We don't care that there are some versions with native support that would get the fallback. We do care about the browsers without native support.


That it is the 4th most searched for feature on caniuse shows how big a problem this is for stylesheet authors :/

Screenshot 2022-08-19 at 19 37 02

romainmenke avatar Aug 19 '22 17:08 romainmenke

Agenda+ to discuss my suggestion to add custom @supports checks and also the other suggestions mentioned here.

Sebastian

SebastianZ avatar Aug 19 '22 23:08 SebastianZ

I have some concerns with custom @supports checks. Needing a sub resource to check if a single property is usable in a single context can quickly escalate.

It requires authors to work around more issues instead of directly solving the issue at hand.

  • what is a supports worklet?
  • are these worklets blocking the page render?
  • what happens if the network is flaky and the worklet loads slowly
  • how does a site behave with 5 of these checks? or with 50?

In my opinion this places too much burden on stylesheet authors.

romainmenke avatar Aug 21 '22 21:08 romainmenke

All good questions! But they apply to all features that use worklets, e.g. several Houdini APIs, and the latter three also to external style sheets. And, as mentioned before, the proposed worklet would only need to run once. So at least in comparison to Houdini worklets which run many times consecutively it's cheap regarding performance.

Sebastian

SebastianZ avatar Aug 22 '22 19:08 SebastianZ

For Houdini features I do not mind the tradeoff. The "user story" is straightforward here.

I want to do something browsers normally can't, so I reach for an API which enables custom behaviour.

Custom @supports for native properties are a different case in my opinion. It is not intuitive or obvious that stylesheet authors needs to create a supports worklet and think about the issues listed above when they actually want to use a standard property.


The same however is true for @supports (flex-gap: 1px) or @supports (gap: 1px) in (display: flex).

There is no clear signal to stylesheet authors that @supports (gap: 1px) will sometimes be true when they would expect it to be false.

Linters/build tools could warn when they encounter ambiguous @supports rules to help stylesheet authors.

romainmenke avatar Aug 22 '22 20:08 romainmenke

There is no clear signal to stylesheet authors that @supports (gap: 1px) will sometimes be true when they would expect it to be false.

Yes, authors still need to learn about this context relation and CSS itself cannot change this, unfortunately. So this issue focuses on a way to at least make it possible to detect context related support of features.

As @tabatkins pointed out earlier, user agent creators want to avoid a situation in which they have to maintain a table of context relations. Custom @support checks take away that burden from them and still provide authors with the possibility to check for context related support.

Linters/build tools could warn when they encounter ambiguous @supports rules to help stylesheet authors.

That's definitely true. And I am pretty sure that some of them already do that. But that's out of scope of the specifications, of course.

Sebastian

SebastianZ avatar Aug 23 '22 20:08 SebastianZ

The CSS Working Group just discussed Testing support of partial implementations.

The full IRC log of that discussion <fantasai> Topic: Testing support of partial implementations
<fantasai> github: https://github.com/w3c/csswg-drafts/issues/3559
<fantasai> astearns: Sebastien added some comments, unable to join in person
<fantasai> astearns: he has a specific suggestion ...
<astearns> this comment https://github.com/w3c/csswg-drafts/issues/3559#issuecomment-1114545949
<fantasai> rachelandrew: This came uporiginally because of the gap property
<fantasai> rachelandrew: originally defined for grid layout, later expanded to flexbox
<fantasai> rachelandrew: but can't test within @supports
<fantasai> rachelandrew: so can't e.g. provide a fallback margin
<fantasai> rachelandrew: unsure where it will come up win future, but for specs like alignment which applie to lots of things could come up
<fantasai> rachelandrew: so that's why opened the issue, for testing whether property X roks in context Y
<fantasai> s/roks/works/
<fantasai> rachelandrew: Idk how likely it is to appear in the future
<astearns> ack fantasai
<Zakim> fantasai, you wanted to ask to review use cases and to
<fantasai> fantasai: Another context is alignment properties applying to abspos or block containers
<chris> rssagent, here
<fantasai> fantasai: and fremy had suggested gap applying to tables, so that's another place
<chris> rrsagent, here
<RRSAgent> See https://www.w3.org/2022/09/16-css-irc#T20-38-42
<fantasai> astearns: we have general issue of interactions that are difficult to test
<fantasai> astearns: sebastian had specific suggestion of allowing custom support rules
<fantasai> astearns: that would run author JS to check for a particular thing they wan'ted to test
<fantasai> astearns: sebastian asked whether this is a reasonable way forward, is it possible in browsers
<fantasai> heycam: No real obvious time to run that script
<fantasai> heycam: would want to run it only once
<fantasai> heycam: maybe at the time the stylesheet is loaded could run it?
<fantasai> heycam: it feels weird to have some script run at some point for that
<fantasai> heycam: I do feel like you do want a traditional feature-testing way of checking this
<fantasai> heycam: e.g. test whether gap on table, checking whether it has some effect on the size of the element
<fantasai> heycam: but idk what's a concise way to run that in a query string
<heycam> fantasai: so far most of these are dependent on your formatting context or box type
<TabAtkins> q+
<heycam> ... so it's not that you want to check interactions with other properties, but specifically whether properties apply to specific types of boxes
<astearns> ack TabAtkins
<fantasai> TabAtkins: On subject of well-defined time to run, this is the same problem as wanting custom MQ
<fantasai> TabAtkins: so we could just run it on that
<fantasai> TabAtkins: no difference between writing @media --flex-gap
<fantasai> TabAtkins: which you set at page load
<fantasai> TabAtkins: vs in supports
<fantasai> TabAtkins: if we feel it's needed for usability, maybe separate registry
<fantasai> TabAtkins: but run script once and run well-defined time, already have proposal to solve
<heycam> fantasai: for the usability aspect of it, there's been proposals of a generic if statement
<heycam> ... rather than having custom MQs, you could have a more generic syntax in an if statement
<astearns> ack fantasai
<heycam> ... so you could do it in a MQ, or in an if
<fantasai> s/if statement/@if statement/
<fantasai> astearns: Maybe this is enough feedback to satisfy for now?
<fantasai> astearns: anyone else to weigh in?
<fantasai> dholbert: curious what DOM these queries run in
<fantasai> dholbert: does it persist
<fantasai> TabAtkins: answer is, dependes on what you are talking about
<fantasai> TabAtkins: if you're doing custom MQ, it's whatever you want. YOu're just running JS and updating a map
<fantasai> TabAtkins: that's attached to the document object
<fantasai> TabAtkins: that'll be available to any stylesheets attached to that page
<fantasai> TabAtkins: if doing something else, depends on specifics
<fantasai> astearns: let's leave this issue here, and see if we make more progress in issue itself

css-meeting-bot avatar Sep 16 '22 20:09 css-meeting-bot

I found inset approximates support for flex gap pretty well. Compare browser comatibility data between the two: https://developer.mozilla.org/en-US/docs/Web/CSS/gap#browser_compatibility https://developer.mozilla.org/en-US/docs/Web/CSS/inset#browser_compatibility

@supports (inset: 0) will not target any browsers which don't support flex gap. The @supports check will also not target some browsers which do support flex gap: Chrome [84, 85, 86], Firefox [63, 64, 65], or Opera [70, 71, 72]. Support in WebKit matches perfectly.

If you're using mdn browser compat data to drop polyfills from your stylesheets it works well to avoid bundling clumsy margin-based polyfills.

Obviously I would love to see a fix for the underlying standards issue but this is a solution we can use today for the flex case.

laverdet avatar Oct 11 '23 20:10 laverdet

With align-content on block layout becoming available in browsers, this problem is getting some new life blown into it.

In https://css-irl.info/how-do-you-vertically-centre-an-element-in-css/, @michebarks wrote:

One thing that concerns me, is that this seems to fall into that tricky area where it becomes impossible to test for browser support and provide fallbacks using a feature query — much like gap when it was implemented for flexbox. As align-content is well-supported for Grid and flexbox, the feature query doesn’t help us here.

@supports (align-content: center) {
 /* This will resolve true for any browsers supporting the property in grid or flexbox */
}

It would be great to see some improvements to how feature queries can handle these sorts of situations.

bramus avatar Dec 23 '23 00:12 bramus

I was recently experimenting with some unrelated things, where I used inline-blocks for a layout, and I really missed the absence of a gap in the normal flow. If we did have an ability to detect partial implementations like this, it could be more likely that we could get it (“normal flow gap”), as well as other similar cases (extending some very specific property to work in different contexts).

The absence of the ability to easily detect the flex + gap support is also a reason why our design system currently has a legacy workaround for the flex “gap” using margins: it is not trivial to migrate to native gap.

I really like the idea of using something like (gap: 1px) with (display: flex) as a way to test how multiple properties & their values interact.

Though I can see the challenge in how that could be defined, and for authors it might be confusing trying to understand which pairs of properties they could use in @supports like that.

We don't have many places like this outside of gap, so I think having a “hardcoded” syntax like @supports (gap: 1px !flex) could work. Maybe not exactly like this, but the idea is for any properties+value pairs that get changes in the specs/implementations that require different type of support detection, to have an explicitly defined “supports flag” which could be used only as a part of @supports.

I do think this case is exceptional enough that not coming up with an abstract “ideal” solution is worth it here, and in this case, it is better to get a working solution sooner rather than later.

kizu avatar Dec 23 '23 01:12 kizu

I think @tabatkins's https://github.com/w3c/csswg-drafts/issues/3559#issuecomment-458734231 (which I agree with) was a good explanation of why we don't want a general solution here.

However, I think it might be reasonable to add specific, one-off solutions when we think they're important enough. They'd need to be for significant enough features that we'd be willing to add an extra keyword to CSS for them, and that an implementor wouldn't miss that it needed to be implemented, and that we'd be willing to write specific web-platform-tests to verify and monitor the results closely to make sure the rollout doesn't go wrong. And we could probably choose reasonably verbose keywords.

For example, for the two examples discussed recently we could add something like gap-on-display-flex or align-justify-properties-on-blocks keywords so that it would be possible to write:

@supports feature(gap-on-display-flex) {
  /* ... styles ... */
}

(It's likely too late for the former to be useful at this point, but the latter is something we really could consider now.)

dbaron avatar Dec 23 '23 01:12 dbaron