Relative Time
stimeo--relative-time
Shows a time as "3 minutes ago" and keeps rewriting itself as time passes.
Shows a time as wording like "3 minutes ago" and rewrites itself as time passes. The interval widens as the stamp ages, so a post from hours ago is not rewritten every second. Old enough, and it falls back to the absolute time as written. The formatting uses what the browser already provides and adds no dependency. The machine-readable value is left untouched, so what screen readers and crawlers see stays correct. The rewriting never interrupts a screen reader, because hearing "4 minutes ago" repeatedly while reading would be an intrusion.
- Posted
- Updated
- Due
- Archived
The text rewrites itself as time passes, less often as it ages. Items within a few minutes update about once a minute, so leaving this page open shows "4 minutes ago" ticking to "5 minutes ago". The updates never interrupt a screen reader. Hover to see the absolute time.
Keyboard
This component has no keyboard interactions of its own.
<%# Markup for the relative-time demo.
Formats the absolute time in <time datetime> into "3 minutes ago" etc. via
Intl.RelativeTimeFormat, updating at an interval that grows with elapsed time. The
machine-readable datetime stays fixed and only the display text updates; it's not a
live region, to avoid interrupting screen readers. The locale follows the page
language. The last example, past the threshold, falls back to an absolute format. %>
<ul class="relative-time-demo">
<% [
{ at: 3.minutes.ago, label: t("components.relative_time.demo.posted") },
{ at: 2.hours.ago, label: t("components.relative_time.demo.updated") },
{ at: 3.days.from_now, label: t("components.relative_time.demo.due") }
].each do |row| %>
<li class="relative-time-demo__row">
<span class="relative-time-demo__label"><%= row[:label] %></span>
<time
class="relative-time"
data-controller="stimeo--relative-time"
datetime="<%= row[:at].iso8601 %>"
title="<%= row[:at].strftime("%Y-%m-%d %H:%M") %>"
data-stimeo--relative-time-locale-value="<%= I18n.locale %>">
<%= row[:at].strftime("%Y-%m-%d %H:%M") %>
</time>
</li>
<% end %>
<li class="relative-time-demo__row">
<span class="relative-time-demo__label"><%= t(
"components.relative_time.demo.archived"
) %></span>
<%# Example that falls back to an absolute format past the threshold
(30 days = 2592000 seconds). %>
<time
class="relative-time"
data-controller="stimeo--relative-time"
datetime="<%= 90.days.ago.iso8601 %>"
data-stimeo--relative-time-locale-value="<%= I18n.locale %>"
data-stimeo--relative-time-threshold-value="2592000">
<%= 90.days.ago.strftime("%Y-%m-%d %H:%M") %>
</time>
</li>
</ul>
<p class="relative-time-demo__hint"><%= t("components.relative_time.demo.hint") %></p>
/*
* Presentation-only styles for the relative-time demo.
* The library only updates the element's text and reflects data-state (relative / absolute).
*/
.relative-time-demo {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 32rem;
}
.relative-time-demo__row {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
}
.relative-time-demo__label {
color: var(--color-text-muted);
font-size: 0.9rem;
}
.relative-time {
font-variant-numeric: tabular-nums;
color: var(--fg);
}
/* Times that fell back to absolute format are shown subtly, in a more monospace style. */
.relative-time[data-state="absolute"] {
font-size: 0.9rem;
color: var(--color-text-muted);
}
/* Explanatory caption: the relative text refreshes automatically over time. */
.relative-time-demo__hint {
margin: 0.75rem 0 0;
max-width: 32rem;
font-size: 0.85rem;
line-height: 1.5;
color: var(--color-text-muted);
}
This demo needs no consumer-side JS (the controller handles the behavior).
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--relative-time"
Values
| Name | Description | Attribute |
|---|---|---|
locale
|
Locale for Intl.RelativeTimeFormat; falls back to the element's lang then the document's; empty by default. | data-stimeo--relative-time-locale-value |
threshold
|
Seconds past which the relative text reverts to the authored absolute text; 0 disables (default 0). | data-stimeo--relative-time-threshold-value |
tickInterval
|
Minimum polling interval in milliseconds, widened for coarser units (default 60000). | data-stimeo--relative-time-tick-interval-value |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
text content |
Root element | The relative phrase (e.g. "3 minutes ago"). |
datetime |
Root element | The machine-readable absolute time (immutable). |
data-state |
Root element | "relative" / "absolute" (past the threshold). |