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


#9

In the case where restrictions would need to apply, I’m just trying to wrap my head around this statement:

Unrelated: would NOT work

Since <dialog> and #target are not related, the position does not apply.

Siblings: This would work

Since <dialog> and #target are siblings it works as expected

Distant Relatives: would NOT work??

Since <dialog> and #target are not direct relatives, the position does not apply.

Would these statements be accurate? If there are performance implications I completely understand, but the ideal would be for that all three of these situations work.


#10

Assuming that what I said was accurate (that we could do element-based positioning if the target was in the same or a descendant containing block), then all three of your examples would work. Here’s an example that wouldn’t work:

<body>
  <div id='target'>...</div>
  <div style="position: relative;">
    <dialog style="position: element(#target);">...</dialog>
  </div>
</body>

This would break because the dialog’s containing block is generated by its parent, and the target is not a descendant of that element.


#11

Perfect, got it. Wasn’t nearly as restricting as I was thinking. Thanks for clearing that up.


#12

Really glad this got brought up, and I like your solution too. It addresses a problem I think most of us have faced many times. My only gripe is that it makes you either use inline styles or tie yourself to DOM ids in the CSS to be able to use it, which puts some limits on how dynamic it can be. That’s a hard thing to get around, though. One way to decouple it a bit and maintain SoC could be to allow something like:

<body>
  <div id="target"></div>
  <dialog class="attached" reference="target"></dialog>
  <style>
    .attached {
      position: reference;
    }
  </style>
</body>

Just spitballing here to get the idea across. Could also use the attr() function with a custom attribute or something like that, potentially.


#13

I’m all for syntax reform (I just pulled a usable example from an existing feature), but I’m not sure I’m on board with your suggestion. I’ve always felt that there’s a little bit of an illusion when it comes to full SoC with our front-end code. Our JS and CSS files will always need to be somewhat aware of the DOM structure (either through tags, attributes or classes/ids) in order to operate on it.

Your example relies on a reference attribute and an attached class to tie your two elements together. At first blush that may seem decoupled, but ultimately, to me, it feels less maintainable and verbose for the end user. I have to set two values to accomplish the same thing I would be able to do with the former syntax in one value.

If we were trying to convey semantic meaning behind this, I think utilizing an attribute would be a good idea. We could treat it like the for attribute and have it be a part of UA stylesheets or something. But I see this as purely presentational, so I think a pure CSS solution is ideal here.


#14

What about changing element() to be something like first-sibling() or first-ancestor() (or I guess first-descendant(), though I thought lookahead CSS rules were a no-go), where it refers to the first element (along a search path relative to the element, within the rules Tab stated) matching a given CSS selector (not just ID selectors)?

I imagine such an ancestor search could have perf problems, but there’s probably some way to sidestep that, eg. by using values with more constrained search paths (like maybe previous for the sibling element immediately before this one).

Also, for that reason, I think the selector should be a second word of the property, with the first defining the relation to the selected element like relative-to (with other possible values, as I mentioned above, like before or after).


#15

Based on what I’ve seen over at Bootstrap, supporting (or aiding implementation of) robust full-featured positioning of tooltip-ish elements is a tad more complicated, although this is certainly a step in the right direction.

Some complications / use-cases to consider:

(A) Say the tooltip is above the element. If the user scrolls down so that the element is at the very top of the screen, my tooltip is now partially/totally outside the viewport and not visible. I want my tooltip to “flip” to be below the element instead. Strawman syntax just to give some examples of what I’m talking about:

  • relative-placement: top; = tooltip always goes above element, regardless of whether that puts it outside the viewport
  • relative-placement: auto(top, right, bottom); = tooltip goes above element, unless that would put it outside of the viewport, otherwise the tooltip goes on the right side of the element, otherwise the tooltip goes below the element; if all of those placements are outside the viewport, then go above since top is first in the list. Authors are advised to include all available directions in their auto preference list.

(B) Say the element is a <span> with multiple rendering boxes cut across two lines (example visual: http://imgur.com/ta68YjM.png). Where does the tooltip get positioned? Most users feel that placing it over the empty space between the two boxes feels pretty weird, although that’s the easiest behavior to opt for.

© You probably want display:none to propagate to any attached elements too?


#16

It might be good to ping the Tether folks and ask them what CSS feature(s) would make their lives easier. (Or to at least mine their docs for inspiration.)


#17

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.


#18

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.


#19

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?


#20

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;
}

#21

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.


#22

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.


#23

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;
}

#24

Say there are multiple elements in .card.


#25

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.


#26

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:


#27

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?


#28

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?