Skip to the page
TACTILE UI

Kit part · Notifications

Notifications

A key with a counter window in its cap, and a card of messages hung from it. Each unread message has its lamp lit; reading it puts the lamp out and the counter steps down.

Element
[popover] role="dialog"
Needs
kit.css + kit.mjs
Rules
notifications.rules.md · 5
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

Counter key · 3 unread
Counter key · nothing new
Icon key with a lamp
  • Meter 2291 missed three readings

    It is back. The gap was filled from the hub’s own log.

  • Tariff changed to Flux

    The schedule now charges between 00:00 and 02:00.

Unread and read
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

PartNothing unreadUnreadCard outFocus
Counter
Lamp

Graphite

PartNothing unreadUnreadCard outFocus
Counter
Lamp
03

API

NameKindValuesWhat it does
.tui-notifications__triggerclasson a .tui-keyThe key. Latched while the card is out.
.tui-notifications__countclasswith .tui-lcd, data-ghost="88"The counter window in the cap: two digits, 00 to 99, aria-hidden (the key’s label says it).
.tui-lamp in the keyelementdata-lamp="amber"For an icon key: a lamp in the cap’s corner, lit while anything is unread.
data-labelattributeon the keyThe first words of its aria-label; the count is added after. “Notifications” by default.
.tui-notificationsclasswith .tui-popoverThe card, up to 372px wide, hung from the key; data-align="end" keeps it under a key at the right of a bar.
.tui-notifications__item · data-unreadclass · attributeon <li>A message. Unread: its lamp lit and its title bold.
.tui-notifications__title · time · __body · __actionsclass · element—The title, when (printed right), one or two lines, and its keys.
data-notifications-read-allattributeon a flat keyReads them all: the lamps go out top to bottom, 70ms apart.
data-notifications-statusattributeon a <span>Rewritten as “3 unread” or “All read”. Put it in a polite live region.
.tui-notifications__empty · __footclass—The line shown when the list is empty; a foot for “See all”.
updateNotifications(card) · markNotificationsRead(card, items?)functionkit.mjsRecount after adding or removing messages; read some or all of them.
tui-notificationseventdetail.unreadFired on the card after every recount.
Enter · Space · Escape · Tabkeyboard—Open and close from the key; Tab walks the messages’ keys; Escape returns to the key.
04

Markup

<button class="tui-key tui-notifications__trigger" popovertarget="inbox"
        aria-expanded="false" aria-label="Notifications, 2 unread">
  <svg …bell…></svg>
  <span class="tui-lcd tui-notifications__count" data-ghost="88" aria-hidden="true">02</span>
</button>

<div class="tui-popover tui-notifications" id="inbox" popover role="dialog"
     aria-labelledby="inbox-t" data-align="end">
  <div class="tui-notifications__head">
    <h2 class="tui-label" id="inbox-t">Notifications</h2>
    <span class="tui-status" aria-live="polite">
      <span class="tui-lamp" data-lamp="amber" data-on></span>
      <span data-notifications-status>2 unread</span>
    </span>
    <button class="tui-key tui-key--flat tui-key--sm" data-notifications-read-all>Mark all read</button>
  </div>
  <ul class="tui-notifications__list">
    <li class="tui-notifications__item" data-unread>
      <span class="tui-lamp" data-lamp="amber" data-on></span>
      <p class="tui-notifications__title">Firmware 4.2 is ready</p>
      <time datetime="2026-09-28T11:02">11:02</time>
      <p class="tui-notifications__body">It installs tonight at 02:00.</p>
      <div class="tui-notifications__actions"><button class="tui-key tui-key--sm">Install now</button></div>
    </li>
  </ul>
  <p class="tui-notifications__empty">Nothing yet. Messages from the hub arrive here.</p>
</div>
import { updateNotifications, markNotificationsRead } from '/kit/kit.mjs';

const card = document.getElementById('inbox');
card.querySelector('ul').insertAdjacentHTML('afterbegin', messageHTML);
updateNotifications(card);          // counter, lamps, words, aria-label
card.addEventListener('tui-notifications', (e) => saveUnread(e.detail.unread));

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

05

Accessibility

  • The key’s aria-label carries the count (“Notifications, 3 unread”); the counter window is aria-hidden, so it is not read twice.
  • The card is a non-modal role="dialog" with a heading; the head’s status is a polite live region, so “All read” is announced after Mark all read.
  • Unread is a lamp and a bold title, never the lamp alone.
  • Opening the card reads nothing: a message is read when one of its keys is pressed, or with Mark all read.
  • Reduced motion puts the lamps out together.
06

How it’s built

The key carries a real LCD from the kit, cut down to 12px, with a ghost of unlit 8s behind the digits; it always shows two digits, because a display does not vanish at zero. The card is a .tui-popover, so it is placed and given its leader by popover.mjs. notifications.mjs recounts the data-unread messages, rewrites the counter, the status and the key’s label, and on Mark all read puts the lamps out one by one with a 70ms step: the only motion, and it stops under reduced motion.

07

Rules

notifications.rules.mdIn the brain

The counter always shows two digits, even at nothing new; a message is read when someone acts on it, never merely because the card opened.

5 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

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.

02:52min

LCD

A value that changes, on grey-green liquid crystal, with a ghost of the unlit segments behind it: clocks, battery, counters.

Lamp

The only colour in the kit: a lamp that is on. Green is on, amber is waiting, red is recording or failing, blue is linked, white is standing by. Always next to words.

Docs
Finish
Volume