JavaScript helpers
Generated by scripts/build.mjs from js/: each helper as its
own source describes it, and its types. They're optional: plain ES modules
with no dependencies, one for each behaviour the CSS can't give. Import the
ones you need, from the same version as the CSS:
<script type="module">
import { initTabs } from "https://design.arabesquelabs.com/v/0.9.0/js/tabs.js";
initTabs();
</script>
The React components (dist/react/) call them for you (React).
announcement
Announcements: a [data-dismiss] button hides its .al-announcement and remembers it (localStorage, by data-announcement), so it stays gone on every page; focus moves on to what came after it. Where storage isn't allowed, it's gone for this page only.
Used by: Phase banner. Source: js/announcement.js.
/** Hide the announcements already dismissed, and wire each [data-dismiss] button inside root. */
export function initAnnouncements(root?: ParentNode): void;
/** Show an announcement again on the next load. */
export function forgetDismissal(id: string): void;
back-to-top
Back to top: hidden until the page has scrolled two screens, then shown. Followed, it goes to the top and moves focus there too, so the next Tab is the page's first link (its skip link), not the one after this.
Used by: Back to top. Source: js/back-to-top.js.
/** Wire every .al-back-to-top inside root: hidden until the page has scrolled two screens; followed, it moves focus to the top. */
export function initBackToTop(root?: ParentNode): void;
chart
A chart's marks from the keyboard: one tab stop for the whole chart, and the arrow keys move from mark to mark (along the reading direction, so reversed in right-to-left); Home and End jump to the ends. Each mark is role="img" with its value and what it is as its name, so focusing one says it; the tooltip shows the same.
initChartKeys(chartElement);
Used by: Charts. Source: js/chart.js.
/** One tab stop for a chart; the arrow keys, Home and End move between its marks. */
export function initChartKeys(chart: HTMLElement, options?: { marks?: string }): void;
copy
Copy buttons: a [data-copy] button copies the text of the element it names (aria-controls), or else of its code block or command (the panel that's showing, when there are tabs). Then it says so: its label reads "Copied" for a moment, and the block's status region tells screen readers. If the clipboard is refused, the text is selected instead, for the person to copy themselves.
Used by: Code and command. Source: js/copy.js.
/** Wire every [data-copy] button inside root: it copies its code block's, command's or aria-controls target's text, and says so. */
export function initCopy(root?: ParentNode): void;
count
Character count, the GOV.UK way: what's left, said in words, and how far over once it's over; typing past the limit is allowed (a paste isn't cut), and the message turns into an error. The visible message updates at once; screen readers hear it after a pause, through a polite region.
Over the limit, the field is marked aria-invalid="true"; the mark is removed again only if this helper set it, so validate.js's stays.
Used by: Character count. Source: js/count.js.
export interface CountOptions {
/** The message for what's left (left ≥ 0) or how far over (left < 0). */
words?: (left: number) => string;
/** The static description of the limit, read with the field. */
limit?: (max: number) => string;
}
/** Character counts for every [data-al-maxlength] under root; returns a function that redraws them. */
export function initCharacterCounts(root?: ParentNode, options?: CountOptions): () => void;
datepicker
Date picker: a text field for a date near today, with a calendar to choose it from (the WAI-ARIA APG date picker dialog). Unlike the browser's , it speaks the page's language: month and day names, the order of the week, the digits people chose (Western or Arabic-Indic) and, in right to left, a grid that runs from the right. The date can always be typed instead, in either digit set; it's kept as an ISO date (2026-11-30) in a hidden field that takes the input's name. Gregorian only.
In the calendar: arrow keys move by day and week (along the reading direction), Home and End to the week's ends, Page Up and Page Down by month (with Shift, by year), Enter or Space chooses, Escape closes; focus goes back to the button. data-min and data-max (ISO) on the input limit what can be chosen.
Used by: Date picker. Source: js/datepicker.js.
import type { Numerals } from "./format.js";
/** A date-only value: "YYYY-MM-DD". */
export type IsoDate = string;
type Live<T> = T | (() => T);
export interface DatePickerLabels {
/** The previous-month button's name; default "Previous month". */
previous?: string;
/** The next-month button's name; default "Next month". */
next?: string;
}
export interface DatePickerOptions {
/** Labels in the page's language; a function is read each time it draws. */
labels?: Live<DatePickerLabels>;
/** Western ("latn", the default) or Arabic-Indic ("arab") digits. */
numerals?: Live<Numerals>;
/** The language; by default the input's nearest lang. */
lang?: Live<string>;
}
export interface DatePicker {
readonly input: HTMLInputElement;
/** The chosen date, or null if none (or what's typed isn't a date). */
value: IsoDate | null;
open(): void;
close(): void;
/** Draw again in the current language and digits. */
refresh(): void;
/** Take the picker away; the input keeps its value and name. */
destroy(): void;
}
/** Every input[data-al-date-picker] under root becomes a date picker. */
export function initDatePickers(root?: ParentNode, options?: DatePickerOptions): DatePicker[];
/** A typed date, day first, in either digit set; null if it isn't a date that exists. */
export function parseDate(text: string): IsoDate | null;
/** A date as it's written in the language and digits: "30/11/2026", "٣٠/١١/٢٠٢٦". */
export function formatDate(iso: IsoDate, options?: { lang?: string; numerals?: Numerals }): string;
/** The week's first day where the language is used, as JS counts days (0 Sunday … 6 Saturday). */
export function weekStart(lang?: string): number;
/** A month (0–11) as weeks of 7 dates, null outside the month. */
export function monthGrid(year: number, month: number, weekStart?: number): (IsoDate | null)[][];
/** Where a key moves the focused date; null if it doesn't. */
export function step(iso: IsoDate, key: string, options?: { rtl?: boolean; shift?: boolean; start?: number }): IsoDate | null;
/** Today, where the person is. */
export function today(): IsoDate;
dialog
Dialogs that don't lose work (from Apple's HIG): if closing would throw away what someone typed, the dialog doesn't close; a bar inside it asks to keep editing or discard. Never a second dialog on top.