
JavaScript Timeline
Pico Library
Built on CalendarJS Timeline: This reactive timeline component is powered by CalendarJS Timeline, the core lightweight timeline engine (2.1KB gzipped) with advanced features. LemonadeJS adds reactive data binding and framework integration (Vue, React, Angular) on top of the CalendarJS foundation.
The LemonadeJS JavaScript Timeline is a framework-agnostic plugin designed for timeline creation, supporting Vue, React, and Angular integration. It facilitates the construction of logs, event timelines, and roadmaps, providing options for customization such as color adjustments, content modification, and control over point positioning. The plugin includes features for automatically grouping events by month and navigation controls, aiding in organizing and displaying timeline data.
Timeline Component Family
CalendarJS Timeline - Core Component
The underlying timeline engine powering this reactive wrapper. CalendarJS Timeline can be used standalone for non-reactive applications:
- Lightweight: 2.1KB gzipped
- Framework-agnostic: Works with vanilla JS, React, Vue, Angular
- Full-featured: Monthly navigation, sorting, alignment controls
- Customizable: Border styles, colors, positioning
Perfect for: Standalone timelines, event logs, project roadmaps, activity feeds
CalendarJS Schedule - Event Scheduling
For interactive drag-and-drop event scheduling instead of chronological lists, explore CalendarJS Schedule:
- Day, week, and weekdays views
- Drag & drop event management
- Resource scheduling with time slots
- Perfect for appointment systems and team calendars
When to Use Each
| Component | Best For | Size | Reactive |
|---|---|---|---|
| LemonadeJS Timeline | Reactive Vue/React/Angular apps needing data binding | Pico | ✓ |
| CalendarJS Timeline | Standalone timelines, vanilla JS, non-reactive frameworks | 2.1KB | - |
| CalendarJS Schedule | Interactive event scheduling with drag & drop | 4.2KB | - |
Documentation
Installation
npm install @lemonadejs/timeline
Configuration Options
Initialize the timeline with these settings to tailor the plugin to your specific needs:
| Attribute | Description |
|---|---|
data: Item[] | An array of items to be displayed. Each item follows the structure defined in Entry Attributes below. |
type?: 'monthly' | Use 'monthly' to enable month-based filtering and the navigation header. Omit for a flat timeline. |
value?: String|Date | Initial date used to seed the timeline (useful with type: 'monthly'). Accepts an ISO string or a Date. |
format?: String | Mask used to render each item’s day label. Defaults: dd mmm yyyy (monthly), dddd, dd (otherwise). |
align?: String | Align the bullet points. Accepted values: "left", "right", "top", "bottom". Default: "left". |
order?: String | 'asc' for ascending or 'desc' for descending. Default: 'asc'. |
message?: String | Displayed when no items match. Default: "No records found". |
width?: Number | Container width in pixels. |
height?: Number | Container height in pixels. |
controls?: Boolean | Show the navigation header. Defaults to true when type is 'monthly', false otherwise. |
position?: String | Passthrough for the data-mode attribute on the data container; a hook for custom CSS. |
url?: String | URL for fetching data. |
remote?: Boolean | When true together with url and type: 'monthly', items are refetched on month/order change. |
onupdate?: Function | Called every time result is recomputed. Signature: (instance, result). |
onedition?: Function | Called when the edit icon on an editable item is clicked. Signature: (entry). |
Entry Attributes
Define each timeline event with these specific properties to customize its appearance and behavior:
| Attribute | Description |
|---|---|
date: String|Date | The item’s date. Required. |
title: String | Main label for the item. |
subtitle?: String | Secondary label shown under the title. |
description?: String | Long-form description. |
borderColor?: String | CSS color applied to the item’s border (--lm-border-color). |
borderStyle?: String | CSS border style ("solid", "dashed", "dotted", …); sets --lm-border-style. |
editable?: Boolean | When true, renders an edit icon that fires onedition. |
tags?: Tag[] | Array of tag chips rendered under the description. See Tag Attributes below. |
Tag Attributes
Each entry under tags accepts:
| Attribute | Description |
|---|---|
title: String | Tag label. |
color?: String | Background color applied to the chip (e.g. '#dc2626', 'red'). |
onclick?: Function | Click handler. Signature: (event, tag) => void. |
Instance API
Calling Timeline(root, options) returns an instance exposing:
| Property / Method | Description |
|---|---|
el | The root DOM element. |
data | Reactive array of items (assign to replace). |
result | Filtered + sorted array used for rendering. |
year / month | Current navigation position (month is 1-12). |
months | Localized month names. |
next() / prev() | Navigate one month forward / backward. |
Event Handling
Use these callbacks to monitor user interactions and to sync data with a server:
| Event | Trigger |
|---|---|
onupdate? | Fired after result is recomputed (initial render + every change). Signature: (instance, result). |
onedition? | Fired when the edit icon is clicked on an editable item. Signature: (entry). |
Examples
This section illustrates practical applications of the JavaScript timeline plugin, highlighting customization techniques for style and functionality to align with specific project objectives.
See more examples on https://codesandbox.io/p/sandbox/frosty-babycat-cjcwrk
Styling Attributes
Customize the visual aspects such as position, border style, and color schemes by setting the corresponding attributes during the initialization phase as demonstrated below:
<html>
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/style.min.css" />
<div id="root"></div>
<script>
const root = document.getElementById("root")
Timeline(root, {
data: [
{
title: "Issue Identification",
date: new Date(2022, 6, 1),
},
{
title: "Root Cause Analysis",
date: new Date(2022, 6, 2),
},
{
title: "Implementation of Solution",
date: new Date(2022, 6, 3),
borderColor: '#808000',
borderStyle: 'dashed',
},
{
title: "Implementation of Solution",
date: new Date(2022, 6, 4),
}
],
align: 'left'
})
</script>
</html>import lemonade from 'lemonadejs'
import Timeline from '@lemonadejs/timeline';
import '@lemonadejs/timeline/dist/style.css';
export default function App() {
const data = [
{
title: "Issue Identification",
date: new Date(2022, 6, 1),
},
{
title: "Root Cause Analysis",
date: new Date(2022, 6, 2),
},
{
title: "Implementation of Solution",
date: new Date(2022, 6, 3),
borderColor: '#808000',
borderStyle: 'dashed',
},
{
title: "Implementation of Solution",
date: new Date(2022, 6, 4),
}
];
return render => render`<Timeline data="${data}" align="left" /></div>`
}import React, { useRef } from 'react';
import Timeline from '@lemonadejs/timeline/dist/react';
export default function App() {
const myRef = useRef();
self.data = [
{
title: "Issue Identification",
date: new Date(2022, 6, 1),
},
{
title: "Root Cause Analysis",
date: new Date(2022, 6, 2),
},
{
title: "Implementation of Solution",
date: new Date(2022, 6, 3),
borderColor: '#808000',
borderStyle: 'dashed',
},
{
title: "Implementation of Solution",
date: new Date(2022, 6, 4),
}
];
return (<div>
<Timeline ref={myRef} data={data} align="left" />
</div>);
}<template>
<Timeline :data="data" align="left" />
</template>
<script>
import Timeline from '@lemonadejs/timeline/dist/vue'
export default {
name: 'App',
components: {
Timeline
},
data() {
return {
data: [
{
title: "Issue Identification",
date: new Date(2022, 6, 1),
},
{
title: "Root Cause Analysis",
date: new Date(2022, 6, 2),
},
{
title: "Implementation of Solution",
date: new Date(2022, 6, 3),
borderColor: '#808000',
borderStyle: 'dashed',
},
{
title: "Implementation of Solution",
date: new Date(2022, 6, 4),
}
]
};
},
}
</script>
<style>
</style>Positioning Options
Configure the placement of the timeline on the page by choosing from four positions: left, right, bottom, or top. Adjustments can also be made programmatically, as shown in the following example:
<html>
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/style.min.css" />
<label for="dropdown">Choose a position to align:</label>
<select id="dropdown">
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
<div id="root"></div>
<script>
const root = document.getElementById("root")
const dropdown = document.getElementById("dropdown")
const tml = Timeline(root, {
data: [
{ title: "Issue Identification", date: new Date(2022, 6, 1) },
{ title: "Root Cause Analysis", date: new Date(2022, 6, 2) },
{ title: "Implementation of Solution", date: new Date(2022, 6, 3) },
],
})
dropdown.addEventListener('change', (e) => {
tml.align = e.target.value
})
</script>
</html>import lemonade from 'lemonadejs'
import Timeline from '@lemonadejs/timeline';
import '@lemonadejs/timeline/dist/style.css';
export default function App() {
let ref = null;
const data = [
{ title: "Issue Identification", date: new Date(2022, 6, 1) },
{ title: "Root Cause Analysis", date: new Date(2022, 6, 2) },
{ title: "Implementation of Solution", date: new Date(2022, 6, 3) },
]
const change = (e) => {
ref.align = e.target.value;
}
return render => render`<div>
<label>Choose a position to align:</label>
<select onchange="${change}">
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
<Timeline data="${data}" :ref="${(e) => ref = e}" />
</div>`
}import React, { useRef } from 'react';
import Timeline from '@lemonadejs/timeline/dist/react';
export default function App() {
const myRef = useRef();
const data = [
{ title: "Issue Identification", date: new Date(2022, 6, 1) },
{ title: "Root Cause Analysis", date: new Date(2022, 6, 2) },
{ title: "Implementation of Solution", date: new Date(2022, 6, 3) },
];
return (<>
<label>Choose a position to align:</label>
<select onChange={(e) => myRef.current.align = e.target.value}>
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
<div>
<Timeline ref={myRef} data={data} />
</div>
</>);
}<template>
<label>Choose a position to align:</label>
<select @change="this.$refs.timelineRef.current.align = $event.target.value">
<option value="left">Left</option>
<option value="right">Right</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
<Timeline :data="data" ref="timelineRef" />
</template>
<script>
import Timeline from '@lemonadejs/timeline/dist/vue'
export default {
name: 'App',
components: {
Timeline
},
data() {
return {
data: [
{ title: "Issue Identification", date: new Date(2022, 6, 1) },
{ title: "Root Cause Analysis", date: new Date(2022, 6, 2) },
{ title: "Implementation of Solution", date: new Date(2022, 6, 3) },
]
}
}
}
</script>- See this React Timeline example on codesandbox;
- See this Vue Timeline example on codesandbox;
Monthly View Configuration
This setting enables a navigation control for monthly views, automatically grouping entries by their respective month and year.
<html>
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/style.min.css" />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/build/build/faker.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<div id="root"></div>
<script>
const root = document.getElementById("root")
let data = [];
for (let i = 0; i < 500; i++) {
data.push({
date: faker.date.between(new Date(2025, 1, 1), new Date(2025, 12, 30)),
title: faker.commerce.productName(),
subtitle: faker.commerce.department(),
description: faker.commerce.productName(),
})
}
const timeline = Timeline(root, {
data: data,
type: 'monthly',
align: 'left',
width: 500,
height: 500,
})
</script>
</html>import lemonade from 'lemonadejs'
import Timeline from '@lemonadejs/timeline';
import { faker } from '@faker-js/faker';
import '@lemonadejs/timeline/dist/style.css';
export default function App() {
const self = this;
const data = [];
for (let i = 0; i < 500; i++) {
data.push({
date: faker.date.between({
from: new Date(2025, 1, 1),
to: new Date(2025, 11, 30)
}),
title: faker.commerce.productName(),
subtitle: faker.commerce.department(),
description: faker.commerce.productName(),
})
}
this.data = data;
return render => render`<Timeline data="${this.data}" width="${500}" height="${300}" type="monthly" />`
}import React, { useRef } from 'react';
import Timeline from '@lemonadejs/timeline/dist/react';
import { faker } from '@faker-js/faker';
import '@lemonadejs/timeline/dist/style.css';
let data = [];
for (let i = 0; i < 10; i++) {
data.push({
date: faker.date.between({
from: new Date(2025, 1, 1),
to: new Date(2025, 11, 30)
}),
title: faker.commerce.productName(),
subtitle: faker.commerce.department(),
description: faker.commerce.productName(),
})
}
export default function App() {
const myRef = useRef();
return (<Timeline ref={myRef} data={data} type="monthly" />);
}<template>
<Timeline :data="data" type="monthly" ref="timelineRef" />
</template>
<script>
import Timeline from '@lemonadejs/timeline/dist/vue'
import { faker } from '@faker-js/faker';
export default {
name: 'App',
components: {
Timeline
},
data() {
let data = [];
for (let i = 0; i < 10; i++) {
data.push({
date: faker.date.between({
from: new Date(2025, 1, 1),
to: new Date(2025, 11, 30)
}),
title: faker.commerce.productName(),
subtitle: faker.commerce.department(),
description: faker.commerce.productName(),
})
}
return {
data: data
}
}
}
</script>Sorting
Configure the timeline to display events in ascending or descending chronological order.
<html>
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/timeline/dist/style.min.css" />
<label for="dropdown">Choose a sorting order:</label>
<select id="dropdown-order">
<option value="asc">Asc</option>
<option value="desc">Desc</option>
</select>
<div id="root"></div>
<script>
const root = document.getElementById("root")
const dropdown = document.getElementById("dropdown-order")
const tml = Timeline(root, {
data: [
{ title: "Issue Identification", date: new Date(2022, 6, 1) },
{ title: "Root Cause Analysis", date: new Date(2022, 6, 2) },
{ title: "Implementation of Solution", date: new Date(2022, 6, 3) },
],
})
dropdown.addEventListener('change', (e) => {
tml.order = e.target.value;
})
</script>
</html>import { state } from 'lemonadejs'
import Timeline from '@lemonadejs/timeline';
import '@lemonadejs/timeline/dist/style.css';
export default function App() {
let data = state([
{ title: "Issue Identification", date: new Date(2022, 6, 1) },
{ title: "Root Cause Analysis", date: new Date(2022, 6, 2) },
{ title: "Implementation of Solution", date: new Date(2022, 6, 3) },
])
const sort = (e) => {
this.ref.order = e.target.value
}
return render => render`<div>
<label>Choose a sorting order:</label>
<select onchange="${sort}">
<option value="asc">Asc</option>
<option value="desc">Desc</option>
</select>
<Timeline data="${data}" :ref="this.ref" />
</div>`
}import React, { useRef } from 'react';
import Timeline from '@lemonadejs/timeline/dist/react';
export default function App() {
const myRef = useRef();
const data = [
{ title: "Issue Identification", date: new Date(2022, 6, 1) },
{ title: "Root Cause Analysis", date: new Date(2022, 6, 2) },
{ title: "Implementation of Solution", date: new Date(2022, 6, 3) },
];
return (<>
<label for="dropdown">Choose a sorting order:</label>
<select id="dropdown" onChange={(e) => myRef.current.order = e.target.value}>
<option value="asc">Asc</option>
<option value="desc">Desc</option>
</select>
<div style={{ height: '300px', width: '500px' }}>
<Timeline ref={myRef} data={data} />
</div>
</>);
}<template>
<label for="dropdown">Choose a sorting order:</label>
<select id="dropdown" @change="this.$refs.timelineRef.current.order = $event.target.value">
<option value="asc">Asc</option>
<option value="desc">Desc</option>
</select>
<div style="height: 300px; width: 500px">
<Timeline :data="data" ref="timelineRef" />
</div>
</template>
<script>
import Timeline from '@lemonadejs/timeline/dist/vue'
export default {
name: 'App',
components: {
Timeline
},
data() {
return {
data: [
{ title: "Issue Identification", date: new Date(2022, 6, 1) },
{ title: "Root Cause Analysis", date: new Date(2022, 6, 2) },
{ title: "Implementation of Solution", date: new Date(2022, 6, 3) },
]
}
}
}
</script>