A customizable timeline component built with Lit for displaying chronological events in horizontal, vertical, or list layouts.
- Multiple layouts - Horizontal, vertical, and list view modes
- Auto date range detection - Automatically determines timeline bounds from events
- Pre-built themes - Dark, light, and modern themes included
- CSS Parts theming - Fully customizable appearance through CSS
::part()selectors - Responsive design - Scrollable container adapts to different screen sizes, with a one-sided mobile vertical layout
- Accessible - ARIA labels, keyboard navigation, and screen reader support
- Lightweight - Built on Lit with minimal dependencies
npm install lit-timelinePeer dependency: This package requires lit ^3.0.0 as a peer dependency.
npm install lit<script type="module">
import 'lit-timeline';
</script>
<timeline-component label="Project milestones">
<timeline-event date="2024-03-15">
<h3>Project Kick-off</h3>
<p>Initial planning phase begins.</p>
</timeline-event>
<timeline-event date="2024-06-01" image-src="launch.jpg" image-alt="Product launch">
<h3>Product Launch</h3>
<p>Official release to the public.</p>
</timeline-event>
</timeline-component>The main container that positions events on a timeline axis.
| Attribute | Type | Default | Description |
|---|---|---|---|
vertical |
boolean | false |
Display timeline vertically instead of horizontally |
list |
boolean | false |
Display as a simple list without timeline axis |
start-year |
number | auto | Override start year for timeline range |
end-year |
number | auto | Override end year for timeline range |
label |
string | Timeline |
Accessible label for the timeline region |
Horizontal timeline (default):
<timeline-component label="Company history">
<timeline-event date="2020-01-15">...</timeline-event>
<timeline-event date="2022-06-30">...</timeline-event>
</timeline-component>Vertical timeline:
<timeline-component vertical label="Project phases">
<timeline-event date="2024-01-01">...</timeline-event>
<timeline-event date="2024-03-15">...</timeline-event>
</timeline-component>List view (no timeline axis):
<timeline-component list label="Event list">
<timeline-event date="2024-01-01">...</timeline-event>
<timeline-event date="2024-06-15">...</timeline-event>
</timeline-component>Fixed year range:
<timeline-component start-year="1990" end-year="2020" label="Career timeline">
<timeline-event date="1995-06-15">...</timeline-event>
<timeline-event date="2010-09-01">...</timeline-event>
</timeline-component>Individual event cards displayed on the timeline.
| Attribute | Type | Default | Description |
|---|---|---|---|
date |
string | required | Canonical calendar date in strict YYYY-MM-DD format |
image-src |
string | "" |
URL for the event header image |
image-alt |
string | "" |
Alternative text for a meaningful image; empty is decorative |
| Slot | Description |
|---|---|
| default | Event content (typically <h3> title and <p> description) |
With image:
<timeline-event
date="2024-03-15"
image-src="photo.jpg"
image-alt="Speaker presenting at the conference"
>
<h3>Conference Talk</h3>
<p>Presented at the annual tech conference.</p>
</timeline-event>Without image:
<timeline-event date="2024-03-15">
<h3>Team Meeting</h3>
<p>Quarterly planning session with the team.</p>
</timeline-event>A standalone <timeline-event> is visible, relatively positioned, and keyboard focusable. When it is a direct child of <timeline-component>, the parent takes over its positioning and roving tabindex. Images are decorative by default; set image-alt only when the image conveys content not already present in the event text. Placeholder artwork is always decorative.
<timeline-component> owns the order of its direct children: it re-appends them into chronological order so that reading order matches visual order, and moves events with an unparseable date to the end. Author-supplied role, tabindex, and the inline styles it manages (position, left, top, max-width, visibility) are captured before the takeover and restored when the event leaves the timeline.
If a framework renders the events, give it a stable keyed list and let it own the data order — sort your event data chronologically before rendering. A framework that reconciles against unkeyed children may otherwise fight the reordering and duplicate or drop nodes.
The components include structural layout styles and usable default card colors, borders, radius, shadow, and typography. Customize those defaults with CSS custom properties and parts, or apply a pre-built theme for a coordinated appearance.
Three ready-to-use themes are included:
| Theme | File | Description |
|---|---|---|
| Dark | theme-dark.css |
Dark purple background with coral accents |
| Light | theme-light.css |
Clean light theme with blue accents |
| Modern | theme-modern.css |
Glass-morphism effects with teal accents |
Using a theme:
<!-- Import the theme CSS -->
<link rel="stylesheet" href="node_modules/lit-timeline/src/styles/theme-dark.css" />
<!-- Or with a bundler -->
<style>
@import 'lit-timeline/styles/theme-dark.css';
</style>
<!-- Wrap your timeline in the theme class -->
<div class="timeline-dark-theme">
<timeline-component label="My timeline">
<timeline-event date="2024-01-01">
<h3>Event Title</h3>
<p>Event description</p>
</timeline-event>
</timeline-component>
</div>Each theme uses a wrapper class:
.timeline-dark-theme- Dark theme.timeline-light-theme- Light theme.timeline-modern-theme- Modern theme
Use CSS ::part() selectors to create custom themes:
/* Apply a dark theme */
timeline-component::part(axis-line) {
stroke: #47476b;
stroke-width: 2;
}
timeline-component::part(connector-line) {
stroke: #47476b;
}
timeline-component::part(dot) {
fill: #ff6b6b;
}
timeline-component::part(marker-tick) {
stroke: #a4a4c1;
}
timeline-component::part(marker-text) {
fill: #a4a4c1;
}
timeline-event::part(card) {
background-color: #2c2c54;
border: 1px solid #47476b;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
timeline-event::part(image),
timeline-event::part(image-placeholder) {
background-color: #3a3a66;
}
/* Slotted content styling */
timeline-event h3 {
color: #ffffff;
}
timeline-event p {
color: #a4a4c1;
}<timeline-component> supports these properties:
| Property | Default | Purpose |
|---|---|---|
--timeline-axis-color |
#47476b |
Axis color |
--timeline-axis-width |
2 |
Axis stroke width |
--timeline-connector-color |
#47476b |
Connector color |
--timeline-connector-width |
2 |
Connector stroke width |
--timeline-dot-color |
#ff6b6b |
Event dot color |
--timeline-dot-size |
5 |
Event dot radius |
--timeline-marker-color |
#a4a4c1 |
Marker tick color |
--timeline-marker-text-color |
#a4a4c1 |
Marker label color |
--timeline-marker-font-size |
0.9rem |
Marker label font size |
--timeline-h-row-gap |
330px |
Horizontal-mode row stride |
--timeline-v-column-gap |
100px |
Vertical-mode column gap |
--timeline-scrollbar-thumb-color |
#47476b |
Scrollbar thumb color |
--timeline-scrollbar-track-color |
transparent |
Scrollbar track color |
--timeline-list-gap |
16px |
List-mode event gap |
--timeline-list-padding |
20px |
List-mode container padding |
<timeline-event> supports these properties:
| Property | Default | Purpose |
|---|---|---|
--timeline-event-width |
250px |
Card width |
--timeline-event-bg-color |
#2c2c54 |
Card background |
--timeline-event-border-color |
#47476b |
Card border color |
--timeline-event-border-radius |
16px |
Card corner radius |
--timeline-event-shadow |
0 10px 30px rgba(0, 0, 0, 0.3) |
Card box shadow |
--timeline-event-image-height |
140px |
Image area height |
--timeline-event-content-padding |
20px |
Content padding |
--timeline-event-content-min-height |
125px |
Content minimum height |
--timeline-event-heading-color |
#ffffff |
Slotted heading color |
--timeline-event-heading-font-size |
1.1rem |
Slotted heading font size |
--timeline-event-heading-font-weight |
700 |
Slotted heading font weight |
--timeline-event-text-color |
#a4a4c1 |
Slotted paragraph color |
--timeline-event-text-font-size |
0.9rem |
Slotted paragraph font size |
--timeline-event-placeholder-bg |
#3a3a66 |
Placeholder background |
--timeline-event-placeholder-color |
#8c8caf |
Placeholder text color |
--timeline-event-focus-offset |
4px |
Focus outline offset |
--timeline-event-date-font-size |
0.85rem |
List date font size |
--timeline-event-date-font-weight |
500 |
List date font weight |
--timeline-event-date-color |
currentColor |
List date color |
--timeline-list-event-max-width |
600px |
List-mode card maximum width |
For styling, use CSS ::part() selectors:
Timeline Component Parts:
timeline-component::part(scroll-wrapper) {
/* Scrollable container */
}
timeline-component::part(container) {
/* Main container */
}
timeline-component::part(svg-layer) {
/* SVG overlay */
}
timeline-component::part(axis-line) {
/* Timeline axis */
}
timeline-component::part(connector-line) {
/* Event connectors */
}
timeline-component::part(marker-tick) {
/* Date marker ticks */
}
timeline-component::part(marker-text) {
/* Date marker labels */
}
timeline-component::part(dot) {
/* Event dots */
}Timeline Event Parts:
timeline-event::part(card) {
/* Card container */
}
timeline-event::part(image) {
/* Event image */
}
timeline-event::part(image-placeholder) {
/* Placeholder when no image */
}
timeline-event::part(content) {
/* Content area */
}
timeline-event::part(date) {
/* Date display (shown in list view) */
}/* Grayscale images that colorize on hover */
.grayscale-hover timeline-event::part(image) {
filter: grayscale(100%);
transition: filter 0.3s ease;
}
.grayscale-hover timeline-event:hover::part(image) {
filter: grayscale(0%);
}Event dates must be real calendar dates in canonical YYYY-MM-DD form. For example, 2024-02-29 is accepted, while 2023-02-29, 2024-2-09, missing dates, and normalized overflow dates such as 2024-02-30 are invalid. Formatting uses UTC, so the displayed calendar day does not change with the viewer's time zone.
Invalid or missing-date events are hidden, excluded from the date range and layout, and produce a deterministic console warning instead of breaking valid siblings. Direct valid <timeline-event> children are reordered chronologically in the light DOM, keeping visual, keyboard, and assistive-technology order aligned.
The timeline automatically determines its date range:
- Short timelines (< 2 years): Shows monthly markers (e.g., "Mar 24", "Apr 24")
- Long timelines (≥ 2 years): Shows 5-year markers (e.g., "1990", "1995", "2000")
Override this with start-year and end-year attributes for explicit control. Each defined bound must be a finite integer from 1 through 9999, and the start must not exceed the end; invalid ranges warn and render no layout. Vertical timelines narrower than 600px use a one-sided layout with the axis on the left and every card on the right, without horizontal scrolling. At 600px and wider, vertical layouts alternate cards on both sides.
The timeline component is designed with accessibility in mind, following WCAG 2.1 AA guidelines.
- Axis timelines expose a
role="region"; list mode exposes a labelledrole="list". Omitted or empty labels fall back toTimeline - Each
<timeline-event>host exposesrole="article"with the slotted heading as its accessible name, so the element that receives focus is the one carrying the semantics; in list mode the host becomes a list item instead - Each event has one date: visually hidden in axis modes and visibly rendered in list mode
- SVG decorations are hidden from assistive technology (
aria-hidden="true")
The timeline uses the roving tabindex pattern for efficient keyboard navigation:
| Key | Action |
|---|---|
Tab |
Move focus into/out of the timeline |
Arrow Right |
Move to next event (horizontal layout) |
Arrow Left |
Move to previous event (horizontal layout) |
Arrow Down |
Move to next event (vertical layout) |
Arrow Up |
Move to previous event (vertical layout) |
Home |
Move to first event |
End |
Move to last event |
How it works:
- Press
Tabto focus the timeline, thenTabagain to focus the first event - Use arrow keys to navigate between events (direction depends on layout orientation)
- Press
Tabto exit the timeline and continue to the next focusable element
<!-- Horizontal: use Left/Right arrows -->
<timeline-component label="History">
<timeline-event date="2024-01-01">...</timeline-event>
<timeline-event date="2024-06-01">...</timeline-event>
</timeline-component>
<!-- Vertical: use Up/Down arrows -->
<timeline-component vertical label="Process">
<timeline-event date="2024-01-01">...</timeline-event>
<timeline-event date="2024-06-01">...</timeline-event>
</timeline-component>- Only one event is in the tab order at a time (roving tabindex)
- Focus automatically scrolls events into view
- Visible focus indicator with customizable offset (
--timeline-event-focus-offset)
Supports all modern browsers:
- Chrome/Edge 88+
- Firefox 78+
- Safari 14+
Full TypeScript support with exported types:
import { TimelineComponent, TimelineEvent } from 'lit-timeline';
import type { TimelineEventData, EventLayout, SVGData } from 'lit-timeline';
// Type-safe element selection
const timeline = document.querySelector('timeline-component')!;
timeline.vertical = true;
const event = document.querySelector('timeline-event')!;
console.log(event.date); // stringnpm test # Build current source, then run cross-browser unit tests
npm run test:unit # Run unit tests against output that is already built
npm run test:watch # Build once, then run unit tests in watch mode
npm run test:package
# Reliable focused diagnosis without whole-suite coverage thresholds:
npm run build && npx web-test-runner --files test/unit/date-utils.test.ts --coverage=false

