Anchored Auto Layout
npm

Hover or focus a box to measure it. Click its tag, or press ] and [, to pin one. Esc unpins.

    Legend
    • Anchor: a box with data-⚓️-name
    • Placed by anchors: out of flow
    • Gap from the anchor's edge, in px
    • Edges of the box you're inspecting

    Every tag, line and number here is itself placed by data-⚓️-* attributes. JS only measures.

    Chrome and Edge 133+ · MIT

    Layout by relationship.

    Put this below that. Flip it when it hits the edge. Size it from its anchor. CSS anchor positioning, written as HTML attributes. No JavaScript.

    Adopt it, step by stepDocsRecipesPlayground

    One CSS file · 19.9 KB · 37 attributes · MIT

    npm i anchored-auto-layout
    1. 2 · Import@import "anchored-auto-layout";
    2. 3 · Anchor<menu data-⚓️-to="--btn" data-⚓️-below>

    Needs typed attr(): Chrome and Edge 133+. Not in stable Safari yet; Firefox unverified. Built and verified in Chromium. Browser support

    How to use it

    One attribute at a time.

    Each step adds one attribute to the markup. The stage beside it runs the real library on that markup. Start from a page you already have, or from nothing.

    Existing project Adopt it in an existing project: a share menu that a script used to place.

    Just added
    1. Before

      A menu placed by script

      A button, a menu with position: absolute, and a script that measures the button and writes top and left. It runs on load and on resize; anything else that moves the button leaves the menu behind.

      index.html
      <button class="share">Share</button>
      <!-- … the rest of the page … -->
      <menu class="share-menu">…</menu>
      place-menu.js
      function placeMenu() {
        const r = button.getBoundingClientRect();
        menu.style.top = `${r.bottom + scrollY}px`;
        menu.style.left = `${r.right - menu.offsetWidth + scrollX}px`;
      }
      placeMenu();
      addEventListener("resize", placeMenu);
    2. Step 0

      Add the stylesheet

      Install it and import it once. Nothing on the page moves: the library only acts on data-⚓️-* attributes.

      app.css
      /* npm i anchored-auto-layout */
      @import "anchored-auto-layout";
    3. Step 1

      Name the button

      data-⚓️-name makes it an anchor called --share. Names start with two dashes.

      <button class="share"
              data-⚓️-name="--share">Share</button>
      <!-- … the rest of the page … -->
      <menu class="share-menu">…</menu>
    4. Step 2

      Point the menu at it, delete the script

      data-⚓️-to ties the menu to --share and takes it out of flow. With no edge set yet it stays where it would sit in flow, at the end of the page. The script can go.

      <button class="share"
              data-⚓️-name="--share">Share</button>
      <!-- … the rest of the page … -->
      <menu class="share-menu"
            data-⚓️-to="--share">…</menu>
      place-menu.js: delete
      function placeMenu() {
        const r = button.getBoundingClientRect();
        menu.style.top = `${r.bottom + scrollY}px`;
        menu.style.left = `${r.right - menu.offsetWidth + scrollX}px`;
      }
      placeMenu();
      addEventListener("resize", placeMenu);
    5. Step 3

      Hang it below

      data-⚓️-below puts the menu's top edge on the button's bottom edge. No value needed: it reads --share from data-⚓️-to.

      <button class="share"
              data-⚓️-name="--share">Share</button>
      <!-- … the rest of the page … -->
      <menu class="share-menu"
            data-⚓️-to="--share"
            data-⚓️-below>…</menu>
    6. Step 4

      Line up the trailing edges

      data-⚓️-align-trailing matches the right edges, or the left ones in RTL. Back where the script put it, without the script.

      <button class="share"
              data-⚓️-name="--share">Share</button>
      <!-- … the rest of the page … -->
      <menu class="share-menu"
            data-⚓️-to="--share"
            data-⚓️-below
            data-⚓️-align-trailing>…</menu>
    7. Step 5

      Flip when there's no room

      data-⚓️-try="flip-y" lets the browser use the other side when the menu would overflow. Move the toolbar to the bottom and the menu opens above the button.

      <button class="share"
              data-⚓️-name="--share">Share</button>
      <!-- … the rest of the page … -->
      <menu class="share-menu"
            data-⚓️-to="--share"
            data-⚓️-below
            data-⚓️-align-trailing
            data-⚓️-try="flip-y">…</menu>

    From scratch Lay out from scratch: a sidebar, a main column and a loading cover.

    Just added
    1. Step 0

      Add the stylesheet

      Install it and import it once. Nothing on the page moves: the library only acts on data-⚓️-* attributes.

      app.css
      /* npm i anchored-auto-layout */
      @import "anchored-auto-layout";
    2. Step 1

      Name the one block that stays in flow

      Start with plain blocks in source order. main stays in flow and becomes the anchor --main; the rest hang off it.

      <nav class="side">…</nav>
      <main
            data-⚓️-name="--main">…</main>
      <div class="loading">Loading…</div>
    3. Step 2

      Put the nav before main

      data-⚓️-before="--main" puts the nav's trailing edge on main's leading edge. There's no data-⚓️-to here, so the anchor goes in the value. Main keeps a margin for it in your own CSS.

      <nav class="side"
           data-⚓️-before="--main">…</nav>
      <main
            data-⚓️-name="--main">…</main>
      <div class="loading">Loading…</div>
    4. Step 3

      Make it as tall as main

      data-⚓️-stretch-y="--main" runs the nav from main's top edge to its bottom edge. Main grows, the nav grows with it.

      <nav class="side"
           data-⚓️-before="--main"
           data-⚓️-stretch-y="--main">…</nav>
      <main
            data-⚓️-name="--main">…</main>
      <div class="loading">Loading…</div>
    5. Step 4

      Cover main while it loads

      data-⚓️-fill="--main" takes main's position and size on both axes. Same idea for overlays, highlights and drop zones.

      <nav class="side"
           data-⚓️-before="--main"
           data-⚓️-stretch-y="--main">…</nav>
      <main
            data-⚓️-name="--main">…</main>
      <div class="loading"
           data-⚓️-fill="--main">Loading…</div>

    Drag the green anchor, or focus it and use the arrow keys. Pick a side:

    target
    <div data-⚓️-to="--anchor" data-⚓️-side="after" data-⚓️-align-y="center">
    Anchored Auto Layout in 54 seconds.

    What it solves

    Built-in answers for the layout you keep rewriting.

    Live

    Things you stop writing JavaScript for.

    anchor
    target
    Fallback

    data-⚓️-try Flips when there's no room below.

    anchor
    target
    --⚓️-mult-x

    data-⚓️-sync-width Size from the anchor.

    AlphaA
    BetaB
    GammaG
    Cards

    data-⚓️-scope-name Names stay inside each copy.

    Dogfooding

    This site is laid out with it.

    Only <main> is in normal flow. Everything else hangs off it.

    Every page
    <body data-⚓️-root>
      <header data-⚓️-pin-top data-⚓️-pin-x>…</header>
      <nav data-⚓️-before="--page" data-⚓️-stretch-y="--page">…</nav>
      <main data-⚓️-name="--page">…</main>
      <footer data-⚓️-to="--page" data-⚓️-below data-⚓️-stretch-x>…</footer>
    </body>
    nav
    width
    bars
    main
    header
    nav
    footer

    Recipes

    Copy one, change a value.

    All 20 recipes →

    FAQ

    Questions we get asked a lot.

    Does it need JavaScript?

    No. The library is one CSS file and the attributes are read by CSS. The demos on this site use a few lines of script for input, such as dragging the anchor; none of it does layout.

    Which browsers does it work in?

    It needs typed attr(), so Chrome and Edge 133 and up. Safari doesn't ship it yet and Firefox is unverified. Where it's missing, the page falls back to plain flow.

    Does it work with popovers?

    Yes. The Menu on this site is a native popover anchored to its button with data-⚓️-to and data-⚓️-below, and it morphs out of it.

    Where are all the attributes?

    The Reference lists every attribute with a live stage. The Spec is the long form, and llms.txt is the same material for your assistant.

    How big is it?

    19.9 KB of CSS, MIT licensed, with nothing else to install.

    @import "anchored-auto-layout";

    Stop writing JavaScript to put one box next to another.

    npm i anchored-auto-layout

    Get startedOpen the playground

    Copied