Navigation

v0.0.4
Application blocks

Table view

A table with its search, filters, selection and pages.

A table view is a table with what it takes to work in one: a title and the main action, a filter and a sort, selection, an action on each row, and pages. Each of those is a component; the block is how they sit together on one sheet.

Table view

<section class="sheet container-inline flex flex-col overflow-hidden" aria-labelledby="tv-title">  <header class="flex flex-wrap items-center justify-between gap-sm p-md">    <div class="flex flex-col gap-3xs">      <h2 class="heading heading-sm" id="tv-title">Projects</h2>      <p class="m(0) fg-text-muted">12 projects in Atlas Labs</p>    </div>    <button type="button" class="btn btn-primary btn-sm">      <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 5v14M5 12h14" />      </svg>New project</button>  </header>  <div class="flex flex-wrap items-center gap-xs px-md pb-md">    <label class="input-group input-group-sm grow(1) basis(200) max-w(320)">      <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 11a7 7 0 1 0 14 0a7 7 0 0 0-14 0M20 20l-4-4" />      </svg>      <input class="input-group-field" type="search" placeholder="Filter projects" aria-label="Filter projects">    </label>    <div class="flex flex-wrap gap-xs" role="group" aria-label="Status">      <label class="chip">        <input type="checkbox" class="chip-input" name="status" value="ready">Ready</label>      <label class="chip">        <input type="checkbox" class="chip-input" name="status" value="building">Building</label>      <label class="chip">        <input type="checkbox" class="chip-input" name="status" value="failed">Failed</label>    </div>    <select class="select select-raised select-sm ml-auto" aria-label="Sort by">      <option class="select-option">Last deployed</option>      <option class="select-option">Name</option>      <option class="select-option">Region</option>    </select>  </div>  <div class="scroll-area scroll-area-inline bt(1) bt-solid b-line-hair">    <table class="table table-interactive">      <thead>        <tr class="table-row">          <th class="table-heading" scope="col">            <input type="checkbox" class="checkbox" aria-label="Select all projects">          </th>          <th class="table-heading" scope="col">Project</th>          <th class="table-heading" scope="col">Status</th>          <th class="table-heading" scope="col">Region</th>          <th class="table-heading" scope="col">Last deployed</th>          <th class="table-heading" scope="col">            <span class="sr-only">Actions</span>          </th>        </tr>      </thead>      <tbody>        <tr class="table-row">          <td class="table-cell">            <input type="checkbox" class="checkbox" aria-label="Select Borealis">          </td>          <th class="table-cell table-primary text-left" scope="row">Borealis            <span class="block fg-text-muted font-regular">borealis.app</span>          </th>          <td class="table-cell">            <span class="badge badge-info badge-dot">Building</span>          </td>          <td class="table-cell">Frankfurt</td>          <td class="table-cell">4 minutes ago</td>          <td class="table-cell text-right">            <button type="button" class="btn btn-quiet btn-square btn-sm" popovertarget="tv-row-1" aria-label="Actions for Borealis">              <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 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M11 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M18 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0" />              </svg>            </button>            <div class="menu menu-end" id="tv-row-1" popover>              <a class="menu-item" href="#">Open</a>              <button type="button" class="menu-item" popovertarget="tv-row-1" popovertargetaction="hide">Redeploy</button>              <hr class="menu-separator">              <button type="button" class="menu-item menu-item-danger" popovertarget="tv-row-1" popovertargetaction="hide">Delete</button>            </div>          </td>        </tr>        <tr class="table-row" aria-selected="true">          <td class="table-cell">            <input type="checkbox" class="checkbox" aria-label="Select Atlas" checked>          </td>          <th class="table-cell table-primary text-left" scope="row">Atlas            <span class="block fg-text-muted font-regular">atlas.dev</span>          </th>          <td class="table-cell">            <span class="badge badge-success badge-dot">Ready</span>          </td>          <td class="table-cell">Tokyo</td>          <td class="table-cell">2 hours ago</td>          <td class="table-cell text-right">            <button type="button" class="btn btn-quiet btn-square btn-sm" popovertarget="tv-row-0" aria-label="Actions for Atlas">              <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 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M11 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M18 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0" />              </svg>            </button>            <div class="menu menu-end" id="tv-row-0" popover>              <a class="menu-item" href="#">Open</a>              <button type="button" class="menu-item" popovertarget="tv-row-0" popovertargetaction="hide">Redeploy</button>              <hr class="menu-separator">              <button type="button" class="menu-item menu-item-danger" popovertarget="tv-row-0" popovertargetaction="hide">Delete</button>            </div>          </td>        </tr>        <tr class="table-row" aria-selected="true">          <td class="table-cell">            <input type="checkbox" class="checkbox" aria-label="Select Cinder" checked>          </td>          <th class="table-cell table-primary text-left" scope="row">Cinder            <span class="block fg-text-muted font-regular">cinder.io</span>          </th>          <td class="table-cell">            <span class="badge badge-success badge-dot">Ready</span>          </td>          <td class="table-cell">Virginia</td>          <td class="table-cell">Yesterday</td>          <td class="table-cell text-right">            <button type="button" class="btn btn-quiet btn-square btn-sm" popovertarget="tv-row-2" aria-label="Actions for Cinder">              <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 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M11 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M18 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0" />              </svg>            </button>            <div class="menu menu-end" id="tv-row-2" popover>              <a class="menu-item" href="#">Open</a>              <button type="button" class="menu-item" popovertarget="tv-row-2" popovertargetaction="hide">Redeploy</button>              <hr class="menu-separator">              <button type="button" class="menu-item menu-item-danger" popovertarget="tv-row-2" popovertargetaction="hide">Delete</button>            </div>          </td>        </tr>        <tr class="table-row">          <td class="table-cell">            <input type="checkbox" class="checkbox" aria-label="Select Drift">          </td>          <th class="table-cell table-primary text-left" scope="row">Drift            <span class="block fg-text-muted font-regular">drift.so</span>          </th>          <td class="table-cell">            <span class="badge badge-danger badge-dot">Failed</span>          </td>          <td class="table-cell">Sydney</td>          <td class="table-cell">Yesterday</td>          <td class="table-cell text-right">            <button type="button" class="btn btn-quiet btn-square btn-sm" popovertarget="tv-row-3" aria-label="Actions for Drift">              <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 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M11 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M18 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0" />              </svg>            </button>            <div class="menu menu-end" id="tv-row-3" popover>              <a class="menu-item" href="#">Open</a>              <button type="button" class="menu-item" popovertarget="tv-row-3" popovertargetaction="hide">Redeploy</button>              <hr class="menu-separator">              <button type="button" class="menu-item menu-item-danger" popovertarget="tv-row-3" popovertargetaction="hide">Delete</button>            </div>          </td>        </tr>        <tr class="table-row">          <td class="table-cell">            <input type="checkbox" class="checkbox" aria-label="Select Ember">          </td>          <th class="table-cell table-primary text-left" scope="row">Ember            <span class="block fg-text-muted font-regular">ember.dev</span>          </th>          <td class="table-cell">            <span class="badge badge-success badge-dot">Ready</span>          </td>          <td class="table-cell">São Paulo</td>          <td class="table-cell">3 days ago</td>          <td class="table-cell text-right">            <button type="button" class="btn btn-quiet btn-square btn-sm" popovertarget="tv-row-4" aria-label="Actions for Ember">              <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 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M11 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M18 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0" />              </svg>            </button>            <div class="menu menu-end" id="tv-row-4" popover>              <a class="menu-item" href="#">Open</a>              <button type="button" class="menu-item" popovertarget="tv-row-4" popovertargetaction="hide">Redeploy</button>              <hr class="menu-separator">              <button type="button" class="menu-item menu-item-danger" popovertarget="tv-row-4" popovertargetaction="hide">Delete</button>            </div>          </td>        </tr>        <tr class="table-row">          <td class="table-cell">            <input type="checkbox" class="checkbox" aria-label="Select Fathom">          </td>          <th class="table-cell table-primary text-left" scope="row">Fathom            <span class="block fg-text-muted font-regular">fathom.app</span>          </th>          <td class="table-cell">            <span class="badge badge-success badge-dot">Ready</span>          </td>          <td class="table-cell">London</td>          <td class="table-cell">Last week</td>          <td class="table-cell text-right">            <button type="button" class="btn btn-quiet btn-square btn-sm" popovertarget="tv-row-5" aria-label="Actions for Fathom">              <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 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M11 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0M18 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0" />              </svg>            </button>            <div class="menu menu-end" id="tv-row-5" popover>              <a class="menu-item" href="#">Open</a>              <button type="button" class="menu-item" popovertarget="tv-row-5" popovertargetaction="hide">Redeploy</button>              <hr class="menu-separator">              <button type="button" class="menu-item menu-item-danger" popovertarget="tv-row-5" popovertargetaction="hide">Delete</button>            </div>          </td>        </tr>      </tbody>    </table>  </div>  <footer class="flex flex-wrap items-center justify-between gap-sm p-md bt(1) bt-solid b-line-hair">    <p class="m(0) fg-text-muted">2 of 12 selected</p>    <nav aria-label="Pagination">      <ol class="pagination">        <li>          <a class="btn btn-quiet btn-square btn-sm" aria-disabled="true" aria-label="Previous page">            <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="m15 6-6 6 6 6" />            </svg>          </a>        </li>        <li>          <a class="btn btn-quiet btn-square btn-sm" href="#" aria-label="Page 1" aria-current="page">1</a>        </li>        <li>          <a class="btn btn-quiet btn-square btn-sm" href="#" aria-label="Page 2">2</a>        </li>        <li>          <a class="btn btn-quiet btn-square btn-sm" href="#" aria-label="Next page">            <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="m9 6 6 6-6 6" />            </svg>          </a>        </li>      </ol>    </nav>  </footer></section>

Blocks are markup to copy. They take the page's own text color and size; the previews set fg-text-body text-sm on the body. Each block is a size container, so it reflows by its own width: in a wide page, a sidebar's worth of room or a dialog.

#What is in it

  • One sheet. The bands are parted by hairlines, bt(1) bt-solid b-line-hair, not by cards inside the card.
  • The header is a split. A title, a count and the primary action: flex flex-wrap items-center justify-between, so the action drops under the title when the sheet is narrow.
  • The filters are a wrapping row. A search input group, filter chips and a raised select; ml-auto sends the sort to the end. No chip is on, so every status shows.
  • The filter grows, to a point. grow(1) basis(200) max-w(320): it takes the room in the row, never less than 200px before wrapping, never more than 320px.
  • The table scrolls in its own band. It stands in a scroll area with scroll-area-inline, so a narrow sheet scrolls the table and not the page, and the edge fades where columns continue.
  • Rows are in the order the sort names. "Last deployed" is chosen, and the newest deployment is first.
  • Selected rows say so. aria-selected="true" on a table-row gives it the selected tone; the checkbox in it is checked. Your script keeps the two in step, and sets the header checkbox's indeterminate property when some rows are selected.
  • Row actions hide in a menu behind a quiet button at the end, named for its row: "Actions for Atlas".
  • The footer is a split: the selection count and a pagination. On the first page "Previous" has aria-disabled="true" and no href, since a link cannot be disabled.

#Acting on a selection

When rows are selected, show what can be done to all of them in a floating toolbar: the count, the actions, and a way to clear. Place it with sticky bottom(16) at the foot of the sheet, or fixed at the foot of the viewport; show it while the selection is not empty.

Acting on a selection

<div class="toolbar toolbar-floating flex-wrap justify-center" role="toolbar" aria-label="2 projects selected">  <span class="px-xs fg-text-strong">2 selected</span>  <span class="toolbar-separator" role="separator" aria-orientation="vertical"></span>  <button type="button" class="btn btn-quiet btn-sm">    <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="M5 14c0-5 4-10 11-10 0 7-5 11-10 11zM5 14l-2 1 1 3M10 19l-1 2-3-1M14.5 9.5h.01" />    </svg>Redeploy</button>  <button type="button" class="btn btn-quiet btn-sm">    <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 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 11l5 5 5-5M12 4v12" />    </svg>Export</button>  <button type="button" class="btn btn-quiet btn-sm btn-danger">    <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 7h16M10 11v6M14 11v6M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12M9 7V4h6v3" />    </svg>Delete</button>  <span class="toolbar-separator" role="separator" aria-orientation="vertical"></span>  <button type="button" class="btn btn-quiet btn-square btn-sm" aria-label="Clear the selection">    <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="M6 6l12 12M18 6 6 18" />    </svg>  </button></div>

#What needs a script

Filtering, sorting, selecting all and paging change data, so they belong to your application or your server. The block gives each of them the right element to listen to: a search field, checkboxes, a <select>, links. Without a script, wrap the bands in a <form method="get"> and the filters become a query string the server can answer.

#Accessibility

  • The sheet is a <section> named by its title.
  • Every checkbox has a name that includes its row: "Select Atlas". The header's is "Select all projects".
  • The actions column has a heading for assistive technology, visually hidden.
  • The pagination is a <nav> with a label, and the current page carries aria-current="page".
  • The first cell of each row is a <th scope="row">, so a cell is announced with its project.
  • The table scrolls sideways in a scroll area. It holds checkboxes and buttons, so the keyboard reaches it through them; a table with nothing focusable in it needs tabindex="0", a role and a name on the scroll area.