A minimal and customizable Vue 3 component to display human-readable relative dates with live updates, compact formats, and multi-language support. Works seamlessly in Single Page Apps and Server-Side Rendered (SSR) environments (e.g. Nuxt 3).
- Features
- Installation
- Quick Start (SPA)
- Nuxt 3 / SSR Usage
- Component Registration Options
- Usage Examples
- Props
- Multi-Language Support
- Composable (useRelativeTime)
- Customization & Formats
- Accessibility
- SSR Notes
- Development
- Changelog
- Contributing
- License
- Human-readable formats: "2 hours ago", "Yesterday", "In 3 days"
- Compact mode:
2h,1d,3w,2mofor space-constrained UIs - Live updates: Auto-refresh at configurable intervals (default 60s)
- Multi-language: Built-in support for English, Spanish, French, Portuguese
- Full date display: Optionally show absolute date alongside relative time
- SSR-compatible: Works with Nuxt 3 and other SSR frameworks
- Lightweight & tree-shakeable: Vue 3 marked as external dependency
- Semantic HTML: Renders as
<time>element with properdatetimeattribute - Accessible: Includes
titleandaria-labelfor screen readers
Using npm:
npm install @todovue/tv-relative-timeUsing yarn:
yarn add @todovue/tv-relative-timeUsing pnpm:
pnpm add @todovue/tv-relative-timeGlobal registration (main.js / main.ts):
import { createApp } from 'vue'
import App from './App.vue'
import { TvRelativeTime } from '@todovue/tv-relative-time'
createApp(App)
.use(TvRelativeTime) // enables <TvRelativeTime /> globally
.mount('#app')Local import inside a component:
<script setup>
import { TvRelativeTime } from '@todovue/tv-relative-time'
const publishedDate = '2024-01-15T10:30:00Z'
</script>
<template>
<TvRelativeTime :date="publishedDate" />
</template>Create a plugin file: plugins/tv-relative-time.client.ts (or without .client suffix as it's SSR-safe):
import { defineNuxtPlugin } from '#app'
import { TvRelativeTime } from '@todovue/tv-relative-time'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(TvRelativeTime)
})Use anywhere in your Nuxt app:
<template>
<TvRelativeTime
:date="article.publishedAt"
lang="es"
show-full-date
/>
</template>Optional direct import (no plugin):
<script setup>
import { TvRelativeTime } from '@todovue/tv-relative-time'
</script>| Approach | When to use |
|---|---|
Global via app.use(TvRelativeTime) |
Many usages across app / design system install |
Local named import { TvRelativeTime } |
Isolated / code-split contexts |
Direct default import import { TvRelativeTime } from '@todovue/tv-relative-time' |
Single usage or manual registration |
| Plugin with custom name | Custom component naming requirements |
<TvRelativeTime :date="'2024-10-18T08:00:00Z'" />
<!-- Output: "2 hours ago" (depending on current time) --><TvRelativeTime :date="postDate" compact />
<!-- Output: "2h", "3d", "1w", etc. --><TvRelativeTime :date="eventDate" show-full-date />
<!-- Output: "3 days ago (October 15, 2024)" --><TvRelativeTime :date="createdAt" lang="es" />
<!-- Output: "Hace 2 horas" --><TvRelativeTime :date="lastSeen" :update-interval="10000" /><TvRelativeTime
:date="article.publishedAt"
lang="fr"
compact
show-full-date
:update-interval="30000"
/>
<!-- Output: "2h (18 octobre 2024)" -->| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
date |
String/Number | — | Yes | Date string (ISO 8601) or timestamp to format. |
updateInterval |
Number | 60000 |
No | Interval in milliseconds for live updates (60s default). |
compact |
Boolean | false |
No | If true, returns compact format (2h, 3d, 1w, 2mo). |
showFullDate |
Boolean | false |
No | If true, appends the full date next to the relative time. |
lang |
String | 'en' |
No | Language code: 'en', 'es', 'fr', 'pt'. |
Accepts:
- ISO 8601 date strings:
'2024-10-18T14:30:00Z' - JavaScript timestamps:
1697635800000 - Any valid date string parseable by
new Date()
Controls how often the component recalculates the relative time. Set to 0 to disable auto-updates (not recommended for live data).
Perfect for space-constrained UIs like tables, cards, or mobile views:
false(default): "2 hours ago", "Yesterday", "In 3 days"true: "2h", "1d", "3w", "2mo", "1y"
Useful for providing context:
false(default): "2 days ago"true: "2 days ago (October 16, 2024)"
Supported languages:
'en': English (default)'es': Spanish / Español'fr': French / Français'pt': Portuguese / Português
The component includes built-in translations for 4 languages. Examples:
| Time difference | English (en) | Spanish (es) | French (fr) | Portuguese (pt) |
|---|---|---|---|---|
| < 1 minute | A moment ago | Hace un momento | Il y a un instant | Há um momento |
| 2 hours ago | 2 hours ago | Hace 2 horas | Il y a 2 heures | Há 2 horas |
| Yesterday | Yesterday | Ayer | Hier | Ontem |
| 3 days ago | 3 days ago | Hace 3 días | Il y a 3 jours | Há 3 dias |
| Last week | Last week | La semana pasada | La semaine dernière | Semana passada |
| Tomorrow | Tomorrow | Mañana | Demain | Amanhã |
| In 5 days | In 5 days | En 5 días | Dans 5 jours | Em 5 dias |
Compact format uses universal abbreviations:
- Minutes:
m(e.g.,15m) - Hours:
h(e.g.,3h) - Days:
d(e.g.,5d) - Weeks:
w(e.g.,2w) - Months:
mo(e.g.,3mo) - Years:
y(e.g.,1y)
You can also use the underlying composable directly in your own logic:
import useRelativeTime from '@todovue/tv-relative-time/composable/useRelativeTime'
const { getRelativeTime } = useRelativeTime()
const result = getRelativeTime('2024-10-15T10:00:00Z', false, false, 'en')
console.log(result.text) // "3 days ago"
console.log(result.tooltip) // "October 15, 2024"getRelativeTime(dateString, isTableQuantity, compact, lang)| Parameter | Type | Description |
|---|---|---|
dateString |
String | Date to format |
isTableQuantity |
Boolean | If true, returns '-' when date is invalid |
compact |
Boolean | Enable compact format |
lang |
String | Language code ('en', 'es', 'fr', 'pt') |
Returns:
{
text: string, // Formatted relative time
tooltip: string // Full formatted date
}- "A moment ago" / "In a moment"
- "2 minutes ago" / "In 5 minutes"
- "1 hour ago" / "In 3 hours"
- "Yesterday" / "Tomorrow"
- "Last Monday" / "This Friday"
- "Last week" / "Next week"
- "3 weeks ago" / "In 2 weeks"
- "2 months ago" / "In 4 months"
- "1 year, 2 months ago"
- "Now"
- "15m", "2h"
- "1d", "5d"
- "2w", "3w"
- "2mo", "6mo"
- "1y", "2y"
When showFullDate is enabled, the full date is formatted according to the selected language using Intl.DateTimeFormat:
- en: "October 18, 2024"
- es: "18 de octubre de 2024"
- fr: "18 octobre 2024"
- pt: "18 de outubro de 2024"
The component is built with accessibility in mind:
<time
class="tv-relative-time"
datetime="2024-10-18T14:30:00Z"
title="October 18, 2024"
aria-label="October 18, 2024"
>
2 hours ago
</time>- Uses semantic
<time>element withdatetimeattribute - Includes
titleattribute for tooltip on hover - Includes
aria-labelwith full date for screen readers - Full date always available even when showing relative time
- Proper language attribute can be set on parent elements
<!-- Good: Context is clear -->
<article lang="es">
<h2>{{ article.title }}</h2>
<TvRelativeTime :date="article.publishedAt" lang="es" />
</article>
<!-- Better: Additional context -->
<p>
Published <TvRelativeTime :date="publishedAt" show-full-date />
</p>- No direct DOM access: Safe for SSR/Nuxt environments
- Hydration-friendly: Component state syncs properly on client
- Universal rendering: Works identically on server and client
- Timezone handling: Uses UTC by default for consistency
- Auto-updates: Live updates start only after client-side hydration
<!-- In Nuxt, this works everywhere -->
<template>
<div>
<TvRelativeTime :date="post.createdAt" />
</div>
</template>The component will render the initial relative time on the server, then activate live updates on the client.
Clone the repository and install dependencies:
git clone https://github.com/TODOvue/tv-relative-time.git
cd tv-relative-time
yarn installRun the development server with demo playground:
yarn devBuild the library:
yarn buildBuild demo site:
yarn build:demosrc/
├── components/
│ └── TvRelativeTime.vue # Main component
├── composable/
│ └── useRelativeTime.js # Core logic
├── locales/
│ └── relativeTime.js # Translations
├── demo/
│ └── Demo.vue # Demo playground
└── entry.ts # Library entry point
See CHANGELOG.md for release history and updates.
PRs and issues welcome! Please read our Contributing Guide and Code of Conduct.
MIT © TODOvue
Crafted for the TODOvue component ecosystem
