Skip to the page
TACTILE UI

Kit part · Dialogs

Drawer

A panel that slides in from any edge of the window, on the native dialog. A face plate bolted to the frame, with a machined grip let into its inner edge to pull it shut by.

Element
<dialog>
Needs
kit.css + kit.mjs
Rules
drawer.rules.md · 6
Finishes
Aluminium · Graphite · Porcelain · Black
Volumes
Tactile · Quiet
On this page
  1. Variants
  2. Every state, at once
  3. API
  4. Markup
  5. Accessibility
  6. How it’s built
  7. Rules
  8. Related parts
01

Variants

Filters

Range, circuits and a minimum draw.

From the right · the default

Menu

Sections of the hub.

From the left

Share this report

A sheet of actions.

From the bottom

Firmware 4.2

A notice that needs an answer.

From the top

Filters

Range, circuits.
ClearShow 42
With a foot · one contrast key
02

Every state, at once

Both finishes, side by side. Hover, pressed and focus are forced with .is-hover, .is-down and .is-focus; checked and disabled are real.

Aluminium

PartRightLeftBottomTop
Edge

Filters

Menu

Share

Notice

Graphite

PartRightLeftBottomTop
Edge

Filters

Menu

Share

Notice

03

API

NameKindValuesWhat it does
.tui-drawerclasson <dialog>The plate: the face token, square along the frame and 16px round on the sides that face the page.
data-edgeattribute"right" · "left" · "bottom" · "top"Which edge it comes from. Right is the default.
--sm · --lg · --edge-sizeclass · CSS property320 · 540 · 400 pxHow far it reaches: width for the sides, the most height for top and bottom.
.tui-drawer__head · __titleclass—A head with the title (an h2) and one flat icon key to close.
.tui-drawer__bodyclass—The part that scrolls. Add .tui-scroll-area for the rail.
.tui-drawer__footclass—Keys, right-aligned. The one contrast key of the drawer goes here.
.tui-drawer__handleclassaria-hiddenThe machined grip on the inner edge. Drag it towards the frame to close.
data-drawer-open · data-drawer-closeattributea dialog id · a return valueOn any element: opens that drawer; closes the one it is in, with an optional returnValue.
data-lockattributeon the dialogA click on the scrim no longer closes it. Escape still does; intercept cancel to stop that too.
openDrawer(d) · closeDrawer(d, value)functionkit.mjsshowModal() and close(), safe to call when already in that state.
drawerDismiss({ offset, size, ms })functionkit.mjsWhether a drag ended far enough (over 35% of the size) or fast enough to close it. Pure.
EscapekeyboardnativeCloses it; focus returns to the key that opened it.
Tab · Shift TabkeyboardnativeCycle inside it: the page behind is inert while it is open.
04

Markup

<button class="tui-key" data-drawer-open="filters">Filters</button>

<dialog class="tui-drawer" id="filters" data-edge="right" aria-labelledby="filters-t">
  <div class="tui-drawer__handle" aria-hidden="true"></div>
  <header class="tui-drawer__head">
    <h2 class="tui-drawer__title" id="filters-t">Filters</h2>
    <button class="tui-key tui-key--icon tui-key--flat" data-drawer-close aria-label="Close filters">…</button>
  </header>
  <div class="tui-drawer__body tui-scroll-area">…</div>
  <footer class="tui-drawer__foot">
    <button class="tui-key tui-key--flat" data-drawer-close>Clear</button>
    <button class="tui-key tui-key--primary" data-drawer-close>Show 42 results</button>
  </footer>
</dialog>
import { openDrawer, closeDrawer } from '/kit/kit.mjs';

// data-drawer-open and data-drawer-close need no code. To do it yourself:
openDrawer(document.getElementById('filters'));   // showModal()
closeDrawer(drawer, 'applied');                    // close(returnValue)

drawer.addEventListener('close', () => {
  if (drawer.returnValue === 'applied') refresh();
});

Needs kit.css and kit.mjs. How to install the kit.

05

Accessibility

  • A native modal <dialog>: focus moves in, is held inside, and returns to the key that opened it; everything behind it is inert.
  • Name it with aria-labelledby pointing at its title. Every drawer has a visible close key with an aria-label; the grip and the scrim are conveniences.
  • Escape closes it. A drawer that holds unsaved work uses data-lock so a stray click does not lose it, and says why on screen.
  • The page behind stops scrolling while it is open, so a wheel or a swipe over the drawer never moves what is under it.
  • The slide is a transform and a fade of the scrim; with reduced motion the drawer simply appears and disappears.
  • Drawers do not open other drawers: a second layer of modal is a page.
06

How it’s built

The browser’s dialog does the hard parts: the top layer, the backdrop, the inert page, the focus trap and the return of focus. The plate is the kit’s face, bolted to an edge (its corners are square where it meets the frame and round where it faces the page) and its shadow falls toward the page. The grip is a slot cut into the inner edge with two ribs standing in it. Sliding is a transform with @starting-style, and allow-discrete keeps the plate on screen until it has left; where that is not understood it simply appears. drawer.mjs adds the triggers, the scrim click and the drag on the grip, which follows the pointer and closes past a third of the way or on a quick flick.

07

Rules

drawer.rules.mdIn the brain

For a task or a record that wants an edge of the screen: filters, a detail, a sheet of actions. Modal, so it says how to leave.

6 rules in this file, with the foundations they rest on and the anti-patterns that break them. Full rules ship in the brain with the Pass.

08

Confirm

Erase reel 3?

KeepErase

Dialog

A decision that must be made now: a native dialog with a question, one sentence of consequence, a way back and the act.

Reserve

Kept for a power cut.

Popover

A card hung from the key that opened it, with a printed leader running back to the key. Not modal: the page stays live around it.

2 matches
BatteryGB
Set battery reserve

Command palette

Every action and page behind one key: a modal plate whose entry line is a lit display, with the results printed below and the chosen one pressed in.

Docs
Finish
Volume