Position an element relatively to another element from anywhere in the DOM

So I’m generally in favor of giving the end-user as much flexibility as possible when it comes to features because they end up doing things the implementors could never imagine with them… and that’s great! Allowing any CSS selector would give a lot more flexibility but it would also open it up to some issues and I don’t think the tradeoffs are worth it.

By sticking with Id’s, we can select one element as fast as possible and we have built in guarantees that the element is unique (or else the document is invalid). It also gives us a much simpler syntax than the one you’re proposing. Finally, if a user runs into issues using the property, there’s a single point of failure to look at… that element exists in the DOM or it does not.

Using your proposed syntax, there’s no guarantee we’ll find only one element and each of these queries could/would be noticeably slower than using Id’s, even if we’re only using the first element returned by the selection.

All that to say I’m favoring simplicity and speed over flexibility in this case.

1 Like

Yes, but a rule that only works once on the page is useless for anything but fixed UI elements and dialog boxes. Assigning the rule to a unique element means it’s completely incapable of working for any sort of repeatable component outside of Shadow DOM.

This argument could be applied to anything that uses CSS selectors. It’s not a valid point.

Since I’ve never actually touched UA code for CSS engines, I leave tractable perf concerns to people like Tab who have some applicable experience in this domain.

1 Like

Thanks for your response!

I’m pretty sure I’m misunderstanding what it is exactly that you’re trying to accomplish with your syntax (over the original) so if you could provide an example that’d be great.

As I read it, your solution ultimately only targets one element as well. If you factor JavaScript into the equation anything is possible with either solution. I can trivially change classes and Id’s on any element thereby changing the rule to point to the element I need. If you’re not factoring JavaScript into the equation, then the DOM isn’t changing and either solution will target just the one element.

The one exception to this (in your case) would be the ability to use pseudo classes, which would be a definite plus!

Except that selectors don’t typically reside in property values. I think the additional clarity afforded to me by an Id here would be beneficial.

I haven’t touched UA code either, but I’m not asking you to take my word for it. Just run some perf tests in your own browser using dev tools. I imagine the scoping of your functional syntax would help mitigate some of the issues, but you should be able to see get a general idea of how the selectors perform.

To add to that, I feel like I worded my initial response poorly so let me try to better explain my point. I’m not trying to say that only Id’s will perform adequately. I’m just suggesting that the open-ended nature of your syntax invites performance considerations. By restricting the selectors to just Id’s, we know that it’ll work, because we have viable examples using the same syntax in existing browsers.

I guess my big question for you is: Why would I want all the extra stuff in your syntax, when at the end of the day all I’m trying to target is one element? Id’s only return one element and that’s what I want… why make it more complicated?

1 Like

HTML:

<div class="card">
  <img src="user-f00.png" class="avatar"><div class="unread">16</div>
</div>
<div class="card">
  <img src="user-ba2.png" class="avatar"><div class="unread">23</div>
</div>

CSS:

.unread {
  position: relative-to first-sibling(.avatar);
  top: 2px;
  left: 2px;
}

I think these are all solid concerns, but I also think they might be outside the scope of what this feature is trying to accomplish. Since these are all edge-cases contingent on the project, I think the solution to solving these problems is Javascript, but it would be really neat to see rules that could handle these cases.

I like this! Container queries (or whatever they end up being) might help some of the special cases, but there will always be others.

Absolutely, they’d have a great perspective on this issue! I’ll hit them up on Github.

Thanks for the example, but it seems like this could be adequately solved using position: absolute/relative. Do you have a more specific use case?

.card {
  position: relative;
}
.unread {
  position: absolute;
  top: 2px;
  left: 2px;
}

Say there are multiple elements in .card.

Author of Tether here…

First off, thanks so much for bringing this up. @cvrebert’s A–C complications/use-cases are great; we’ve certainly see all those with Tether. Some things that come to mind (in no particular order):

  • This may seem obvious or I may have missed somebody mention it, but overflow: hidden needs to be ignored by the element for all of the target’s parents as well as the target itself.
  • Similarly, overflow: scroll (or auto when the scrollHeight/Width exceeds the clientHeight/Width) needs to be addressed for all of the target’s parents as well.
  • Allowing an element to be the target of anotherElement (a tethered chain) would be marvelous.
  • Expanding on @cvrebert’s (A), allowing for a simplistic version of Tether’s notion of Constraints & Attachment would be nice. Syntactical strawman:
#element {
  position: element(#target) constrain-to-window;
  left: 0;
  top: 100%
}
  • I wonder if there might be an overlap with position: sticky, or any of the work there.

In general, my two cents are that when in doubt, making the behavior match how positioning would work if the element were position: absolute and a direct descendent of the target is probably the right call. So for example in @cvrebert’s (B), setting left: 0; right: 0 would render the width 0.

I hope this helps! Happy to share more of my experiences working on Tether or its consumer libraries Tooltip, Drop, Select, Shepherd, etc.

2 Likes

This feature would solve one of CSS’ biggest problems in UI designs. However, just accepting IDs makes this feature as a no-js-solution completely useless, because UI components usually don’t use IDs :worried:

2 Likes

Hi all

It would be a very usefull technique and it would help in moving UI-JS logic more into CSS. Although it is little bit ambiguous, I like the idea with:

#element{
   position: element(#target)
}

I have another suggestion to the problem, what about using an at-rule(!) or a css function:

@@stick(#element to #target)
or
@@design( group(#element to #target) )

-fn-group(#element to #target);

What do you think?

Maybe I’m just missing something here, but doesn’t restricting to IDs just put us back at square one? If we can only target by ID then either:

  1. It’s not DRY because we have to have a different CSS rule for every single “relative-to” element, or
  2. We have to use JS to retarget dynamically

…which is exactly what we wanted to avoid in the first place, no?

1 Like

The original use case was focused on positioning one element against another and that cannot be adequately accomplished today (outside of stylistically positioning with JS). Using tooltips, popups, modals, etc. outside of JavaScript has never been a use case for me (as they’re typically a reaction to some DOM event) so I could keep things DRY by simply switching the target Id.

When it comes to positioning the element, stuff we have (directional properties, media queries, transforms) and stuff that’s in the works (container queries) looks like it’d pretty much accomplish what I needed.

The suggested syntax accomplished everything I needed, would be super easy to implement for the end developer, and has a track record in existing browsers.

As other use cases are added/considered my suggested syntax probably won’t be all things for all people and that’s why it’s awesome to see everyone commenting. They have more cases from a variety of different perspectives.

If you have another use case in mind, please link it up or post it here! Ultimately, any additional flexibility the spec could give us in this area is a good thing.

When you say this it makes me think you’ve got a list of items with counters, tooltips, etc. I’ve been able to accomplish this effect with a combination of position: relative/absolute; as outlined in one of my earlier posts. If you have other use cases, please link or add them to the thread.

Sort of. My main goal was to eliminate styling in my JavaScript. Using JavaScript to change target Id’s or toggling classes on and off in response to DOM events works for me. Did you have a specific use case in mind where you wouldn’t want to use JS?

Hopefully that makes sense! As long as I can accomplish my simple use case I’ll be happy with anything :smile:

That’s an interesting remark, you often need IDs in many UI components to be accessible.

In the example associated with this proposal, it would be hard to uniquely identify the appropriate element the dialog (in open mode as the dialog presentation available via showModal() would be irrelevant here) should be relative to.

An ID would be needed for the dialog be associated with the correct element as far as what its information is relevant to. For similar reasons, WAI-ARIA attributes primary only accepts IDs.

In general, seemingly classes & other selectors much less specific than ID would need things like the content CSS property to be much more powerful across all browsers for it to be worthwhile to allow things that traditionally only accepted IDs to also accommodate them.

2 Likes

Using CSS for layouting and JS just to target the element is surely better and less error prone than using JS for everything. However, if I use JS to target the element, it’s still a bit complicated. I would need to generate a unique ID, assign it to element1 and then add dynamically the required position styling to element2.

Wouldn’t it be more helpful to have IDs that are only valid within a specific scope? Or is the Shadow DOM just the thing we’re searching for?

How about something like this?

<div class="wrapper">
    <div class="tooltip">Tooltip Text</div>
    <input type="text">
</div>

<style>
    .tooltip {
        position: relative-to(input:focus);
    }
</style>

When the input isn’t in focus, the match fails and the tooltip reverts to display: none.

Of course, this would require that the browser only match to elements with a common ancestor, as previously discussed—but if it works then you could just have the one rule apply to an entire set of tooltips on a form.

You’ve got me here. I mentioned earlier that pseudo states would be a powerful tool to have in the spec:

I think it’ll all come down to performance and what browser makers can allow. Maybe just allow a single selector with an optional pseudo state? From there, any selector that matches multiple elements could automatically imply :first-of-type. What would you think about that?

.tooltip {
    /* Perfectly Valid */
    position: relative-to(input:focus);
    position: relative-to(.input:invalid);
    position: relative-to(#input);
    position: relative-to([type="text"]:disabled);
    position: relative-to(*:hover);

    /* Just no... */
    position: relative-to(*.list-item:not(.blue):not(.red):not(.green):not(.yellow) + *:last-child);
}

I don’t think that DRY needs to apply to rendered code (ie HTML)

1 Like

:+1: Something along those lines might actually solve all the issues—if the browser finds multiple matches, it goes with the first/closest and ignores the others. The spec would need rules to define “closest”, but once that’s done it opens up the possibility of using just about any kind of selector.

We would probably need something like that for hover states anyway since they propagate up the tree.

1 Like

I would think it’s pretty good, which is why I proposed the function work that way three days ago.