Skip to content

Session Warning Dialog

Authentication UISince 0.3.0
import { SessionWarningDialog } from '@absoluteui/components/session-warning-dialog';
import '@absoluteui/components/session-warning-dialog/style.css';

Using Angular? See Session Warning Dialog for Angular.

An alert dialog that warns before a session ends (WCAG 2.2.1 Timing Adjustable). It provides:

  • a countdown to a deadline given as an Instant, recomputed from the clock on every tick (timezone independent);
  • “Stay signed in” (initial focus) and “Sign out” actions, and an expired state with “Sign in again”;
  • throttled announcements: at multiples of announceInterval, then at 60, 30, and 10 seconds, not every second;
  • optional automatic opening warnBefore the deadline;
  • events for every outcome: aui-extend, aui-sign-out, aui-expire.

It pauses, extends, and ends nothing on its own. Your application calls its session service when the events fire. AbsoluteUI is not an authentication provider.

  • Warning users before an inactivity or absolute session timeout.
  • Any deadline where the user must be able to extend with one simple action.
  • Non-urgent notices: use Toast or Alert.
  • Enforcing the timeout: the server owns session expiry.

Warn two minutes before expiry and extend on request

Section titled “Warn two minutes before expiry and extend on request”
import { duration, instant } from '@absoluteui/core';
import { SessionWarningDialog } from '@absoluteui/components/session-warning-dialog';
const warning = SessionWarningDialog.create({ expiresAt: instant(Date.now() + 15 * 60_000), warnBefore: duration(2 * 60_000) });
warning.element.addEventListener('aui-extend', () => {
void fetch('/api/session/refresh', { method: 'POST' }).then(async (r) => {
const body: unknown = await r.json();
if (typeof body === 'object' && body !== null && 'expiresAt' in body && typeof body.expiresAt === 'number') {
warning.update({ expiresAt: instant(body.expiresAt) });
}
});
});
warning.element.addEventListener('aui-sign-out', () => {
location.assign('/sign-out');
});
import { instant } from '@absoluteui/core';
import { SessionWarningDialog } from '@absoluteui/components/session-warning-dialog';
const warning = SessionWarningDialog.create({
expiresAt: instant(Date.parse('2026-03-08T12:00:00Z')),
title: 'Still working?',
message: 'Unsaved changes are kept for 10 minutes after sign-out.',
});
warning.open();
import { duration, instant } from '@absoluteui/core';
import { SessionWarningDialog } from '@absoluteui/components/session-warning-dialog';
import '@absoluteui/components/button/style.css';
import '@absoluteui/components/dialog/style.css';
import '@absoluteui/components/session-warning-dialog/style.css';
const warning = SessionWarningDialog.create({ expiresAt: instant(Date.now() + 30 * 60_000), warnBefore: duration(5 * 60_000) });
warning.element.addEventListener('aui-extend', () => {
void fetch('/api/session/refresh', { method: 'POST' }).then(() => {
warning.update({ expiresAt: instant(Date.now() + 30 * 60_000) });
});
});
warning.element.addEventListener('aui-expire', () => {
location.assign('/signed-out');
});
import { duration, instant } from '@absoluteui/core';
import { SessionWarningDialog } from '@absoluteui/components/session-warning-dialog';
const warning = SessionWarningDialog.create({ expiresAt: instant(Date.now() + 15 * 60_000), warnBefore: duration(120_000) });
warning.element.addEventListener('aui-sign-out', () => {
location.assign('/sign-out');
});
<dialog class="aui-dialog aui-dialog--sm aui-session-warning-dialog" role="alertdialog" aria-labelledby="T" aria-describedby="M C">
<header class="aui-dialog__header"><h2 id="T">Your session is about to expire</h2></header>
<div class="aui-dialog__body">
<div class="aui-session-warning">
<p class="aui-session-warning__message" id="M">You will be signed out soon ...</p>
<p class="aui-session-warning__countdown" id="C">Time remaining: 1 minute, 30 seconds</p>
<div class="aui-session-warning__content">(optional content)</div>
</div>
</div>
<footer class="aui-dialog__footer">
<button class="aui-button aui-button--primary" autofocus>Stay signed in</button>
<button class="aui-button aui-button--secondary">Sign out</button>
</footer>
</dialog>
SessionWarningDialog.create(options: SessionWarningDialogOptions, doc?: Document): SessionWarningDialog

Global configuration key: sessionWarningDialog (see Configuration).

Option Type Default Description
expiresAt Instant When the session ends. Required.
warnBefore Duration Open automatically this long before expiresAt (at least 20 s).
title string Default: localized “Your session is about to expire”.
message string Default: localized inactivity message.
content Node | string Additional body content below the countdown.
extendLabel string Default: localized “Stay signed in”.
signOutLabel string Default: localized “Sign out”.
expiredTitle string Default: localized “Your session has expired”.
expiredMessage string Default: localized “You have been signed out. Sign in again to continue.”
signInLabel string The single action after expiry. Default: localized “Sign in again”.
announceInterval number 60 Announce the remaining time each time it crosses a multiple of this many seconds (15..3600), and at 60, 30, and 10 seconds. Globally configurable.
escapeExtends boolean true Escape means “Stay signed in”; when false, Escape does nothing. Globally configurable.
locale string "en-US" Message and unit-formatting locale. Globally configurable. Inherited from common configuration.
Property Type Description
dialog Dialog The underlying Dialog.
element HTMLDialogElement The <dialog>; events are dispatched on it.
isOpen boolean Whether the dialog is open.
remaining Duration Time left (never negative).
expired boolean The deadline has passed.
options Readonly<SessionWarningDialogOptions> Current options.
Method Description
open(): void Open now; focus moves to “Stay signed in”.
close(): void Close programmatically (no events besides aui-close).
update(options: Partial<SessionWarningDialogOptions>): void Merge options; a new expiresAt re-arms auto-open and expiry.
destroy(): void Stop timers and remove the dialog.
Event Detail Bubbles Cancelable Description
aui-open null No No The dialog opened (from Dialog).
aui-close { returnValue: string; reason: DialogCloseReason } No No The dialog closed (from Dialog).
aui-extend { reason: 'action' | 'escape' } No No The user chose to stay signed in: extend the session, then call update({ expiresAt }).
aui-sign-out { reason: 'action' | 'expired' } No No The user chose to sign out, or acknowledged expiry (“Sign in again”).
aui-expire null No No The deadline passed (once per deadline, open or closed).
Area Description
content Optional Node or text below the countdown (Angular: the template content).

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-session-warning-dialog The <dialog> (also aui-dialog).
aui-session-warning Body wrapper.
aui-session-warning__message Message.
aui-session-warning__countdown Countdown text.
aui-session-warning__content Consumer content.

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-color-text | --aui-font-family-sans | --aui-font-size-md | --aui-font-weight-semibold | --aui-line-height-normal | --aui-space-3

interface SessionWarningDialogConfig { announceInterval: number; escapeExtends: boolean }
interface SessionWarningDialogOptions extends Partial<SessionWarningDialogConfig> {
expiresAt: Instant; warnBefore?: Duration; title?: string; message?: string; content?: Node | string;
extendLabel?: string; signOutLabel?: string; expiredTitle?: string; expiredMessage?: string; signInLabel?: string; locale?: string;
}
interface SessionWarningExtendDetail { reason: 'action' | 'escape' }
interface SessionWarningSignOutDetail { reason: 'action' | 'expired' }
function formatRemaining(seconds: number, locale: string): string;

Pattern: WAI-ARIA alertdialog (native modal <dialog>) named by its title and described by the message and countdown.

Key Context Action
Tab / Shift+Tab Dialog open Moves between the actions (focus is contained by the modal).
Enter / Space Action focused Stay signed in, Sign out, or Sign in again.
Escape Dialog open, not expired Stay signed in (escapeExtends: true, default); nothing when false.
Element Role
dialog alertdialog
actions button (native)
Attribute Element Value
aria-labelledby dialog Title id
aria-describedby dialog Message and countdown ids (message only when expired)

On open, focus moves to “Stay signed in” (least destructive). On expiry, to “Sign in again”. On close, focus returns to the element that had it.

Criterion Level Note
1.3.1 Info and Relationships A
1.4.3 Contrast (Minimum) AA
2.1.1 Keyboard A
2.1.2 No Keyboard Trap A
2.2.1 Timing Adjustable A
2.4.3 Focus Order A
2.4.7 Focus Visible AA
4.1.2 Name, Role, Value A
4.1.3 Status Messages AA

Automated test results: Accessibility and compliance.

  • WCAG 2.2.1: users are warned and can extend with one simple action; warnBefore must be at least 20 s.
  • Opening an alertdialog is read by screen readers; afterwards the remaining time is announced politely only when it crosses a multiple of announceInterval and at 60, 30, and 10 seconds. Expiry is announced assertively.
  • Announcements go to a live region inside the modal (everything outside is inert).
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.

closed (scheduled when warnBefore is set), open and counting down, expired, suspended (answered; waits for a new expiresAt).

From Trigger To
closed open(), or expiresAt - warnBefore reached open; focus on “Stay signed in”
open “Stay signed in” or Escape closed, suspended; aui-extend
open “Sign out” closed, suspended; aui-sign-out (action)
open deadline expired: title, message, and a single “Sign in again”; announced assertively; aui-expire
expired “Sign in again” closed; aui-sign-out (expired)
closed deadline aui-expire (stays closed)
suspended update({ expiresAt }) scheduled again
  • WCAG 2.2.1: the user is warned and can extend with one action; warnBefore must be at least 20 seconds.
  • Screen readers read the alert dialog when it opens. Afterwards the remaining time is announced politely only when it crosses a multiple of announceInterval and at 60, 30, and 10 seconds; expiry is announced assertively. Announcements go to a live region inside the modal.
  • No animation of its own; the countdown is a text change once per second. Dialog transitions follow reduced motion.
  • Manual screen-reader verification is pending.

Dialog size sm; actions wrap on narrow screens.

As Dialog (logical properties). The countdown text is localized.

Dialog surface and Button variants; the countdown uses color-text with tabular numbers.

configureAbsoluteUI({ components: { sessionWarningDialog: { announceInterval: 120, escapeExtends: false } } });

Instance options override global and theme defaults. All options are dynamic.

Timezone independent. expiresAt is an exact Instant; remaining time is expiresAt - Date.now(), a duration. The configured timezone and the device timezone are never used. Only the device clock matters: derive expiresAt from your server’s expiry (for example “expires in N seconds” measured on receipt) to limit clock skew.

None of its own; Dialog has no close button here.

  • Never signs out, extends, or stores tokens: it emits events.
  • All text is rendered with textContent; content strings are text.
  • Extending must happen on the server; a client-only extension changes nothing.
  • CSP-safe.

Holds only the deadline and texts in memory. No persistence, network access, or telemetry.

One timer at a time: a tick aligned to the next whole second while open, and one scheduling timer while closed (capped at 2^31 - 1 ms and re-armed). Budget: open() < 16 ms (e2e performance suite).

One dialog per page.

  • Background tabs throttle timers; the next tick recomputes from the clock, so the countdown is correct but may update late, and missed thresholds are announced once.
  • A deadline already past at creation opens in the expired state without aui-expire.
  • After the user answers, auto-open waits for update({ expiresAt }), so an unchanged deadline does not reopen the dialog in a loop.
  • update({ expiresAt }) while expired returns to the countdown.
Code Category Cause
SESSION_WARNING_DIALOG_OPTIONS configuration expiresAt not an Instant, warnBefore not a Duration of at least 20 s, empty texts, invalid content.
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION configuration Invalid or unknown configuration.

A failed construction creates no dialog.

  • The device clock is trusted.
  • Escape extends by default.
  • Manual screen-reader verification is pending.
  • Activity tracking (the application decides when the session was used).
  • Cross-tab coordination (use BroadcastChannel in the application and call update()).