The button appears once you are past the first screen, but never takes focus or interrupts; it is the last thing in the Tab order, where it is needed.Pressing it scrolls to the top, instantly if you asked for reduced motion, and moves focus to the first heading, so the next Tab starts from the top too.
Use it with a mouse, a keyboard or a screen reader.The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.
Release notes for Khata
What changed in the app, newest first.
Version 4.2
AddedSplit a bill between up to ten people.
AddedReminders on WhatsApp for unpaid bills.
FixedTotals no longer round twice on GST invoices.
Version 4.1
AddedA dark theme that follows your phone's setting.
ChangedThe add button is larger and sits within reach of your thumb.
FixedScreen readers now read amounts in rupees, not as plain numbers.
Version 4.0
AddedHindi, Tamil and Bengali.
ChangedReports open in half the time.
RemovedThe old CSV export. Use the Excel export instead.
Version 3.9
AddedSearch across all your notes.
FixedLong names are no longer cut off on small phones.
Version 3.8
AddedExport a month of entries as a PDF.
FixedChanges made offline sync when you reconnect.
Version 3.7
AddedAttach a photo of the receipt to any entry.
FixedThe app no longer asks for your PIN twice after an update.
Version 3.6
ChangedAmounts use Indian digit grouping, like 1,00,000.
FixedEvery icon button now has a name for screen readers.
The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.
Keyboard
Keys it answers to
Every action works without a pointer.
Key
What it does
Tab
Moves to the scroll area, then through the links in it, then to Back to top once it is showing.
Arrow Down or Page Down or Space or End
With focus on the scroll area, scrolls the notes; Back to top appears once you are past the first screen.
Enter or Space
On Back to top, scrolls to the top and moves focus to the first heading.
Screen readers
What it announces
Written from the roles, names and states in the markup.
When
Expected announcement
Tab reaches the scroll area
Release notes for Khata, region
The button appears
Nothing: it appears without taking focus or interrupting
Tab reaches the button
Back to top, button
Enter on Back to top
Release notes for Khata, heading level 3
These are expected announcements, not recordings.Wording and order differ between screen readers and browsers.
Code
Copy the code
The exact files this demo runs on.The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.
The markup
<div class="ap-backtop" data-ap-backtop>
<div class="ap-backtop__scroller" role="region" aria-labelledby="btt-name" tabindex="0">
<div class="ap-backtop__mark" aria-hidden="true"></div>
<article class="ap-backtop__article">
<h3 class="ap-backtop__name" id="btt-name" tabindex="-1">Release notes for Khata</h3>
<p class="ap-backtop__intro">What changed in the app, newest first.</p>
<section class="ap-backtop__release">
<div class="ap-backtop__meta">
<h4 class="ap-backtop__version" id="btt-42">Version 4.2</h4>
<time class="ap-backtop__date" datetime="2026-09-30">30 September 2026</time>
</div>
<ul class="ap-backtop__changes">
<li><span class="ap-backtop__tag ap-backtop__tag--added">Added</span> <span>Split a bill between up to ten people.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--added">Added</span> <span>Reminders on WhatsApp for unpaid bills.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--fixed">Fixed</span> <span>Totals no longer round twice on GST invoices.</span></li>
</ul>
</section>
<section class="ap-backtop__release">
<div class="ap-backtop__meta">
<h4 class="ap-backtop__version" id="btt-41">Version 4.1</h4>
<time class="ap-backtop__date" datetime="2026-08-12">12 August 2026</time>
</div>
<ul class="ap-backtop__changes">
<li><span class="ap-backtop__tag ap-backtop__tag--added">Added</span> <span>A dark theme that follows your phone's setting.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--changed">Changed</span> <span>The add button is larger and sits within reach of your thumb.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--fixed">Fixed</span> <span>Screen readers now read amounts in rupees, not as plain numbers.</span></li>
</ul>
</section>
<section class="ap-backtop__release">
<div class="ap-backtop__meta">
<h4 class="ap-backtop__version" id="btt-40">Version 4.0</h4>
<time class="ap-backtop__date" datetime="2026-07-01">1 July 2026</time>
</div>
<ul class="ap-backtop__changes">
<li><span class="ap-backtop__tag ap-backtop__tag--added">Added</span> <span>Hindi, Tamil and Bengali.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--changed">Changed</span> <span>Reports open in half the time.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--removed">Removed</span> <span>The old CSV export. Use the Excel export instead.</span></li>
</ul>
</section>
<section class="ap-backtop__release">
<div class="ap-backtop__meta">
<h4 class="ap-backtop__version" id="btt-39">Version 3.9</h4>
<time class="ap-backtop__date" datetime="2026-05-20">20 May 2026</time>
</div>
<ul class="ap-backtop__changes">
<li><span class="ap-backtop__tag ap-backtop__tag--added">Added</span> <span>Search across all your notes.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--fixed">Fixed</span> <span>Long names are no longer cut off on small phones.</span></li>
</ul>
</section>
<section class="ap-backtop__release">
<div class="ap-backtop__meta">
<h4 class="ap-backtop__version" id="btt-38">Version 3.8</h4>
<time class="ap-backtop__date" datetime="2026-04-02">2 April 2026</time>
</div>
<ul class="ap-backtop__changes">
<li><span class="ap-backtop__tag ap-backtop__tag--added">Added</span> <span>Export a month of entries as a PDF.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--fixed">Fixed</span> <span>Changes made offline sync when you reconnect.</span></li>
</ul>
</section>
<section class="ap-backtop__release">
<div class="ap-backtop__meta">
<h4 class="ap-backtop__version" id="btt-37">Version 3.7</h4>
<time class="ap-backtop__date" datetime="2026-02-18">18 February 2026</time>
</div>
<ul class="ap-backtop__changes">
<li><span class="ap-backtop__tag ap-backtop__tag--added">Added</span> <span>Attach a photo of the receipt to any entry.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--fixed">Fixed</span> <span>The app no longer asks for your PIN twice after an update.</span></li>
</ul>
</section>
<section class="ap-backtop__release">
<div class="ap-backtop__meta">
<h4 class="ap-backtop__version" id="btt-36">Version 3.6</h4>
<time class="ap-backtop__date" datetime="2026-01-09">9 January 2026</time>
</div>
<ul class="ap-backtop__changes">
<li><span class="ap-backtop__tag ap-backtop__tag--changed">Changed</span> <span>Amounts use Indian digit grouping, like 1,00,000.</span></li>
<li><span class="ap-backtop__tag ap-backtop__tag--fixed">Fixed</span> <span>Every icon button now has a name for screen readers.</span></li>
</ul>
</section>
<p class="ap-backtop__more"><a href="/khata/changelog/archive/">Older releases</a></p>
</article>
<div class="ap-backtop__dock">
<button type="button" class="ap-backtop__btn">
<svg class="ap-backtop__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 19V5M6 11l6-6 6 6"/></svg>
<span>Back to top</span>
</button>
</div>
</div>
</div>
/**
* Back to top: a button that appears once the reader has scrolled past the
* first screen, without taking focus, and takes them back to the top with
* focus on the first heading.
*
* Markup: [data-ap-backtop] holding a scroll area (.ap-backtop__scroller, a
* labelled region with tabindex=0) whose first heading has tabindex=-1, an
* empty .ap-backtop__mark as tall as the threshold, and the button as the
* last thing in the scroll area. Showing it is the class ap-backtop--shown.
*/
export function init(root) {
const scroller = root.querySelector(".ap-backtop__scroller");
const mark = root.querySelector(".ap-backtop__mark");
const button = root.querySelector(".ap-backtop__btn");
const heading = scroller.querySelector("[tabindex='-1']");
const reduce = matchMedia("(prefers-reduced-motion: reduce)");
let past = false;
// Shown past the threshold, and kept while it has focus, so focus is never
// left on something that has just disappeared.
function update() {
root.classList.toggle("ap-backtop--shown", past || document.activeElement === button);
}
// The marker leaves the scroll area once the reader is past the threshold:
// one callback per crossing, nothing on every scroll frame.
const watch = new IntersectionObserver(([entry]) => {
past = !entry.isIntersecting && entry.boundingClientRect.top < (entry.rootBounds?.top ?? 0);
update();
}, { root: scroller });
watch.observe(mark);
function onClick() {
scroller.scrollTo({ top: 0, behavior: reduce.matches ? "auto" : "smooth" });
heading.focus({ preventScroll: true });
}
button.addEventListener("click", onClick);
button.addEventListener("blur", update);
return () => {
watch.disconnect();
button.removeEventListener("click", onClick);
button.removeEventListener("blur", update);
};
}
for (const root of document.querySelectorAll("[data-ap-backtop]")) init(root);
Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard.Not yet tested with every screen reader and browser pair.Report a correction