diff --git a/frontend/src/lib/document/TimelineDensityFilter.svelte b/frontend/src/lib/document/TimelineDensityFilter.svelte
index 973d3c1b..d13d6e61 100644
--- a/frontend/src/lib/document/TimelineDensityFilter.svelte
+++ b/frontend/src/lib/document/TimelineDensityFilter.svelte
@@ -225,6 +225,19 @@ const dragWindowRightPct = $derived.by(() => {
});
const tickIndices = $derived(tickIndicesFor(filled));
+
+// While dragging, expose the live preview range to assistive tech via a
+// polite live region. Empty text outside drag avoids announcing residual state.
+const dragLiveMessage = $derived.by(() => {
+ if (!isDragging || dragLowIndex === null || dragHighIndex === null) return '';
+ const fromLabel = filled[dragLowIndex]?.month;
+ const toLabel = filled[dragHighIndex]?.month;
+ if (!fromLabel || !toLabel) return '';
+ return m.timeline_dragging_aria_live({
+ from: formatTickLabel(fromLabel, getLocale()),
+ to: formatTickLabel(toLabel, getLocale())
+ });
+});
const omitTickYear = $derived.by(() => {
if (filled.length === 0 || filled[0].month.length === 4) return false;
const firstYear = filled[0].month.slice(0, 4);
@@ -306,6 +319,10 @@ const omitTickYear = $derived.by(() => {
+
+ {dragLiveMessage}
+
+
{#if isZoomed}