Scoping Lessons and Anchored Menus

Scoping Lessons and Anchored Menus

3 Min Read

I had this (bad) idea.

It’s related to popovers and anchor-positioned menus. I love this pairing: with only HTML and CSS, we can make a button that opens/closes anything we want. A tooltip or a menu is a wonderful use-case.

This isn’t a terribly difficult thing to do, but you have to remember a bunch of stuff and put certain unique values on certain elements exactly.

1. Remember the right `command` attribute value on the button
2. Put a unique `id` on the menu.
3. Match up the `commandfor` attribute on the button to that id.
4. Make sure the button has a unique `anchor-name`.
5. Match up the `position-anchor` on the menu to that unique name.
6. Make sure you’re using good anchor positioning fallbacks.

Menu

That feels like kind of a lot to remember and get right.

Here’s my (bad) idea: make a quick “ that does those things. On the surface, maybe that makes sense. It did to me. But the ridiculous part is that now it introduces JavaScript into things in a place we didn’t need JavaScript before, which makes it more fragile (and potentially render later) than it would without.

So I’m not advocating for use here, but I did learn some things along the way that I found interesting and worth sharing.

## Light DOM Web Component

I called it “ just to be short and slightly cheeky.

“`javascript
import { LitElement, html, css } from ‘lit’;
import { customElement, property } from ‘lit/decorators.js’;

@customElement(‘a-menu’)
export class AMenu extends LitElement {
@property({ attribute: ‘button-name’ }) buttonName = ‘Menu’;

private menuId = `menu-${Math.random().toString(36).substr(2, 9)}`;

createRenderRoot() {
return this;
}

firstUpdated() {
const menu = this.querySelector(‘menu’);
if (menu) {
menu.setAttribute(‘popover’, ‘auto’);
menu.id = this.menuId;
}
}

render() {
return html`
a-menu {
display: inline-block;

button {
position-anchor: –menu-button-${this.menuId};
}

menu {
position-anchor: –menu-button-${this.menuId};
position-area: block-end span-inline-start;
position-try: flip-block, flip-inline, flip-block flip-inline;
inset: unset;
margin: 0;
}
}

`;
}
}
“`

Then usage is as simple as this:

  • Notice we **don’t** need to:

    1. Remember a unique ID on the menu.
    2. Remember the popover commands.
    3. Remember to attach an `anchor-name` or `position-anchor` to put the menu next to the button.

    ### … but now we have a problem.

    Even though we’re putting a unique ID on the menu and using unique custom idents on the anchors, the **first** menu will open in the position of the **last** button. Why? Because we’re using the Light DOM here, and the last generic `a-menu menu {}` selector will **override** the first one, making **all** buttons/menus use the values of the last one.

    ## Using @scope

    It occurred to me that a potential fix here is the newfangled `@scope` in CSS. If we updated the style block to be this instead:

    “`javascript
    @scope {
    :scope {
    display: inline-block;

    button {
    anchor-name: –menu-button-${this.menuId};
    }

    menu {
    position-anchor: –menu-button-${this.menuId};
    position-area: block-end span-inline-start;
    position-try: flip-block, flip-inline, flip-block flip-inline;
    inset: unset;
    margin: 0;
    border: 0;

    You might also like