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.

    iOS sign-up form + keyboardLayoutGuide

    A UIKit-style sign-up form built as a constraint chain, with the primary button anchored data-⚓️-side="above" a simulated keyboardLayoutGuide so it rides the keyboard up.

    Full page
    <header class="page-intro">
      <h1>Sign-up form + keyboardLayoutGuide</h1>
      <p>
        Every field is anchored below the previous one (<code>data-⚓️-below</code>, 8pt standard spacing), with
        equal widths via <code>data-⚓️-sync-width</code>. The primary button hangs off a simulated
        <code>keyboardLayoutGuide</code> with <code>data-⚓️-side="above"</code>, so it rides the keyboard up.
        Wide screens switch to the regular size class: two columns, <code>--⚓️-mult-x: 0.5</code>, hardware keyboard.
      </p>
    </header>
    
    <div class="screen" id="scr" data-⚓️-root data-⚓️-name="--scr" data-kb="hidden" data-mode="signup">
    
      <!-- safeAreaLayoutGuide (top): the status bar region -->
      <div class="status-bar" id="safe" data-⚓️-name="--safe" data-⚓️-pin-top data-⚓️-pin-x aria-hidden="true">
        <span class="time">9:41</span>
        <span class="island"></span>
        <span class="glyphs"><i class="sig"></i><i class="wifi"></i><i class="batt"></i></span>
      </div>
    
      <!-- keyboardLayoutGuide: top == safeArea.bottom when hidden, == keyboard.top when shown -->
      <div class="kbd-guide" id="kbd" data-⚓️-name="--kbd" data-⚓️-pin-bottom data-⚓️-pin-x aria-hidden="true"></div>
    
      <!-- the simulated software keyboard (keys are built by script.js) -->
      <div class="keyboard" id="kb" data-⚓️-pin-bottom data-⚓️-pin-x aria-hidden="true">
        <div class="kb-inner">
          <div class="kb-suggest" id="kbSuggest"></div>
          <div class="kb-keys" id="kbKeys"></div>
        </div>
      </div>
    
      <!-- navigation bar: navBar.top = safeArea.bottom -->
      <nav class="nav-bar" id="nav" data-⚓️-name="--nav" data-⚓️-to="--safe" data-⚓️-below data-⚓️-pin-x>
        <button class="bar-btn" id="cbtn" data-⚓️-name="--cbtn" type="button" aria-pressed="false" aria-controls="inspector">Constraints</button>
        <span class="nav-title" id="navTitle">Create account</span>
        <button class="bar-btn trailing" id="kbtn" type="button" aria-pressed="false" title="Software keyboard (regular width only)">Keyboard</button>
      </nav>
    
      <!-- scrollView.top = safeArea.bottom, scrollView.bottom = keyboardLayoutGuide.top -->
      <form class="scroll" id="sv" data-⚓️-below="--safe" data-⚓️-above="--kbd" data-⚓️-pin-x novalidate>
    
        <!-- layoutMarginsGuide (20pt each side, zero height) -->
        <div class="guide" id="margins" data-⚓️-name="--margins" data-⚓️-pin-top data-⚓️-pin-x></div>
    
        <h2 class="large-title" id="title" data-⚓️-name="--title" data-⚓️-pin-top data-⚓️-align-leading="--margins">Create account</h2>
    
        <p class="subtitle col" id="sub" data-⚓️-name="--sub"
           data-⚓️-below="--title" data-⚓️-align-leading="--title" data-⚓️-sync-width="--margins" data-⚓️-transition="glide">
          One login for every dashboard. Tap a field to raise the keyboard.
        </p>
    
        <div class="segmented col" id="seg" role="radiogroup" aria-label="Mode" data-⚓️-name="--seg"
             data-⚓️-below="--sub" data-⚓️-align-leading="--margins" data-⚓️-sync-width="--margins" data-⚓️-transition="glide">
          <span class="seg-pill" id="pill" data-⚓️-fill="--seg-a" data-⚓️-transition="glide" aria-hidden="true"></span>
          <button type="button" class="seg-btn" id="segA" data-⚓️-name="--seg-a" role="radio" aria-checked="true" data-mode="signup">Sign up</button>
          <button type="button" class="seg-btn" id="segB" data-⚓️-name="--seg-b" role="radio" aria-checked="false" data-mode="login">Log in</button>
        </div>
    
        <h3 class="section-header" id="hdrp" data-⚓️-name="--hdrp" data-⚓️-below="--seg" data-⚓️-align-leading="--margins" data-⚓️-transition="glide">Profile</h3>
    
        <label class="field col" id="fName" data-⚓️-name="--name"
               data-⚓️-below="--hdrp" data-⚓️-align-leading="--margins" data-⚓️-sync-width="--margins" data-⚓️-transition="glide">
          <span class="field-label">Name</span>
          <input name="name" autocomplete="name" placeholder="Required" enterkeyhint="next">
        </label>
    
        <label class="field" id="fEmail" data-⚓️-name="--email"
               data-⚓️-below="--name" data-⚓️-align-leading="--name" data-⚓️-sync-width="--name" data-⚓️-transition="glide">
          <span class="field-label">Email</span>
          <input name="email" type="email" autocomplete="email" placeholder="you@example.com" enterkeyhint="next">
        </label>
    
        <label class="field" id="fPw" data-⚓️-name="--pw"
               data-⚓️-below="--email" data-⚓️-align-leading="--email" data-⚓️-sync-width="--email" data-⚓️-transition="glide">
          <span class="field-label">Password</span>
          <input name="password" type="password" autocomplete="new-password" placeholder="8+ characters" enterkeyhint="go">
        </label>
    
        <p class="footnote" id="hint" data-⚓️-name="--hint"
           data-⚓️-below="--pw" data-⚓️-align-leading="--pw" data-⚓️-sync-width="--pw" data-⚓️-transition="glide">
          Use 8 or more characters with a mix of letters, numbers and symbols.
        </p>
    
        <h3 class="section-header" id="hdra" data-⚓️-name="--hdra" data-⚓️-below="--hint" data-⚓️-align-leading="--margins" data-⚓️-transition="glide">Account preview</h3>
    
        <div class="group" id="summary" data-⚓️-name="--summary"
             data-⚓️-below="--hdra" data-⚓️-align-leading="--hdra" data-⚓️-sync-width="--margins" data-⚓️-transition="glide">
          <!-- detail precedes label in the DOM so the label may anchor to it when the value wins -->
          <div class="row" data-⚓️-root data-⚓️-scope="all">
            <span class="detail" data-⚓️-name="--val" data-⚓️-after="--lbl" data-⚓️-pin-trailing data-⚓️-pin-y id="dName">Not set</span>
            <span class="lbl" data-⚓️-name="--lbl">Display name</span>
          </div>
          <div class="row" data-⚓️-root data-⚓️-scope="all">
            <span class="detail" data-⚓️-name="--val" data-⚓️-after="--lbl" data-⚓️-pin-trailing data-⚓️-pin-y id="dEmail">Not set</span>
            <span class="lbl" data-⚓️-name="--lbl">Email</span>
          </div>
          <div class="row" id="rowTz" data-⚓️-root data-⚓️-scope="all">
            <span class="detail" data-⚓️-name="--val" data-⚓️-after="--lbl" data-⚓️-pin-trailing data-⚓️-pin-y>Pacific Daylight Time (Los Angeles, United States)</span>
            <span class="lbl" data-⚓️-name="--lbl">Time zone for reminders</span>
          </div>
          <label class="row" data-⚓️-root>
            <span class="lbl">Value resists compression</span>
            <input type="checkbox" role="switch" class="switch" id="prio" data-⚓️-pin-trailing>
          </label>
        </div>
    
        <p class="footnote" id="footer" data-⚓️-name="--footer"
           data-⚓️-below="--summary" data-⚓️-align-leading="--summary" data-⚓️-sync-width="--summary" data-⚓️-transition="glide">
          Off: the label has the higher compression resistance (751), so long values truncate first.
          On: the value wins and the label truncates.
        </p>
    
        <!-- contentInset.bottom: keeps the last rows scrollable clear of the floating button -->
        <div class="spacer" data-⚓️-below="--hint" data-⚓️-pin-leading></div>
        <div class="spacer" data-⚓️-below="--footer" data-⚓️-pin-leading></div>
      </form>
    
      <!-- the button bar and button: bottom = keyboardLayoutGuide.top (− 8 for the button) -->
      <div class="cta-bar" id="ctabar" data-⚓️-to="--kbd" data-⚓️-side="above" data-⚓️-pin-x aria-hidden="true"></div>
      <button class="cta" id="cta" type="submit" form="sv" data-⚓️-name="--cta" data-⚓️-to="--kbd" data-⚓️-side="above" data-⚓️-pin-x>Create account</button>
    
      <!-- home indicator: centerX = superview.centerX, bottom = superview.bottom − 8 -->
      <span class="home-indicator" data-⚓️-pin-bottom data-⚓️-pin-x aria-hidden="true"></span>
    </div>
    
    <!-- Constraints inspector: a sheet on compact width, a popover off the bar button on regular -->
    <aside class="inspector" id="inspector" popover="manual" aria-labelledby="inspTitle"
           data-⚓️-to="--scr" data-⚓️-align-bottom data-⚓️-stretch-x>
      <header class="insp-head">
        <h2 id="inspTitle">Constraints</h2>
        <button type="button" class="bar-btn" id="inspClose">Done</button>
      </header>
      <p class="insp-note">Hover or focus a group to highlight the view. Dashed outlines are the named anchors.</p>
      <ul class="insp-list" id="inspList"></ul>
    </aside>

    data-⚓️-abovedata-⚓️-afterdata-⚓️-align-bottomdata-⚓️-align-leadingdata-⚓️-belowdata-⚓️-filldata-⚓️-namedata-⚓️-pin-bottomdata-⚓️-pin-leadingdata-⚓️-pin-topdata-⚓️-pin-trailingdata-⚓️-pin-xdata-⚓️-pin-ydata-⚓️-rootdata-⚓️-scopedata-⚓️-sidedata-⚓️-stretch-xdata-⚓️-sync-widthdata-⚓️-todata-⚓️-transition

    Copied