Navigation

v0.0.4
Application pages

Settings

A short navigation beside the sections of a long form.

A settings page is a long form with a short navigation beside it. The navigation stays put while the sections scroll; the form decides for itself when its headings stand beside their fields and when they stack.

Settings

<div class="page bg-surface-base fg-text-body text-sm">  <a class="skip" href="#settings-main">Skip to content</a>  <header class="navbar">    <a class="navbar-brand" href="#">      <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">        <path d="M12 3a9 9 0 1 0 0 18a9 9 0 0 0 0-18M12 3a9 9 0 0 0 0 18" />      </svg>Atlas</a>    <nav class="navbar-nav" aria-label="Main">      <a class="navbar-link" href="#" aria-current="page">Product</a>      <a class="navbar-link" href="#">Pricing</a>      <a class="navbar-link" href="#">Docs</a>      <a class="navbar-link" href="#">Changelog</a>    </nav>    <div class="navbar-end">      <span class="avatar avatar-sm">        <img class="avatar-image" src="/demo/portrait-1.svg" alt="Ada Lovelace">      </span>      <button type="button" class="btn btn-quiet btn-square btn-sm navbar-toggle" popovertarget="settings-menu" aria-label="Open the menu">        <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">          <path d="M4 8h16M4 16h16" />        </svg>      </button>      <div class="menu menu-end" id="settings-menu" popover>        <a class="menu-item" href="#">Product</a>        <a class="menu-item" href="#">Pricing</a>        <a class="menu-item" href="#">Docs</a>        <a class="menu-item" href="#">Changelog</a>      </div>    </div>  </header>  <main class="grow(1) w-full max-w(1040) mx-auto px-lg py-xl container-inline" id="settings-main" tabindex="-1">    <header class="flex flex-col gap-3xs mb-lg">      <h1 class="heading heading-xl">Settings</h1>      <p class="m(0) fg-text-muted">Manage the Atlas project.</p>    </header>    <nav class="tabs mb-lg hidden@container(--4xl)" aria-label="Settings">      <a class="tabs-item" href="#" aria-current="page">General</a>      <a class="tabs-item" href="#">Members</a>      <a class="tabs-item" href="#">Billing</a>      <a class="tabs-item" href="#">Domains</a>      <a class="tabs-item" href="#">API keys</a>      <a class="tabs-item" href="#">Notifications</a>    </nav>    <div class="flex gap-xl items-start">      <nav class="nav w(200) shrink(0) sticky top(72) hidden flex@container(--4xl)" aria-label="Settings">        <a class="nav-item" href="#" aria-current="page">General</a>        <a class="nav-item" href="#">Members</a>        <a class="nav-item" href="#">Billing</a>        <a class="nav-item" href="#">Domains</a>        <a class="nav-item" href="#">API keys</a>        <a class="nav-item" href="#">Notifications</a>      </nav>      <div class="grow(1) basis(0) min-w(0)">        <form class="form">          <section class="form-section" aria-labelledby="set-project">            <div class="form-heading">              <h2 class="form-title" id="set-project">Project</h2>              <p class="form-description">The name and address people see.</p>            </div>            <div class="form-fields">              <label class="field">Name                <input class="input" name="name" value="Atlas">              </label>              <div class="field">                <label for="set-domain">Address</label>                <div class="input-group">                  <span class="input-group-addon">https://</span>                  <input class="input-group-field" id="set-domain" name="domain" value="atlas">                  <span class="input-group-addon">.atlas.dev</span>                </div>                <span class="field-hint">Changing it breaks existing links after 30 days.</span>              </div>            </div>          </section>          <section class="form-section" aria-labelledby="set-build">            <div class="form-heading">              <h2 class="form-title" id="set-build">Build</h2>              <p class="form-description">How a push becomes a deployment.</p>            </div>            <div class="form-fields">              <div class="form-row">                <label class="field">Framework                  <select class="select" name="framework">                    <option class="select-option">Astro</option>                    <option class="select-option">Next.js</option>                    <option class="select-option">SvelteKit</option>                  </select>                </label>                <label class="field">Node version                  <select class="select" name="node">                    <option class="select-option">24</option>                    <option class="select-option">22</option>                  </select>                </label>              </div>              <label class="check">                <input type="checkbox" class="checkbox" name="previews" checked>Build a preview for every branch</label>              <label class="check">                <input type="checkbox" class="checkbox" name="cancel">Cancel a build when a newer push arrives</label>            </div>          </section>          <div class="form-actions">            <button type="button" class="btn">Discard</button>            <button class="btn btn-primary">Save changes</button>          </div>        </form>      </div>    </div>  </main></div>
At Tablet and Phone the side navigation becomes a row of tabs over the form.

#How it is built

  • A page with one measure. The main region is max-w(1040) mx-auto and a size container, container-inline.
  • Two columns from 56rem. A fixed w(200) navigation and a fluid form, grow(1) basis(0) min-w(0), in a flex row.
  • Two navigations, one on show. Below 56rem of main region the tabs are shown and the nav is hidden; from 56rem it is the other way round, with hidden@container(--4xl) on one and hidden flex@container(--4xl) on the other. The one that is hidden is display: none, so assistive technology meets only one.
  • The form looks after itself. form-section puts each heading beside its fields from 40rem of form and above them below that; form-row and form-actions do the same at their own widths. The page's layout and the form's never need to agree on a breakpoint.

The side navigation is sticky top(72): it stays 16px under the 56px navbar while the sections scroll. That is all "stays put" needs, because the document is what scrolls.

#One page or many

Give each group its own page when saving one should not save another: a "Save changes" button at the foot of a long page is easy to miss, and a reader cannot tell which sections it covers. On one page, save each section by itself or save on change, and say which.

#Accessibility

  • Both navigations carry the same label; only one is in the accessibility tree at a time.
  • Mark the current group with aria-current="page".
  • Each section of the form is a <section> named by its title.